juici 0.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. data/.gitignore +2 -0
  2. data/Gemfile +5 -0
  3. data/Gemfile.lock +74 -0
  4. data/Procfile +1 -0
  5. data/README.md +138 -0
  6. data/Rakefile +33 -0
  7. data/TODO.md +28 -0
  8. data/bin/juici +7 -0
  9. data/bin/juicic +54 -0
  10. data/config/mongoid.yml.sample +25 -0
  11. data/juici-interface.gemspec +19 -0
  12. data/juici.gemspec +32 -0
  13. data/lib/juici.rb +24 -0
  14. data/lib/juici/app.rb +67 -0
  15. data/lib/juici/build_environment.rb +38 -0
  16. data/lib/juici/build_logic.rb +37 -0
  17. data/lib/juici/build_queue.rb +111 -0
  18. data/lib/juici/callback.rb +26 -0
  19. data/lib/juici/config.rb +11 -0
  20. data/lib/juici/controllers.rb +6 -0
  21. data/lib/juici/controllers/base.rb +26 -0
  22. data/lib/juici/controllers/build_queue.rb +14 -0
  23. data/lib/juici/controllers/builds.rb +74 -0
  24. data/lib/juici/controllers/index.rb +20 -0
  25. data/lib/juici/controllers/trigger.rb +85 -0
  26. data/lib/juici/database.rb +25 -0
  27. data/lib/juici/exceptions.rb +2 -0
  28. data/lib/juici/find_logic.rb +11 -0
  29. data/lib/juici/helpers/form_helpers.rb +11 -0
  30. data/lib/juici/helpers/html_helpers.rb +4 -0
  31. data/lib/juici/helpers/url_helpers.rb +38 -0
  32. data/lib/juici/interface.rb +13 -0
  33. data/lib/juici/models/build.rb +190 -0
  34. data/lib/juici/models/build_process.rb +7 -0
  35. data/lib/juici/models/project.rb +9 -0
  36. data/lib/juici/server.rb +172 -0
  37. data/lib/juici/version.rb +8 -0
  38. data/lib/juici/views/README.markdown +138 -0
  39. data/lib/juici/views/about.erb +16 -0
  40. data/lib/juici/views/builds.erb +7 -0
  41. data/lib/juici/views/builds/edit.erb +23 -0
  42. data/lib/juici/views/builds/list.erb +27 -0
  43. data/lib/juici/views/builds/new.erb +43 -0
  44. data/lib/juici/views/builds/show.erb +4 -0
  45. data/lib/juici/views/index.erb +30 -0
  46. data/lib/juici/views/layout.erb +44 -0
  47. data/lib/juici/views/not_found.erb +3 -0
  48. data/lib/juici/views/partials/builds/debug.erb +22 -0
  49. data/lib/juici/views/partials/builds/output.erb +1 -0
  50. data/lib/juici/views/partials/builds/show.erb +19 -0
  51. data/lib/juici/views/partials/builds/sidebar.erb +13 -0
  52. data/lib/juici/views/partials/index/recently_built.erb +19 -0
  53. data/lib/juici/views/queue/list.erb +6 -0
  54. data/lib/juici/views/redirect.erb +0 -0
  55. data/lib/juici/views/support.erb +6 -0
  56. data/lib/juici/watcher.rb +48 -0
  57. data/public/favicon.ico +0 -0
  58. data/public/images/black_denim.png +0 -0
  59. data/public/styles/builds.css +62 -0
  60. data/public/styles/juici.css +226 -0
  61. data/public/vendor/bootstrap.css +6004 -0
  62. data/public/vendor/bootstrap.js +2036 -0
  63. data/public/vendor/img/glyphicons-halflings-white.png +0 -0
  64. data/public/vendor/jquery.js +9440 -0
  65. data/sample/mongod.conf +5 -0
  66. data/script/cibuild +10 -0
  67. data/spec/build_callback_spec.rb +54 -0
  68. data/spec/build_environment_spec.rb +53 -0
  69. data/spec/build_process_spec.rb +96 -0
  70. data/spec/build_queue_spec.rb +63 -0
  71. data/spec/controllers/builds_spec.rb +68 -0
  72. data/spec/controllers/index_spec.rb +28 -0
  73. data/spec/juici_app_spec.rb +8 -0
  74. data/spec/models/build_spec.rb +54 -0
  75. data/spec/spec_helper.rb +26 -0
  76. metadata +290 -0
@@ -0,0 +1,8 @@
1
+ module Juici
2
+ VERSION_MAJOR=0
3
+ VERSION_MINOR=0
4
+ VERSION_PATCH=0
5
+ VERSION_EXTRA=".alpha1"
6
+
7
+ VERSION="#{VERSION_MAJOR}.#{VERSION_MINOR}.#{VERSION_PATCH}#{VERSION_EXTRA}"
8
+ end
@@ -0,0 +1,138 @@
1
+ ## JuiCI
2
+
3
+ JuiCI is a CI server that has a notion of queuing and priority.
4
+
5
+ It's designed to work well with [agent99](https://github.com/99designs/agent99) but will play nicely with most frontends to CI.
6
+
7
+ ## Features
8
+
9
+ * callbacks are created as builds are requested
10
+ * Builds are executed sequentially in a series of parallel queues.
11
+ * Queues can be dynamically created
12
+ * Build status visualised
13
+
14
+ ## Important but Miscellaneous
15
+
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
+
19
+ ## Setup
20
+
21
+ JuiCI is deliberately very light on the setup front.
22
+
23
+ ```bash
24
+ bundle install
25
+ bundle exec bin/juici
26
+ ```
27
+
28
+ is all you need to have a working instance (provided that you have mongo installed)
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
+
43
+ ## Usage
44
+
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)
50
+
51
+ Example:
52
+
53
+ ```bash
54
+ curl --data-ascii @/dev/stdin <<EOF
55
+ payload={"environment":{
56
+ "SHA1":"e8b179f75bbc8717c948af052353424d458af981"},
57
+ "command":"[ -d .git ] || (git init .; git remote add origin git://github.com/richo/twat.git); git fetch; git checkout $SHA1; bundle install; bundle exec rake spec"
58
+ EOF
59
+ ```
60
+
61
+ Using a convention like `script/cibuild` as in janky/hubot etc is advisable,
62
+ although bear in mind that the logic to checkout the repo will need to be
63
+ seperate.
64
+
65
+ ## Priority
66
+
67
+ JuiCI supports the notion of priority. Builds given without a priority will be
68
+ assigned priority 1 (to allow for marking a build as less important with
69
+ priority 0).
70
+
71
+ If juici recieves a new build with priority higher than any currently
72
+ unfinished, it will pause whatever it's doing and build the new project. If
73
+ there is a tie for priority, a FIFO queue is assumed.
74
+
75
+ JuiCI uses `SIGSTOP` and `SIGCONT` internally for job control.
76
+
77
+ ## Hooks
78
+
79
+ You may specify one or more callbacks when you request a build. They will be
80
+ called with an (as yet unformalised) json body as the body if/when the build
81
+ reaches that state. Alternately you may specify "any" as the callback state and
82
+ it will be called on all state changes.
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
+
98
+ ## Security
99
+
100
+ JuiCI poses some interesting security conecerns. First off, it will allow
101
+ anyone with access to run arbitrary commands on your server. I have
102
+ deliberately not implemented any kind of security inside JuiCI, it plays nicely
103
+ as a Rack application, and middlewares are much better suited to this task.
104
+
105
+ It should go without saying, but any builds started by JuiCI will inherit its
106
+ environment. This means that if you run it in dev mode and forward your ssh
107
+ agent, builds can ssh to other machines as you!
108
+
109
+ When running in production you should take steps to ensure that the user JuiCI
110
+ runs as is no more privileged than it needs to be, and sanitise its
111
+ environment before execution.
112
+
113
+ ## A note on subprocesses
114
+
115
+ JuiCI by default invokes everything in a subshell- indeed this is the only way
116
+ to approach this if you want to execute more than one command.
117
+
118
+ What this means to you as the user though is that unless you go to lengths to
119
+ specifically implement it, your process won't see any of the signal handling
120
+ madness. The shell(`/bin/sh`) will see everything, and if killed, your
121
+ processes will become orphaned, but carry on.
122
+
123
+ ## Authors
124
+
125
+ * [Richo Healey](https://github.com/rcho)
126
+ * [Alec Sloman](https://github.com/alecsloman)
127
+
128
+ ## Contact
129
+
130
+ JuiCI's code lives on [Github](https://github.com/richo/juici)
131
+ and the [author](mailto:richo@psych0tik.net) can be contacted on
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.
@@ -0,0 +1,16 @@
1
+ <div class="row-fluid">
2
+ <div class="span9">
3
+ <div>
4
+ <%= content %>
5
+ </div>
6
+ </div>
7
+ <div class="span2 offset1">
8
+ <div>
9
+ <h3 class="block-header builds-header--failure">quick links</h3>
10
+ <ul>
11
+ <li>richo on <a href="https://twitter.com/rich0H">twitter</a></li>
12
+ <li>richo on <a href="https://github.com/richo">github</a></li>
13
+ <li>JuiCI on <a href="https://github.com/richo/juici">github</a></li>
14
+ </div>
15
+ </div>
16
+ </div>
@@ -0,0 +1,7 @@
1
+ Projects:
2
+
3
+ <ul>
4
+ <% ::Juici::Project.all.each do |p| %>
5
+ <li><a href="<%= build_url_for(p['name']) %>"><%= p['name'] %></a></li>
6
+ <% end %>
7
+ </ul>
@@ -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>
@@ -0,0 +1,27 @@
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 }) %>
24
+ </div>
25
+ <% end %>
26
+ </div>
27
+ </div>
@@ -0,0 +1,43 @@
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>
43
+ </div>
@@ -0,0 +1,4 @@
1
+ <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>
2
+ <div class="accordion" id="accordion0">
3
+ <%= erb(:"partials/builds/show", :locals => { :build => build, :idx => 0, :show => true }) %>
4
+ </div>
@@ -0,0 +1,30 @@
1
+ <div class="row-fluid">
2
+ <div class="span8">
3
+ <h1 class="block-header">JuiCI</h1>
4
+ <p>
5
+ JuiCI is a CI server, written at RailsCamp after Jenkins left me feeling a little dead inside. JuiCI aims to solve some of the issues with existing (and outstanding) CI solutions like <a href="http://jenkins-ci.org/">Jenkins</a> and <a href="http://travis-ci.org">Travis</a>, by not trying to be all things to all people.
6
+ </p>
7
+ <p>
8
+ JuiCI has a very thin wrapper around build triggers, is almost entirely API driven, and has a queuing system with a notion of priority, globally shared across all projects.
9
+ </p>
10
+ <div>
11
+ In the past, this instance of Juici has built code for:
12
+ <ul>
13
+ <% ::Juici::Project.all.each do |p| %>
14
+ <li><a href="<%= build_url_for(p) %>"><%= p.name %></a></li>
15
+ <% end %>
16
+ </ul>
17
+ </div>
18
+ </div>
19
+ <div class="span4">
20
+ <h4 class="block-header builds-header">currently building</h4>
21
+ <ul class="builds">
22
+ <% $build_queue.currently_building.each do |build| %>
23
+ <li>
24
+ <a class="<%= build.heading_color %>" href="<%= build_url_for(build) %>"><%= build.link_title %></a>
25
+ </li>
26
+ <% end %>
27
+ </ul>
28
+ <%= erb(:"partials/index/recently_built") %>
29
+ </div>
30
+ </div>
@@ -0,0 +1,44 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <% if ::Juici.env == "development" %>
6
+ <link href="/vendor/bootstrap.css" rel="stylesheet">
7
+ <script src="/vendor/jquery.js"></script>
8
+ <script src="/vendor/bootstrap.js"></script>
9
+ <% else %>
10
+ <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.2.1/css/bootstrap.min.css" rel="stylesheet">
11
+ <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
12
+ <script src="//netdna.bootstrapcdn.com/twitter-bootstrap/2.2.1/js/bootstrap.min.js"></script>
13
+ <% end %>
14
+ <link href="/styles/juici.css" rel="stylesheet">
15
+ <% styles.each do |style| %>
16
+ <link href="/styles/<%= style %>.css" rel="stylesheet">
17
+ <% end rescue nil%>
18
+ <title>JuiCI</title>
19
+ <% if @redirect_to %>
20
+ <meta http-equiv="Refresh" content="0; url=<%= @redirect_to %>" />
21
+ <% end %>
22
+ </head>
23
+ <body>
24
+ <div class="navbar navbar-fixed-top">
25
+ <div class="navbar-inner">
26
+ <div class="container-fluid">
27
+ <div class="nav-collapse">
28
+ <ul class="nav">
29
+ <% active rescue active = nil %>
30
+ <li class="main <%= "active" if active == :index %>"><a href="/">JuiCI</a></li>
31
+ <li class="<%= "active" if active == :new_build %>"><a href="/builds/new">New Build</a></li>
32
+ <li class="<%= "active" if active == :about %>"><a href="/about">About</a></li>
33
+ <li class="<%= "active" if active == :support %>"><a href="/support">Support</a></li>
34
+ <li class="<%= "active" if active == :queue %>"><a href="/queue">Build Queue</a></li>
35
+ </ul>
36
+ </div><!--/.nav-collapse -->
37
+ </div>
38
+ </div>
39
+ </div>
40
+ <div class="container-fluid">
41
+ <%= yield %>
42
+ </div>
43
+ </body>
44
+ </html>
@@ -0,0 +1,3 @@
1
+ <h3>Page not found!</h3>
2
+
3
+ <pre>[Insert witty 404 joke]</pre>
@@ -0,0 +1,22 @@
1
+ <div class="debug-output">
2
+ <% if build[:priority] %>
3
+ <h3 class="block-header">priority:</h3>
4
+ <%= build[:priority] %>
5
+ <% end %>
6
+ <% if build[:command] %>
7
+ <h3 class="block-header">command:</h3>
8
+ <%= build[:command] %>
9
+ <% end %>
10
+ <% if build[:environment] %>
11
+ <h3 class="block-header">environment:</h3>
12
+ <% build[:environment].reject{|k, v| v.nil?}.each do |k, v| %>
13
+ <p><strong><%="#{k} = " %></strong><%="#{v}" %></p>
14
+ <% end %>
15
+ <% end %>
16
+ <% if build[:callbacks] %>
17
+ <h3 class="block-header">callbacks:</h3>
18
+ <% build[:callbacks].each do |callback| %>
19
+ <p><%= callback %><p>
20
+ <% end %>
21
+ <% end %>
22
+ </div>
@@ -0,0 +1 @@
1
+ <div class="build-output"><%= ansi_escaped(escaped(build.output)) if build.respond_to? :output %></div>
@@ -0,0 +1,19 @@
1
+ <div class="row-fluid">
2
+ <div class="accordion-heading span10">
3
+ <a class="set-url accordion-toggle <%= build.heading_color %>" data-toggle="collapse" data-parent="#accordion<%= idx+1 %>" href="#collapse_<%= idx %>" data-url="<%= build_url_for(build) %>">
4
+ <%= display_title rescue build.display_title %>&nbsp;<% build[:warnings].each do |warning| %><span class="label label-important"><%= warning %></span><% end %>
5
+ <% if build[:status] == :started %>
6
+ <div class="bowlG pull-right"><div class="bowl_ringG"><div class="ball_holderG"><div class="ballG"></div></div></div></div>
7
+ <% end %>
8
+ </a>
9
+ </div>
10
+ <a class="btn span2" href="<%= build_url_for(build) %>">Build Link</a>
11
+ </div>
12
+ <div id="collapse_<%= idx %>" class="accordion-body collapse <%= "in" if show rescue nil %>">
13
+ <div class="row-fluid" style="padding-top:20px">
14
+ <div class="span8">
15
+ <%= erb(:"partials/builds/output", :locals => { :build => build }) %>
16
+ </div>
17
+ <div class="span4"><%= erb(:"partials/builds/sidebar", :locals => { :build => build }) %><%= erb(:"partials/builds/debug", :locals => { :build => build }) %></div>
18
+ </div>
19
+ </div>
@@ -0,0 +1,13 @@
1
+ <div class="build-sidebar">
2
+ <span><%= form_at(rebuild_url_for(build), {}, :submit => "Rebuild") %></span>
3
+ <% if build.status == Juici::BuildStatus::START %>
4
+ <span><%= form_at(kill_url_for(build),
5
+ {:project => build[:parent], :id => build[:_id]},
6
+ :submit => "Kill!") %></span>
7
+ <% elsif build.status == Juici::BuildStatus::WAIT %>
8
+ <span><%= form_at(cancel_url_for(build),
9
+ {:project => build[:parent], :id => build[:_id]},
10
+ :submit => "Cancel") %></span>
11
+ <% end %>
12
+ <span><a class="btn" href="<%= edit_url_for(build) %>">Edit</a></span>
13
+ </div>
@@ -0,0 +1,19 @@
1
+ <h1 class="block-header builds-header builds-header--failure">fail!<span class="pull-right table-flipper">(╯°□°)╯︵ ┻━┻</span></h1>
2
+ <ul class="builds">
3
+ <% ::Juici::Build.get_recent(5, :status.in => [::Juici::BuildStatus::FAIL]).each do |build| %>
4
+ <li>
5
+ <a class="recently-built--fail" href="<%= build_url_for(build) %>"><%= build.link_title %></a>
6
+ </li>
7
+ <% end %>
8
+ </ul>
9
+
10
+ <hr>
11
+
12
+ <h1 class="block-header builds-header builds-header--success">pass!<span class="pull-right table-flipper">┬─┬ノ( º _ ºノ)</span></h1>
13
+ <ul class="builds">
14
+ <% ::Juici::Build.get_recent(5, :status.in => [::Juici::BuildStatus::PASS]).each do |build| %>
15
+ <li>
16
+ <a class="recently-built--pass" href="<%= build_url_for(build) %>"><%= build.link_title %></a>
17
+ </li>
18
+ <% end %>
19
+ </ul>
@@ -0,0 +1,6 @@
1
+ <% builds.each_with_index do |build, idx| %>
2
+ <div class="accordion" id="accordion<%= idx %>">
3
+ <%= erb(:"partials/builds/show",
4
+ :locals => { :build => build, :idx => idx, :display_title => "#{build[:parent]} :: #{build.display_title}"}) %>
5
+ </div>
6
+ <% end %>