rhomobile-cijoe 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ .DS_Store
2
+ rdoc
3
+ pkg
4
+ *.tmproj
5
+ tmp/**/*
6
+ tmp/*
7
+ nbproject
8
+ nbproject/**/*
9
+ *.gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Chris Wanstrath
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,153 @@
1
+ CI Joe
2
+ ======
3
+
4
+ Joe is a [Continuous
5
+ Integration](http://en.wikipedia.org/wiki/Continuous_integration)
6
+ server that'll run your tests on demand and report their pass/fail status.
7
+
8
+ Because knowing is half the battle.
9
+
10
+ ![The Battle](http://img.skitch.com/20090805-g4a2qhttwij8n2jr9t552efn3k.png)
11
+
12
+ Quickstart
13
+ ----------
14
+
15
+ Rip:
16
+
17
+ $ rip install git://github.com/defunkt/cijoe.git
18
+ $ git clone git://github.com/you/yourrepo.git
19
+ $ cijoe yourrepo
20
+
21
+ Gemcutter:
22
+
23
+ $ gem install cijoe
24
+ $ git clone git://github.com/you/yourrepo.git
25
+ $ cijoe yourrepo
26
+
27
+ Boom. Navigate to http://localhost:4567 to see Joe in action.
28
+ Check `cijoe -h` for other options.
29
+
30
+ Basically you need to run `cijoe` and hand it the path to a git
31
+ repo. Make sure this isn't a shared repo: Joe needs to own it.
32
+
33
+ Joe looks for various git config settings in the repo you hand it. For
34
+ instance, you can tell Joe what command to run by setting
35
+ `cijoe.runner`:
36
+
37
+ $ git config --add cijoe.runner "rake -s test:units"
38
+
39
+ Joe doesn't care about Ruby, Python, or whatever. As long as the
40
+ runner returns a non-zero exit status on fail and a zero on success,
41
+ everyone is happy.
42
+
43
+ Need to do some massaging of your repo before the tests run, like
44
+ maybe swapping in a new database.yml? No problem - Joe will try to
45
+ run `.git/hooks/after-reset` if it exists before each build phase.
46
+ Do it in there. Just make sure it's executable.
47
+
48
+ Want to notify IRC or email on test pass or failure? Joe will run
49
+ `.git/hooks/build-failed` or `.git/hooks/build-worked` if they exist
50
+ and are executable on build pass / fail. They're just shell scripts -
51
+ put whatever you want in there.
52
+
53
+ Tip: your repo's `HEAD` will point to the commit used to run the
54
+ build. Pull any metadata you want out of that scro.
55
+
56
+
57
+ Other Branches
58
+ --------------
59
+
60
+ Want joe to run against a branch other than `master`? No problem:
61
+
62
+ $ git config --add cijoe.branch deploy
63
+
64
+ Build History
65
+ -------------
66
+
67
+ Want joe to save more or less builds?
68
+
69
+ $ git confing --add cijoe.buildhistory 5
70
+
71
+ Campfire
72
+ --------
73
+
74
+ Campfire notification is included, because it's what we use. Want Joe
75
+ notify your Campfire? Put this in your repo's `.git/config`:
76
+
77
+ [campfire]
78
+ user = your@campfire.email
79
+ pass = passw0rd
80
+ subdomain = whatever
81
+ room = Awesomeness
82
+ ssl = false
83
+
84
+ Or do it the old fashion way:
85
+
86
+ $ cd yourrepo
87
+ $ git config --add campfire.user chris@ozmm.org
88
+ $ git config --add campfire.subdomain github
89
+ etc.
90
+
91
+
92
+ Multiple Projects
93
+ -----------------
94
+
95
+ Want CI for multiple projects? Just start multiple instances of Joe!
96
+ He can run on any port - try `cijoe -h` for more options.
97
+
98
+
99
+ HTTP Auth
100
+ ---------
101
+
102
+ Worried about people triggering your builds? Setup HTTP auth:
103
+
104
+ $ git config --add cijoe.user chris
105
+ $ git config --add cijoe.pass secret
106
+
107
+
108
+ GitHub Integration
109
+ ------------------
110
+
111
+ Any POST to Joe will trigger a build. If you are hiding Joe behind
112
+ HTTP auth, that's okay - GitHub knows how to authenticate properly.
113
+
114
+ ![Post-Receive URL](http://img.skitch.com/20090806-d2bxrk733gqu8m11tf4kyir5d8.png)
115
+
116
+ You can find the Post-Receive option under the 'Service Hooks' subtab
117
+ of your project's "Admin" tab.
118
+
119
+
120
+ Daemonize
121
+ ---------
122
+
123
+ Want to run Joe as a daemon? Use `nohup`:
124
+
125
+ $ nohup cijoe -p 4444 repo &
126
+
127
+
128
+ Other CI Servers
129
+ ----------------
130
+
131
+ Need more features? More notifiers? Check out one of these bad boys:
132
+
133
+ * [Cerberus](http://cerberus.rubyforge.org/)
134
+ * [Integrity](http://integrityapp.com/)
135
+ * [CruiseControl.rb](http://cruisecontrolrb.thoughtworks.com/)
136
+ * [BuildBot](http://buildbot.net/trac)
137
+
138
+
139
+ Screenshots
140
+ -----------
141
+
142
+ ![Building](http://img.skitch.com/20090806-ryw34ksi5ixnrdwxcptqy28iy7.png)
143
+
144
+ ![Built](http://img.skitch.com/20090806-f7j3r65yecaq13hdcxqwtc5krd.)
145
+
146
+
147
+ Questions? Concerns?
148
+ --------------------
149
+
150
+ [Issues](http://github.com/defunkt/cijoe/issues) or [the mailing list](http://groups.google.com/group/cijoe).
151
+
152
+
153
+ ( Chris Wanstrath :: chris@ozmm.org )
data/Rakefile ADDED
@@ -0,0 +1,59 @@
1
+ desc "Build a gem"
2
+ task :gem => [ :gemspec, :build ]
3
+
4
+ begin
5
+ require 'jeweler'
6
+
7
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/lib'
8
+ require 'cijoe/version'
9
+
10
+ Jeweler::Tasks.new do |gemspec|
11
+ gemspec.name = "rhomobile-cijoe"
12
+ gemspec.summary = "CI Joe is a simple Continuous Integration server."
13
+ gemspec.description = "CI Joe is a simple Continuous Integration server."
14
+ gemspec.email = "dev@rhomobile.com"
15
+ gemspec.homepage = "http://github.com/ldm314/cijoe"
16
+ gemspec.authors = ["Chris Wanstrath", "Brian Moore", "Lars Burgess"]
17
+ gemspec.add_dependency 'choice'
18
+ gemspec.add_dependency 'sinatra'
19
+ gemspec.add_dependency 'systemu'
20
+ gemspec.version = CIJoe::Version.to_s
21
+ end
22
+ rescue LoadError
23
+ puts "Jeweler not available."
24
+ puts "Install it with: gem install jeweler"
25
+ end
26
+
27
+ require 'rake/testtask'
28
+ Rake::TestTask.new(:test) do |test|
29
+ test.libs << 'lib' << 'test'
30
+ test.pattern = 'test/**/test_*.rb'
31
+ test.verbose = true
32
+ end
33
+
34
+ begin
35
+ require 'rcov/rcovtask'
36
+ Rcov::RcovTask.new do |test|
37
+ test.libs << 'test'
38
+ test.pattern = 'test/**/test_*.rb'
39
+ test.verbose = true
40
+ end
41
+ rescue LoadError
42
+ task :rcov do
43
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
44
+ end
45
+ end
46
+
47
+ task :test => :check_dependencies
48
+
49
+ task :default => :test
50
+
51
+ require 'rake/rdoctask'
52
+ Rake::RDocTask.new do |rdoc|
53
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
54
+
55
+ rdoc.rdoc_dir = 'rdoc'
56
+ rdoc.title = "someproject #{version}"
57
+ rdoc.rdoc_files.include('README*')
58
+ rdoc.rdoc_files.include('lib/**/*.rb')
59
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
data/bin/cijoe ADDED
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
3
+
4
+ require 'cijoe'
5
+ require 'choice'
6
+
7
+ Choice.options do
8
+ banner "Usage: #{File.basename(__FILE__)} [-hpv] path_to_git_repo"
9
+ header ''
10
+ header 'Server options:'
11
+
12
+ option :host do
13
+ d = "0.0.0.0"
14
+ short '-h'
15
+ long '--host=HOST'
16
+ desc "The hostname or ip of the host to bind to (default #{d})"
17
+ default d
18
+ end
19
+
20
+ option :port do
21
+ d = 4567
22
+ short '-p'
23
+ long '--port=PORT'
24
+ desc "The port to listen on (default #{d})"
25
+ cast Integer
26
+ default d
27
+ end
28
+
29
+ separator ''
30
+ separator 'Common options: '
31
+
32
+ option :help do
33
+ long '--help'
34
+ desc 'Show this message'
35
+ end
36
+
37
+ option :version do
38
+ short '-v'
39
+ long '--version'
40
+ desc 'Show version'
41
+ action do
42
+ puts "#{File.basename(__FILE__)} v#{CIJoe::Version}"
43
+ exit
44
+ end
45
+ end
46
+ end
47
+
48
+ options = Choice.choices
49
+ CIJoe::Server.start(options[:host], options[:port], Choice.rest[0])
data/deps.rip ADDED
@@ -0,0 +1,4 @@
1
+ git://github.com/collectiveidea/tinder.git 1.2.0
2
+ git://github.com/sinatra/sinatra.git 0.9.4
3
+ git://github.com/rack/rack.git 1.0
4
+ git://github.com/defunkt/choice.git 8b125564
data/examples/cijoe.ru ADDED
@@ -0,0 +1,15 @@
1
+ # Example CI Joe rackup config. Drop a cijoe.ru file
2
+ # in your projects direct
3
+ require 'cijoe'
4
+
5
+ # setup middleware
6
+ use Rack::CommonLogger
7
+
8
+ # configure joe
9
+ CIJoe::Server.configure do |config|
10
+ config.set :project_path, File.dirname(__FILE__)
11
+ config.set :show_exceptions, true
12
+ config.set :lock, true
13
+ end
14
+
15
+ run CIJoe::Server
data/examples/cijoed ADDED
@@ -0,0 +1,53 @@
1
+ #!/bin/sh
2
+ ### BEGIN INIT INFO
3
+ # Provides: cijoe
4
+ # Required-Start: $syslog $local_fs $network
5
+ # Required-Stop: $syslog $local_fs $network
6
+ # Default-Start: 2 3 4 5
7
+ # Default-Stop: 0 1
8
+ # Description: Run the CIJoe CI server. Yo Joe!!
9
+ ### END INIT INFO
10
+
11
+ . /lib/lsb/init-functions
12
+
13
+ REPO=/path/to/your/git/repository
14
+ PORT=4567
15
+
16
+ NAME=cijoe
17
+ INSTALL_DIR=/usr/sbin
18
+ DAEMON=$INSTALL_DIR/$NAME
19
+ DAEMON_ARGS="-p $PORT $REPO"
20
+ PIDFILE=/var/run/$NAME.pid
21
+ DAEMON_USER=www-data
22
+ DAEMON_GROUP=$DAEMON_USER
23
+
24
+ # test -f $DAEMON || exit 0
25
+ # test -f $PROJECT_DIR || exit 0
26
+
27
+ case "$1" in
28
+ start)
29
+ log_daemon_msg "Starting cijoe" "cijoe"
30
+ start-stop-daemon --background --make-pidfile --exec $DAEMON --start --name $NAME --pidfile $PIDFILE --chuid $DAEMON_USER:$DAEMON_GROUP -- $DAEMON_ARGS
31
+ log_end_msg $?
32
+ ;;
33
+ stop)
34
+ log_daemon_msg "Stopping cijoe" "cijoe"
35
+ start-stop-daemon --stop --pidfile $PIDFILE --quiet --retry 10
36
+ log_end_msg $?
37
+ ;;
38
+ restart)
39
+ log_daemon_msg "Restarting cijoe" "cijoe"
40
+ start-stop-daemon --stop --pidfile $PIDFILE --quiet --retry 10
41
+ start-stop-daemon --background --make-pidfile --exec $DAEMON --start --name $NAME --pidfile $PIDFILE --chuid $DAEMON_USER:$DAEMON_GROUP -- $DAEMON_ARGS
42
+ log_end_msg $?
43
+ ;;
44
+ status)
45
+ status_of_proc $DAEMON $NAME && exit 0 || exit $?
46
+ ;;
47
+ *)
48
+ log_action_msg "Usage: /etc/init.d/cijoe (start|stop|restart)"
49
+ exit 2
50
+ ;;
51
+ esac
52
+
53
+ exit 0
data/lib/cijoe.rb ADDED
@@ -0,0 +1,264 @@
1
+ ##
2
+ # CI Joe.
3
+ # Because knowing is half the battle.
4
+ #
5
+ # This is a stupid simple CI server. It can build one (1)
6
+ # git-based project only.
7
+ #
8
+ # It only remembers the last build.
9
+ #
10
+ # It only notifies to Campfire.
11
+ #
12
+ # It's a RAH (Real American Hero).
13
+ #
14
+ # Seriously, I'm gonna be nuts about keeping this simple.
15
+
16
+ begin
17
+ require 'systemu'
18
+ rescue LoadError
19
+ abort "** Please install systemu"
20
+ end
21
+
22
+ require 'cijoe/version'
23
+ require 'cijoe/config'
24
+ require 'cijoe/commit'
25
+ require 'cijoe/build'
26
+ require 'cijoe/email'
27
+ require 'cijoe/server'
28
+
29
+ class CIJoe
30
+ attr_reader :user, :project, :url, :current_build, :old_builds
31
+
32
+ def initialize(project_path)
33
+ project_path = File.expand_path(project_path)
34
+ Dir.chdir(project_path)
35
+
36
+ @user, @project = git_user_and_project
37
+ @url = "http://github.com/#{@user}/#{@project}"
38
+
39
+ @old_builds = []
40
+ @current_build = nil
41
+
42
+ trap("INT") { stop }
43
+ end
44
+
45
+ # is a build running?
46
+ def building?
47
+ !!@current_build
48
+ end
49
+
50
+ # the pid of the running child process
51
+ def pid
52
+ building? and current_build.pid
53
+ end
54
+
55
+ # kill the child and exit
56
+ def stop
57
+ Process.kill(9, pid) if pid
58
+ exit!
59
+ end
60
+
61
+ # build callbacks
62
+ def build_failed(output, error)
63
+ finish_build :failed, "#{error}\n\n#{output}"
64
+ run_hook "build-failed"
65
+ end
66
+
67
+ def build_worked(output)
68
+ finish_build :worked, output
69
+ run_hook "build-worked"
70
+ end
71
+
72
+ def finish_build(status, output)
73
+ @current_build.finished_at = Time.now
74
+ @current_build.status = status
75
+ @current_build.output = output
76
+
77
+ @current_build.total = $1 if output =~ /Agg Total: ([0-9]+)/
78
+ @current_build.passes = $1 if output =~ /Agg Passed: ([0-9]+)/
79
+ @current_build.fails = $1 if output =~ /Agg Failed: ([0-9]+)/
80
+
81
+ Dir.glob("**/*.txt") do |f|
82
+ @current_build.faillog = IO.read(f) if f =~ /faillog/
83
+ end
84
+
85
+ @old_builds.insert(0,@current_build)
86
+
87
+ @current_build = nil
88
+ write_build 'current', @current_build
89
+
90
+ @old_builds.each do |build|
91
+
92
+ name = build.finished_at.to_i.to_s
93
+ write_build name,build unless File.exist? ".git/builds/#{name}"
94
+
95
+ end
96
+ clean_builds
97
+
98
+ @old_builds[0].notify if @old_builds[0].respond_to? :notify
99
+ end
100
+
101
+ # run the build but make sure only
102
+ # one is running at a time
103
+ def build
104
+ return if building?
105
+ @current_build = Build.new(@user, @project)
106
+ write_build 'current', @current_build
107
+ Thread.new { build! }
108
+ end
109
+
110
+ def open_pipe(cmd)
111
+ read, write = IO.pipe
112
+
113
+ pid = fork do
114
+ read.close
115
+ STDOUT.reopen write
116
+ exec cmd
117
+ end
118
+
119
+ write.close
120
+
121
+ yield read, pid
122
+ end
123
+
124
+ # update git then run the build
125
+ def build!
126
+ build = @current_build
127
+ output = ''
128
+ git_update
129
+ build.sha = git_sha
130
+ write_build 'current', build
131
+
132
+ status, stdout, stderr = systemu(runner_command) do |pid|
133
+ build.pid = pid
134
+ write_build 'current', build
135
+ end
136
+ err, out = stderr, stdout
137
+ status.exitstatus.to_i == 0 ? build_worked(out) : build_failed(out, err)
138
+ rescue Object => e
139
+ puts "Exception building: #{e.message} (#{e.class})"
140
+ build_failed('', e.to_s)
141
+ end
142
+
143
+ # shellin' out
144
+ def runner_command
145
+ runner = Config.cijoe.runner.to_s
146
+ runner == '' ? "rake -s test:units" : runner
147
+ end
148
+
149
+ def git_sha
150
+ `git rev-parse origin/#{git_branch}`.chomp
151
+ end
152
+
153
+ def git_update
154
+ `git fetch origin && git reset --hard origin/#{git_branch}`
155
+ run_hook "after-reset"
156
+ end
157
+
158
+ def git_user_and_project
159
+ Config.remote.origin.url.to_s.chomp('.git').split(':')[-1].split('/')[-2, 2]
160
+ end
161
+
162
+ def git_branch
163
+ branch = Config.cijoe.branch.to_s
164
+ branch == '' ? "master" : branch
165
+ end
166
+
167
+ # massage our repo
168
+ def run_hook(hook)
169
+ if File.exists?(file=".git/hooks/#{hook}") && File.executable?(file)
170
+ data =
171
+ if @old_builds[0] && @old_builds[0].commit
172
+ {
173
+ "MESSAGE" => @old_builds[0].commit.message,
174
+ "AUTHOR" => @old_builds[0].commit.author,
175
+ "SHA" => @old_builds[0].commit.sha,
176
+ "OUTPUT" => @old_builds[0].clean_output
177
+ }
178
+ else
179
+ {}
180
+ end
181
+ env = data.collect { |k, v| %(#{k}=#{v.inspect}) }.join(" ")
182
+ `#{env} sh #{file}`
183
+ end
184
+ end
185
+
186
+ # restore current / old build state from disk.
187
+ def restore
188
+ unless @old_builds.length > 0
189
+ clean_builds
190
+ builds = []
191
+ Dir.glob(".git/builds/*") do |file|
192
+ file = File.basename(file)
193
+ builds << file unless (file =~/\./ or file.to_i == 0)
194
+ end
195
+ builds = builds.sort.reverse
196
+ builds.each do |file|
197
+ @old_builds << read_build(file)
198
+ end
199
+ end
200
+
201
+ unless @current_build
202
+ @current_build = read_build('current')
203
+ end
204
+
205
+ Process.kill(0, @current_build.pid) if @current_build && @current_build.pid
206
+ rescue Errno::ESRCH
207
+ # build pid isn't running anymore. assume previous
208
+ # server died and reset.
209
+ @current_build = nil
210
+ end
211
+
212
+ # write build info for build to file.
213
+ def write_build(name, build)
214
+ filename = ".git/builds/#{name}"
215
+ Dir.mkdir '.git/builds' unless File.directory?('.git/builds')
216
+ if build
217
+ build.dump filename
218
+ elsif File.exist?(filename)
219
+ File.unlink filename
220
+ end
221
+ end
222
+
223
+ # load build info from file.
224
+ def read_build(name)
225
+ Build.load(".git/builds/#{name}")
226
+ end
227
+
228
+ #removes builds older than what is set in the config
229
+ def clean_builds
230
+ numbuilds = Config.cijoe.buildhistory.to_s.to_i
231
+ numbuilds = numbuilds == 0 ? 10 : numbuilds
232
+ builds = []
233
+
234
+ #old builds saved with thier name as a timestamp
235
+ Dir.glob(".git/builds/*") do |file|
236
+ file = File.basename(file)
237
+ builds << file unless (file =~/\./ or file.to_i == 0)
238
+ end
239
+
240
+ if builds.length > numbuilds
241
+ #sort and reverse, makes the older builds at the end
242
+ builds = builds.sort.reverse
243
+ #remove old builds
244
+ builds[(numbuilds)...(builds.length)].each do |file|
245
+ File.unlink(".git/builds/#{file}") if File.exist? ".git/builds/#{file}"
246
+ end
247
+ end
248
+
249
+ end
250
+
251
+ def log_for_time(time)
252
+ @old_builds.each do |build|
253
+ return build.output if build.finished_at.to_i.to_s == time.to_s
254
+ end
255
+ "Log not available"
256
+ end
257
+
258
+ def failure_for_time(time)
259
+ @old_builds.each do |build|
260
+ return build.faillog if build.finished_at.to_i.to_s == time.to_s and build.faillog.to_s != ""
261
+ end
262
+ "Log not available"
263
+ end
264
+ end