juici 0.0.0 → 0.0.1.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/.gitignore +2 -0
  2. data/Gemfile +4 -1
  3. data/Gemfile.lock +26 -16
  4. data/README.md +45 -6
  5. data/Rakefile +24 -0
  6. data/bin/juicic +54 -0
  7. data/config/mongoid.yml.sample +20 -5
  8. data/juici-interface.gemspec +19 -0
  9. data/juici.gemspec +7 -3
  10. data/lib/juici.rb +8 -3
  11. data/lib/juici/app.rb +19 -23
  12. data/lib/juici/build_environment.rb +1 -1
  13. data/lib/juici/build_logic.rb +10 -1
  14. data/lib/juici/build_queue.rb +30 -3
  15. data/lib/juici/callback.rb +9 -5
  16. data/lib/juici/config.rb +4 -0
  17. data/lib/juici/controllers.rb +6 -0
  18. data/lib/juici/controllers/base.rb +26 -0
  19. data/lib/juici/controllers/build_queue.rb +14 -0
  20. data/lib/juici/controllers/builds.rb +74 -0
  21. data/lib/juici/controllers/index.rb +20 -0
  22. data/lib/juici/controllers/{trigger_controller.rb → trigger.rb} +24 -5
  23. data/lib/juici/database.rb +19 -13
  24. data/lib/juici/exceptions.rb +2 -0
  25. data/lib/juici/find_logic.rb +11 -0
  26. data/lib/juici/helpers/form_helpers.rb +11 -0
  27. data/lib/juici/helpers/html_helpers.rb +4 -0
  28. data/lib/juici/helpers/url_helpers.rb +28 -0
  29. data/lib/juici/interface.rb +13 -0
  30. data/lib/juici/models/build.rb +81 -24
  31. data/lib/juici/models/project.rb +1 -1
  32. data/lib/juici/server.rb +103 -40
  33. data/lib/juici/version.rb +8 -0
  34. data/lib/juici/views/README.markdown +45 -6
  35. data/lib/juici/views/about.erb +5 -5
  36. data/lib/juici/views/builds/edit.erb +23 -0
  37. data/lib/juici/views/builds/list.erb +26 -33
  38. data/lib/juici/views/builds/new.erb +42 -37
  39. data/lib/juici/views/builds/show.erb +4 -28
  40. data/lib/juici/views/index.erb +30 -13
  41. data/lib/juici/views/layout.erb +22 -13
  42. data/lib/juici/views/not_found.erb +3 -0
  43. data/lib/juici/views/partials/builds/debug.erb +12 -18
  44. data/lib/juici/views/partials/builds/output.erb +1 -0
  45. data/lib/juici/views/partials/builds/show.erb +19 -0
  46. data/lib/juici/views/partials/builds/sidebar.erb +13 -0
  47. data/lib/juici/views/partials/index/recently_built.erb +19 -0
  48. data/lib/juici/views/queue/list.erb +6 -0
  49. data/lib/juici/watcher.rb +33 -25
  50. data/public/favicon.ico +0 -0
  51. data/public/images/black_denim.png +0 -0
  52. data/public/styles/builds.css +59 -8
  53. data/public/styles/juici.css +226 -2
  54. data/public/vendor/bootstrap.css +6004 -0
  55. data/public/vendor/bootstrap.js +2036 -0
  56. data/public/vendor/img/glyphicons-halflings-white.png +0 -0
  57. data/public/vendor/jquery.js +9440 -0
  58. data/script/cibuild +10 -0
  59. data/spec/build_callback_spec.rb +46 -1
  60. data/spec/build_process_spec.rb +71 -5
  61. data/spec/build_queue_spec.rb +3 -1
  62. data/spec/controllers/builds_spec.rb +68 -0
  63. data/spec/controllers/index_spec.rb +28 -0
  64. data/spec/juici_app_spec.rb +0 -15
  65. data/spec/models/build_spec.rb +54 -0
  66. data/spec/spec_helper.rb +13 -0
  67. metadata +76 -12
  68. data/lib/juici/controllers/build_controller.rb +0 -0
  69. data/lib/juici/url_helpers.rb +0 -15
@@ -1,7 +1,7 @@
1
1
  module Juici
2
2
  class Project
3
3
  include Mongoid::Document
4
- include Juici.url_helpers("builds")
4
+ extend FindLogic
5
5
 
6
6
  field :name, type: String
7
7
 
@@ -11,6 +11,8 @@ module Juici
11
11
  end
12
12
 
13
13
  helpers do
14
+ include Ansible
15
+
14
16
  Dir[File.dirname(__FILE__) + "/helpers/**/*.rb"].each do |file|
15
17
  load file
16
18
  end
@@ -19,9 +21,16 @@ module Juici
19
21
  dir = File.dirname(File.expand_path(__FILE__))
20
22
 
21
23
  def self.start(host, port)
22
- Database.initialize!
23
- @@juici = App.new
24
- Juici::Server.run! :host => host, :port => port
24
+ @@juici = App.new
25
+ Juici::Server.run!(:host => host, :port => port) do |server|
26
+ [:INT, :TERM].each do |sig|
27
+ trap(sig) do
28
+ $stderr.puts "Shutting down JuiCI"
29
+ App.shutdown
30
+ server.respond_to?(:stop!) ? server.stop! : server.stop
31
+ end
32
+ end
33
+ end
25
34
  end
26
35
 
27
36
  def self.rack_start(project_path)
@@ -33,76 +42,130 @@ module Juici
33
42
  set :static, true
34
43
 
35
44
  get '/' do
36
- @page = :index
37
- erb(:index, {}, :juici => juici)
45
+ Controllers::Index.new.index do |template, opts|
46
+ erb(template, {}, opts)
47
+ end
38
48
  end
39
49
 
40
50
  get '/about' do
41
- @page = :about
42
- @action = :show
43
- erb(:about)
51
+ Controllers::Index.new.about do |template, opts|
52
+ erb(template, {}, opts)
53
+ end
44
54
  end
45
55
 
46
56
  get '/builds' do
47
- @page = :builds
48
- @action = :list
49
- erb(:builds, {}, :juici => juici)
57
+ Controllers::Index.new.builds do |template, opts|
58
+ erb(template, {}, opts)
59
+ end
50
60
  end
51
61
 
52
- get '/builds/new' do
53
- @page = :builds
54
- @action = :new
55
- erb(:"builds/new", :juici => juici)
62
+ get '/support' do
63
+ Controllers::Index.new.support do |template, opts|
64
+ erb(template, {}, opts)
65
+ end
56
66
  end
57
67
 
58
68
  post '/builds/new' do
59
- TriggerController.new(params[:project], params).build!
60
- @redirect_to = build_url_for(params[:project])
61
- erb(:redirect, {}, :juici => juici)
69
+ build = Controllers::Trigger.new(params[:project], params).build!
70
+ @redirect_to = build_url_for(build)
71
+ erb(:redirect, {}, {})
72
+ end
73
+
74
+ post '/builds/:project/rebuild/:id' do
75
+ build = Controllers::Trigger.new(params[:project], params).rebuild!
76
+ @redirect_to = build_url_for(build)
77
+ erb(:redirect, {}, {})
78
+ end
79
+
80
+ post '/builds/:user/:project/rebuild/:id' do
81
+ params[:project] = "#{params[:user]}/#{params[:project]}"
82
+ build = Controllers::Trigger.new(params[:project], params).rebuild!
83
+ @redirect_to = build_url_for(build)
84
+ erb(:redirect, {}, {})
62
85
  end
63
86
 
64
- def list_builds(params)
65
- erb(:"builds/list", {}, :juici => juici, :project => params[:project])
87
+ post '/builds/kill' do
88
+ build = Controllers::Builds.new(params).kill
89
+ @redirect_to = build_url_for(build)
90
+ erb(:redirect, {}, {})
91
+ end
92
+
93
+ post '/builds/cancel' do
94
+ build = Controllers::Builds.new(params).cancel
95
+ @redirect_to = build_url_for(build)
96
+ erb(:redirect, {}, {})
97
+ end
98
+
99
+ get ::Juici::Routes::NEW_BUILD do
100
+ Controllers::Builds.new(params).new do |template, opts|
101
+ erb(template, {}, opts)
102
+ end
66
103
  end
67
104
 
68
105
  get '/builds/:project/list' do
69
- @page = :builds
70
- @action = :list
71
- list_builds(params)
106
+ Controllers::Builds.new(params).list do |template, opts|
107
+ erb(template, {}, opts)
108
+ end
72
109
  end
73
110
 
74
111
  get '/builds/:user/:project/list' do
75
- @page = :builds
76
- @action = :list
77
112
  params[:project] = "#{params[:user]}/#{params[:project]}"
78
- list_builds(params)
113
+ Controllers::Builds.new(params).list do |template, opts|
114
+ erb(template, {}, opts)
115
+ end
116
+ end
117
+
118
+ get '/builds/:project/edit/:id' do
119
+ Controllers::Builds.new(params).edit do |template, opts|
120
+ erb(template, {}, opts)
121
+ end
122
+ end
123
+
124
+ get '/builds/:user/:project/edit/:id' do
125
+ params[:project] = "#{params[:user]}/#{params[:project]}"
126
+ Controllers::Builds.new(params).edit do |template, opts|
127
+ erb(template, {}, opts)
128
+ end
129
+ end
130
+
131
+ post '/builds/:project/edit/:id' do
132
+ build = Controllers::Builds.new(params).update!
133
+ @redirect_to = build_url_for(build)
134
+ erb(:redirect, {}, {})
79
135
  end
80
136
 
81
- def show_build(params)
82
- erb(:"builds/show", {}, :juici => juici, :project => params[:project], :id => params[:id])
137
+ post '/builds/:user/:project/edit/:id' do
138
+ params[:project] = "#{params[:user]}/#{params[:project]}"
139
+ build = Controllers::Builds.new(params).update!
140
+ @redirect_to = build_url_for(build)
141
+ erb(:redirect, {}, {})
83
142
  end
84
143
 
85
144
  get '/builds/:project/show/:id' do
86
- @page = :builds
87
- @action = :show
88
- show_build(params)
145
+ Controllers::Builds.new(params).show do |template, opts|
146
+ erb(template, {}, opts)
147
+ end
89
148
  end
90
149
 
91
150
  get '/builds/:user/:project/show/:id' do
92
- @page = :builds
93
- @action = :show
94
151
  params[:project] = "#{params[:user]}/#{params[:project]}"
95
- show_build(params)
152
+ Controllers::Builds.new(params).show do |template, opts|
153
+ erb(template, {}, opts)
154
+ end
96
155
  end
97
156
 
98
- get '/support' do
99
- @page = :support
100
- @action = :show
101
- erb(:"support", {})
157
+ post '/trigger/:project' do
158
+ Controllers::Trigger.new(params[:project], params).build!
102
159
  end
103
160
 
104
- post '/trigger/:project' do
105
- TriggerController.new(params[:project], params).build!
161
+ get '/queue' do
162
+ Controllers::BuildQueue.new(params).list do |template, opts|
163
+ erb(template, {}, opts)
164
+ end
165
+ end
166
+
167
+ not_found do
168
+ erb(:not_found, {}, {})
106
169
  end
107
170
 
108
171
  end
@@ -0,0 +1,8 @@
1
+ module Juici
2
+ VERSION_MAJOR=0
3
+ VERSION_MINOR=0
4
+ VERSION_PATCH=1
5
+ VERSION_EXTRA=".alpha1"
6
+
7
+ VERSION="#{VERSION_MAJOR}.#{VERSION_MINOR}.#{VERSION_PATCH}#{VERSION_EXTRA}"
8
+ end
@@ -13,8 +13,8 @@ It's designed to work well with [agent99](https://github.com/99designs/agent99)
13
13
 
14
14
  ## Important but Miscellaneous
15
15
 
16
- If you create child process in modules/plugins then you need to register your
17
- disinterest or JuiCI will think they're builds and that would be bad
16
+ If you create child processes in modules/plugins then you need to register your
17
+ disinterest or JuiCI will think they're builds and that would be bad.
18
18
 
19
19
  ## Setup
20
20
 
@@ -27,12 +27,26 @@ bundle exec bin/juici
27
27
 
28
28
  is all you need to have a working instance (provided that you have mongo installed)
29
29
 
30
+ ### Gotchas
31
+
32
+ Make sure you don't do something innocuous like
33
+
34
+ ```bash
35
+ bundle install --path .bundle
36
+ ```
37
+
38
+ this might look sane (and it is, kinda) but owing to a quick in bundler, it
39
+ will break any ruby code you try to build.
40
+
41
+ I'm working on a workaround, but in the meantime the fix is to not do it!
42
+
30
43
  ## Usage
31
44
 
32
- JuiCI chooses to be very "Mongo" (which is an adjective now), in that you don't
33
- need to formally create a project. Just request a build; however this means
34
- that on your first build you will need to send the commands to create your test
35
- environment)
45
+ JuiCI is very focused on minimal configuration; meaning that beyond starting
46
+ the server and pointing it at a mongoDB instance, you do not need to do
47
+ anything special to build a new project. Just request a build; however this
48
+ means that on your first build you will need to send the commands to create
49
+ your test environment)
36
50
 
37
51
  Example:
38
52
 
@@ -67,6 +81,20 @@ called with an (as yet unformalised) json body as the body if/when the build
67
81
  reaches that state. Alternately you may specify "any" as the callback state and
68
82
  it will be called on all state changes.
69
83
 
84
+ ## Integration
85
+
86
+ Apps written in ruby wanting to interact with Juici can include the
87
+ `juici-interface` gem, which presently exposes a few constants to line up with
88
+ JuiCI's internal state.
89
+ Over time this will be expanded, but for now they are:
90
+
91
+ ```ruby
92
+ Juici::BuildStatus::PASS
93
+ Juici::BuildStatus::FAIL
94
+ Juici::BuildStatus::START
95
+ Juici::BuildStatus::WAIT
96
+ ```
97
+
70
98
  ## Security
71
99
 
72
100
  JuiCI poses some interesting security conecerns. First off, it will allow
@@ -92,8 +120,19 @@ specifically implement it, your process won't see any of the signal handling
92
120
  madness. The shell(`/bin/sh`) will see everything, and if killed, your
93
121
  processes will become orphaned, but carry on.
94
122
 
123
+ ## Authors
124
+
125
+ * [Richo Healey](https://github.com/rcho)
126
+ * [Alec Sloman](https://github.com/alecsloman)
127
+
95
128
  ## Contact
96
129
 
97
130
  JuiCI's code lives on [Github](https://github.com/richo/juici)
98
131
  and the [author](mailto:richo@psych0tik.net) can be contacted on
99
132
  [Twitter](https://twitter.com/rich0H)
133
+
134
+ ## Legalese
135
+
136
+ (c) Richo Healey 2012, richo@psych0tik.net
137
+
138
+ Released under the terms of the MIT license.
@@ -1,12 +1,12 @@
1
- <div class="row">
2
- <div class="span8">
1
+ <div class="row-fluid">
2
+ <div class="span9">
3
3
  <div>
4
- <%= GitHub::Markdown.render(File.read("lib/juici/views/README.markdown")) %>
4
+ <%= content %>
5
5
  </div>
6
6
  </div>
7
- <div class="span4">
7
+ <div class="span2 offset1">
8
8
  <div>
9
- <p>Quick Links</p>
9
+ <h3 class="block-header builds-header--failure">quick links</h3>
10
10
  <ul>
11
11
  <li>richo on <a href="https://twitter.com/rich0H">twitter</a></li>
12
12
  <li>richo on <a href="https://github.com/richo">github</a></li>
@@ -0,0 +1,23 @@
1
+ <h4 class="accordion-toggle ">
2
+ <%= display_title rescue build.display_title %>&nbsp;<% build[:warnings].each do |warning| %><span class="label label-important"><%= warning %></span><% end %>
3
+ </h4>
4
+ <div class="row-fluid" style="padding-top:20px">
5
+ <div><p>Note that any changes made here will only take effect when this project starts building. Running builds will not inherit these changes, but you'll likey confuse the hell out of anyone trying to debug it.</p></div>
6
+ <form action="<%= edit_url_for(build) %>" method="post" class="submit-build">
7
+ <div class="span4">
8
+ <h3>Misc</h3>
9
+ <% ::Juici::Build::EDITABLE_ATTRIBUTES[:string].each do |attr| %>
10
+ <%# TODO css transform to make this pretty %>
11
+ <label><%= attr %></label>
12
+ <input type="text" name="<%= attr %>" value="<%= build[attr] %>">
13
+ <% end %>
14
+ <button type="submit" class="btn">Submit</button>
15
+ </div>
16
+ <div class="span4">
17
+ <h3>Environment</h3>
18
+ </div>
19
+ <div class="span4">
20
+ <h3>Callbacks</h3>
21
+ </div>
22
+ </form>
23
+ </div>
@@ -1,34 +1,27 @@
1
- <% if proj = ::Juici::Project.where(name: project).first %>
2
- <script>
3
- $(document).ready(function() {
4
- $(".collapse").collapse();
5
- });
6
- </script>
7
- <h3><em><%= proj.name %></em> - <a href="/builds/new?project=<%= proj.name %>"><em>New Build</em></a></h3>
8
- <% ::Juici::Build.where(parent: proj.name).sort.reverse.each_with_index do |build, idx| %>
9
- <div class="accordion" id="accordion<%= idx %>">
10
- <div class="accordion-heading">
11
- <a class="accordion-toggle <%= build.heading_color %>" data-toggle="collapse" data-parent="#accordion<%= idx+1 %>" href="#collapse_<%= idx %>">
12
- <%= build.display_title %>&nbsp;<% build[:warnings].each do |warning| %><span class="label label-important"><%= warning %></span><% end %>
13
- </a>
1
+ <script>
2
+ $(function() {
3
+ $('.accordion-set-url').on('click', '.set-url', function() {
4
+ history.pushState(null, null, $(this).data('url'));
5
+ })
6
+ });
7
+ </script>
8
+
9
+ <div class="row-fluid">
10
+ <!-- Pagination -->
11
+ <div class="span1">
12
+ <% pages.times.with_index do |page| %>
13
+ <a href="?page=<%= page %>" class="bloop<%= " active" if page == params[:page] %>"><%= page %></a>
14
+ <% end %>
15
+ </div>
16
+ <!-- Main contents -->
17
+ <div class="span11">
18
+ <!-- Create a new build -->
19
+ <h3 class="project-title"><a href="<%= build_url_for(project) %>"><%= project.name %></a><a class="btn pull-right" href="/builds/new?project=<%= project.name %>">New Build</a></h3>
20
+ <!-- Build history -->
21
+ <% builds.each_with_index do |build, idx| %>
22
+ <div class="accordion accordion-set-url" id="accordion<%= idx %>">
23
+ <%= erb(:"partials/builds/show", :locals => { :build => build, :idx => idx }) %>
14
24
  </div>
15
- <div id="collapse_<%= idx %>" class="accordion-body collapse in">
16
- <div class="row">
17
- <div class="span8">
18
- <div>
19
- <a href="<%= build_url_for(build) %>">Link to this build</a>
20
- </div>
21
- <div class="accordion-inner build-output">
22
- <%= build.output if build.respond_to? :output %>
23
- </div>
24
- </div>
25
- <div class="span4">
26
- <%= erb(:"partials/builds/debug", :locals => { :build => build }) %>
27
- </div>
28
- </div>
29
- </div>
30
- </div>
31
- <% end %>
32
- <% else %>
33
- No such project.
34
- <% end %>
25
+ <% end %>
26
+ </div>
27
+ </div>
@@ -1,38 +1,43 @@
1
- <div class="row">
2
- <div class="span6">
3
- <form class="well" action="/builds/new" method="post">
4
- <label>Project Name</label>
5
- <input type="text" name="project" class="span3" value="<%= params[:project] %>" <%= "readonly" if params[:project] %>>
6
- <label>Environment</label>
7
- <input type="text" name="environment" class="span3" value="">
8
- <span class="help-block">nb: Expects a valid json hash</span>
9
- <label>Priority</label>
10
- <input type="text" name="priority" class="span3" value="1">
11
- <label>Command</label>
12
- <textarea name="command" class="span3"></textarea>
13
- <%# TODO -> When callbacks get implemented %>
14
- <%# <label>Callback URL</label> %>
15
- <%# <input type="text" name="callback" class="span3"> %>
16
- <label><%# HACK %></label>
17
- <button type="submit" class="btn">Submit</button>
18
- </form>
19
- </div>
20
- <div class="span6">
21
- <p>From here you can create a new build</p>
22
- <p>Name is a unique identifier for the project. It doesn't need to already exist, but builds for a like project need to be named identically</p>
23
- <p>Environment is a json hash of KEY - VALUE pairs, to be passed into the child environment</p>
24
- <p>Command is where the magic happens. Often they'll be longwinded, but a reasonable boilerplate might look something like<p>
25
- <pre class="prettyprint linenums">
26
- if [ ! -d .git ]; then
27
- git init .
28
- git remote add origin git://github.com/$user/$project.git
29
- fi
30
-
31
- git fetch origin
32
- git checkout -fq origin/master
33
-
34
- bundle install --path .bundle
35
- bundle exec rake spec
36
- </pre>
37
- </div>
1
+ <div class="row-fluid">
2
+
3
+ <div class="span4">
4
+ <%= erb(:"partials/index/recently_built") %>
5
+ </div>
6
+
7
+ <div class="span8">
8
+
9
+ <h1 class="block-header">new</h1>
10
+ <p>From here you can create a new build</p>
11
+ <p>Name is a unique identifier for the project. It doesn't need to already exist, but builds for a like project need to be named identically</p>
12
+ <p>Environment is a json hash of KEY - VALUE pairs, to be passed into the child environment</p>
13
+ <p>Command is where the magic happens. Often they'll be longwinded, but a reasonable boilerplate might look <a href="#">something like this.</a><p>
14
+ <hr>
15
+
16
+ <form action="/builds/new" method="post" class="submit-build">
17
+
18
+ <div class="row-fluid">
19
+
20
+ <!-- First Column -->
21
+ <div class="span3">
22
+ <label>Project Name</label>
23
+ <input type="text" name="project" value="<%= params[:project] %>" <%= "readonly" if params[:project] %>>
24
+ <label>Environment</label>
25
+ <input type="text" name="environment" placeholder="expects a valid json hash" value="">
26
+ <label>Priority</label>
27
+ <input type="text" name="priority" value="1">
28
+ </div><!-- End first column -->
29
+
30
+ <div class="span8 offset1">
31
+ <label>Command</label>
32
+ <textarea name="command" class="input-xxlarge" rows="4"></textarea>
33
+ <%# TODO -> When callbacks get implemented %>
34
+ <%# <label>Callback URL</label> %>
35
+ <%# <input type="text" name="callback" class="span3"> %>
36
+ <!-- <label><%# HACK %></label> -->
37
+ <button type="submit">START BUILD</button>
38
+ </div>
39
+
40
+ </div>
41
+ </form>
42
+ </div>
38
43
  </div>