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,2 @@
1
+ *.gem
2
+ config/mongoid.yml
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source :rubygems
2
+
3
+ ruby '1.9.3'
4
+
5
+ gemspec :name => "juici"
@@ -0,0 +1,74 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ juici (0.0.0)
5
+ ansible
6
+ bson_ext
7
+ github-markdown
8
+ json
9
+ mongoid
10
+ sinatra
11
+ thin
12
+
13
+ GEM
14
+ remote: http://rubygems.org/
15
+ specs:
16
+ activemodel (3.2.8)
17
+ activesupport (= 3.2.8)
18
+ builder (~> 3.0.0)
19
+ activesupport (3.2.8)
20
+ i18n (~> 0.6)
21
+ multi_json (~> 1.0)
22
+ ansible (0.2.0)
23
+ bson (1.7.0)
24
+ bson_ext (1.7.0)
25
+ bson (~> 1.7.0)
26
+ builder (3.0.4)
27
+ daemons (1.1.9)
28
+ diff-lcs (1.1.3)
29
+ eventmachine (1.0.0)
30
+ github-markdown (0.5.3)
31
+ i18n (0.6.1)
32
+ json (1.7.5)
33
+ metaclass (0.0.1)
34
+ mocha (0.11.4)
35
+ metaclass (~> 0.0.1)
36
+ mongoid (3.0.10)
37
+ activemodel (~> 3.1)
38
+ moped (~> 1.1)
39
+ origin (~> 1.0)
40
+ tzinfo (~> 0.3.22)
41
+ moped (1.2.7)
42
+ multi_json (1.3.7)
43
+ origin (1.0.10)
44
+ rack (1.4.1)
45
+ rack-protection (1.2.0)
46
+ rack
47
+ rake (0.9.2.2)
48
+ rspec (2.10.0)
49
+ rspec-core (~> 2.10.0)
50
+ rspec-expectations (~> 2.10.0)
51
+ rspec-mocks (~> 2.10.0)
52
+ rspec-core (2.10.1)
53
+ rspec-expectations (2.10.0)
54
+ diff-lcs (~> 1.1.3)
55
+ rspec-mocks (2.10.1)
56
+ sinatra (1.3.3)
57
+ rack (~> 1.3, >= 1.3.6)
58
+ rack-protection (~> 1.2)
59
+ tilt (~> 1.3, >= 1.3.3)
60
+ thin (1.5.0)
61
+ daemons (>= 1.0.9)
62
+ eventmachine (>= 0.12.6)
63
+ rack (>= 1.0.0)
64
+ tilt (1.3.3)
65
+ tzinfo (0.3.34)
66
+
67
+ PLATFORMS
68
+ ruby
69
+
70
+ DEPENDENCIES
71
+ juici!
72
+ mocha
73
+ rake
74
+ rspec
@@ -0,0 +1 @@
1
+ web: RACK_ENV=heroku bundle exec ruby bin/juici
@@ -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,33 @@
1
+ require 'rake'
2
+ require 'rspec/core/rake_task'
3
+
4
+ require 'juici/database'
5
+
6
+ RSpec::Core::RakeTask.new(:spec) do |t|
7
+ t.pattern = "spec/**/*_spec.rb"
8
+ end
9
+
10
+ desc 'Default: run specs'
11
+ task :default => :spec
12
+
13
+ namespace :db do
14
+ desc "Destroy the test db specified in mongoid.yml"
15
+ task :destroy do
16
+ Juici::Database.initialize!
17
+ Mongoid.purge!
18
+ end
19
+ end
20
+
21
+ desc "Build all gems"
22
+ task :gems do
23
+ %w[juici juici-interface].each do |gem|
24
+ `gem build #{gem}.gemspec`
25
+ end
26
+ end
27
+
28
+ desc "Delete all built gems"
29
+ task :clean do
30
+ Dir["juici-*.gem"].each do |gem|
31
+ File.unlink(gem)
32
+ end
33
+ end
data/TODO.md ADDED
@@ -0,0 +1,28 @@
1
+ * This awkward `::Juici::Model` kludge inside views is a pain.
2
+
3
+ * RSS feed for build status (spyware)
4
+
5
+ * Paginated builds to avoid blocking up a connection
6
+
7
+ * Proper callback support
8
+
9
+ * Graphing of build times and statuses
10
+
11
+ * Search
12
+
13
+ ## Only child model
14
+
15
+ `lib/juici/watcher +21`
16
+
17
+ Basically, it would be nice to have a watcher get started with the first build
18
+ out of the ranks, and then die on ECHILD
19
+
20
+ This means that with lots of builds passing through we can retain a single
21
+ watcher, and when we're idle we let him die and then spawn a new one when we
22
+ need
23
+
24
+ * Neat method of either resuming or starting a build
25
+
26
+ * RSS api
27
+
28
+ * Import of Xdefaults/itermcolors.plist for output colors
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.expand_path("../../lib", __FILE__))
3
+ require 'juici'
4
+
5
+ port = ENV['PORT'] || 9000
6
+
7
+ Juici::Server.start('localhost', port)
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env ruby
2
+ require 'net/http'
3
+ require 'json'
4
+ require 'juici/interface'
5
+
6
+ # TODO Refactor
7
+ def main(args)
8
+ action = args.shift.to_sym
9
+ options = {}
10
+
11
+ until args.empty?
12
+ case args.shift
13
+ when "--command"
14
+ command = args.shift
15
+ options[:command] = if command == "-"
16
+ File.read(command)
17
+ else
18
+ command
19
+ end
20
+ when "--host"
21
+ options[:host] = args.shift
22
+ when "--title"
23
+ options[:title] = args.shift
24
+ when "--project"
25
+ options[:project] = args.shift
26
+ when "--priority"
27
+ options[:priority] = args.shift
28
+ end
29
+ end
30
+
31
+ send(action, options)
32
+ end
33
+
34
+ def build(opts)
35
+ host = URI(opts[:host])
36
+ Net::HTTP.start(host.host, host.port) do |h|
37
+ req = Net::HTTP::Post.new(Juici::Routes::NEW_BUILD)
38
+ req.body = _create_payload(opts)
39
+ h.request req
40
+ end
41
+ end
42
+
43
+ def _create_payload(opts)
44
+ URI.encode_www_form({
45
+ "project" => opts[:project],
46
+ "environment" => (opts[:environment] || {}).to_json,
47
+ "command" => opts[:command],
48
+ "priority" => opts[:priority] || 1,
49
+ "callbacks" => (opts[:callbacks] || []).to_json,
50
+ "title" => opts[:title]
51
+ })
52
+ end
53
+
54
+ main(ARGV.dup)
@@ -0,0 +1,25 @@
1
+ development:
2
+ sessions:
3
+ default:
4
+ database: juici
5
+ hosts:
6
+ - localhost:27017
7
+
8
+ production:
9
+ sessions:
10
+ default:
11
+ database: juici
12
+ hosts:
13
+ - localhost:27017
14
+
15
+ test:
16
+ sessions:
17
+ default:
18
+ database: juici-test
19
+ hosts:
20
+ - localhost:27017
21
+
22
+ heroku:
23
+ sessions:
24
+ default:
25
+ uri: <%= ENV['MONGOLAB_URI'] %>
@@ -0,0 +1,19 @@
1
+ # vim: ft=ruby
2
+ #
3
+ require File.expand_path("../lib/juici/version", __FILE__)
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "juici-interface"
7
+ s.version = Juici::VERSION
8
+ s.authors = ["Richo Healey"]
9
+ s.email = ["richo@psych0tik.net"]
10
+ s.homepage = "http://github.com/richo/juici"
11
+ s.summary = "Interface definition for JuiCI callbacks and API"
12
+ s.description = s.summary
13
+
14
+ s.files = "lib/juici/interface.rb"
15
+ s.require_paths = ["lib"]
16
+ s.executables = "juicic"
17
+ end
18
+
19
+
@@ -0,0 +1,32 @@
1
+ # vim: ft=ruby
2
+ #
3
+ require File.expand_path("../lib/juici/version", __FILE__)
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "juici"
7
+ s.version = Juici::VERSION
8
+ s.authors = ["Richo Healey"]
9
+ s.email = ["richo@psych0tik.net"]
10
+ s.homepage = "http://github.com/richo/juici"
11
+ s.summary = "Minimal CI server with some support for dynamic"
12
+ s.description = s.summary
13
+
14
+ s.add_dependency "sinatra"
15
+ s.add_dependency "thin"
16
+ s.add_dependency "json"
17
+ s.add_dependency "mongoid"
18
+ s.add_dependency "bson_ext"
19
+ s.add_dependency "github-markdown"
20
+ s.add_dependency "ansible"
21
+
22
+ s.add_development_dependency "rake"
23
+ s.add_development_dependency "mocha"
24
+ s.add_development_dependency "rspec"
25
+
26
+ s.files = `git ls-files`.split("\n")
27
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
28
+ s.executables = 'juici'
29
+ s.require_paths = ["lib"]
30
+ end
31
+
32
+
@@ -0,0 +1,24 @@
1
+ require 'github/markdown'
2
+ require 'ansible'
3
+
4
+ ENV['RACK_ENV'] ||= "development"
5
+
6
+ module Juici
7
+ def self.dbgp(*args)
8
+ if ENV['JUICI_DEBUG'] || env == "development"
9
+ $stderr.puts(args)
10
+ end
11
+ end
12
+
13
+ def self.env
14
+ ENV['JUICI_ENV'] || ENV['RACK_ENV']
15
+ end
16
+ end
17
+
18
+ # Load juici core, followed by extras
19
+ ["", "controllers", "models"].each do |el|
20
+ Dir[File.dirname(__FILE__) + "/juici/#{el}/*.rb"].each do |file|
21
+ Juici.dbgp "Loading #{file}"
22
+ require file
23
+ end
24
+ end