rhomobile-cijoe 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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