xlymian-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.
@@ -0,0 +1,6 @@
1
+ .DS_Store
2
+ rdoc
3
+ pkg
4
+ *.tmproj
5
+ tmp/**/*
6
+ tmp/*
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.
@@ -0,0 +1,159 @@
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
+
65
+ Campfire
66
+ --------
67
+
68
+ Campfire notification is included, because it's what we use. Want Joe
69
+ notify your Campfire? Put this in your repo's `.git/config`:
70
+
71
+ [campfire]
72
+ user = your@campfire.email
73
+ pass = passw0rd
74
+ subdomain = whatever
75
+ room = Awesomeness
76
+ ssl = false
77
+
78
+ Or do it the old fashion way:
79
+
80
+ $ cd yourrepo
81
+ $ git config --add campfire.user chris@ozmm.org
82
+ $ git config --add campfire.subdomain github
83
+ etc.
84
+
85
+
86
+ Checkin' Status
87
+ ---------------
88
+
89
+ Want to see how your build's doing without any of this fancy UI crap?
90
+ Ping Joe for the lowdown:
91
+
92
+ curl http://localhost:4567/ping
93
+
94
+ Joe will return `200 OK` if all is quiet on the Western Front. If
95
+ Joe's busy building or your last build failed, you'll get `412
96
+ PRECONDITION FAILED`.
97
+
98
+ Multiple Projects
99
+ -----------------
100
+
101
+ Want CI for multiple projects? Just start multiple instances of Joe!
102
+ He can run on any port - try `cijoe -h` for more options.
103
+
104
+
105
+ HTTP Auth
106
+ ---------
107
+
108
+ Worried about people triggering your builds? Setup HTTP auth:
109
+
110
+ $ git config --add cijoe.user chris
111
+ $ git config --add cijoe.pass secret
112
+
113
+
114
+ GitHub Integration
115
+ ------------------
116
+
117
+ Any POST to Joe will trigger a build. If you are hiding Joe behind
118
+ HTTP auth, that's okay - GitHub knows how to authenticate properly.
119
+
120
+ ![Post-Receive URL](http://img.skitch.com/20090806-d2bxrk733gqu8m11tf4kyir5d8.png)
121
+
122
+ You can find the Post-Receive option under the 'Service Hooks' subtab
123
+ of your project's "Admin" tab.
124
+
125
+
126
+ Daemonize
127
+ ---------
128
+
129
+ Want to run Joe as a daemon? Use `nohup`:
130
+
131
+ $ nohup cijoe -p 4444 repo &
132
+
133
+
134
+ Other CI Servers
135
+ ----------------
136
+
137
+ Need more features? More notifiers? Check out one of these bad boys:
138
+
139
+ * [Cerberus](http://cerberus.rubyforge.org/)
140
+ * [Integrity](http://integrityapp.com/)
141
+ * [CruiseControl.rb](http://cruisecontrolrb.thoughtworks.com/)
142
+ * [BuildBot](http://buildbot.net/trac)
143
+
144
+
145
+ Screenshots
146
+ -----------
147
+
148
+ ![Building](http://img.skitch.com/20090806-ryw34ksi5ixnrdwxcptqy28iy7.png)
149
+
150
+ ![Built](http://img.skitch.com/20090806-f7j3r65yecaq13hdcxqwtc5krd.)
151
+
152
+
153
+ Questions? Concerns?
154
+ --------------------
155
+
156
+ [Issues](http://github.com/defunkt/cijoe/issues) or [the mailing list](http://groups.google.com/group/cijoe).
157
+
158
+
159
+ ( Chris Wanstrath :: chris@ozmm.org )
@@ -0,0 +1,60 @@
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 = "xlymian-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 = "chris@ozmm.org"
15
+ gemspec.homepage = "http://github.com/defunkt/cijoe"
16
+ gemspec.authors = ["Chris Wanstrath"]
17
+ gemspec.add_dependency 'choice'
18
+ gemspec.add_dependency 'sinatra'
19
+ gemspec.add_development_dependency 'rack-test'
20
+ gemspec.version = CIJoe::Version.to_s
21
+ end
22
+ Jeweler::GemcutterTasks.new
23
+ rescue LoadError
24
+ puts "Jeweler not available."
25
+ puts "Install it with: gem install jeweler"
26
+ end
27
+
28
+ require 'rake/testtask'
29
+ Rake::TestTask.new(:test) do |test|
30
+ test.libs << 'lib' << 'test'
31
+ test.pattern = 'test/**/test_*.rb'
32
+ test.verbose = true
33
+ end
34
+
35
+ begin
36
+ require 'rcov/rcovtask'
37
+ Rcov::RcovTask.new do |test|
38
+ test.libs << 'test'
39
+ test.pattern = 'test/**/test_*.rb'
40
+ test.verbose = true
41
+ end
42
+ rescue LoadError
43
+ task :rcov do
44
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
45
+ end
46
+ end
47
+
48
+ task :test => :check_dependencies
49
+
50
+ task :default => :test
51
+
52
+ require 'rake/rdoctask'
53
+ Rake::RDocTask.new do |rdoc|
54
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
55
+
56
+ rdoc.rdoc_dir = 'rdoc'
57
+ rdoc.title = "someproject #{version}"
58
+ rdoc.rdoc_files.include('README*')
59
+ rdoc.rdoc_files.include('lib/**/*.rb')
60
+ end
@@ -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])
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,203 @@
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
+ require 'cijoe/version'
17
+ require 'cijoe/config'
18
+ require 'cijoe/commit'
19
+ require 'cijoe/build'
20
+ require 'cijoe/campfire'
21
+ require 'cijoe/talker'
22
+ require 'cijoe/server'
23
+
24
+ class CIJoe
25
+ attr_reader :user, :project, :url, :current_build, :last_build
26
+
27
+ def initialize(project_path)
28
+ project_path = File.expand_path(project_path)
29
+ Dir.chdir(project_path)
30
+
31
+ @user, @project = git_user_and_project
32
+ @url = "http://github.com/#{@user}/#{@project}"
33
+
34
+ @last_build = nil
35
+ @current_build = nil
36
+
37
+ trap("INT") { stop }
38
+ end
39
+
40
+ # is a build running?
41
+ def building?
42
+ !!@current_build
43
+ end
44
+
45
+ # the pid of the running child process
46
+ def pid
47
+ building? and current_build.pid
48
+ end
49
+
50
+ # kill the child and exit
51
+ def stop
52
+ Process.kill(9, pid) if pid
53
+ exit!
54
+ end
55
+
56
+ # build callbacks
57
+ def build_failed(output, error)
58
+ finish_build :failed, "#{error}\n\n#{output}"
59
+ run_hook "build-failed"
60
+ end
61
+
62
+ def build_worked(output)
63
+ finish_build :worked, output
64
+ run_hook "build-worked"
65
+ end
66
+
67
+ def finish_build(status, output)
68
+ @current_build.finished_at = Time.now
69
+ @current_build.status = status
70
+ @current_build.output = output
71
+ @last_build = @current_build
72
+
73
+ @current_build = nil
74
+ write_build 'current', @current_build
75
+ write_build 'last', @last_build
76
+ @last_build.notify if @last_build.respond_to? :notify
77
+ end
78
+
79
+ # run the build but make sure only
80
+ # one is running at a time
81
+ def build
82
+ return if building?
83
+ @current_build = Build.new(@user, @project)
84
+ write_build 'current', @current_build
85
+ Thread.new { build! }
86
+ end
87
+
88
+ def open_pipe(cmd)
89
+ read, write = IO.pipe
90
+
91
+ pid = fork do
92
+ read.close
93
+ $stdout.reopen write
94
+ exec cmd
95
+ end
96
+
97
+ write.close
98
+
99
+ yield read, pid
100
+ end
101
+
102
+ # update git then run the build
103
+ def build!
104
+ build = @current_build
105
+ output = ''
106
+ git_update
107
+ build.sha = git_sha
108
+ write_build 'current', build
109
+
110
+ open_pipe("#{runner_command} 2>&1") do |pipe, pid|
111
+ puts "#{Time.now.to_i}: Building #{build.short_sha}: pid=#{pid}"
112
+
113
+ build.pid = pid
114
+ write_build 'current', build
115
+ output = pipe.read
116
+ end
117
+
118
+ Process.waitpid(build.pid)
119
+ status = $?.exitstatus.to_i
120
+ puts "#{Time.now.to_i}: Built #{build.short_sha}: status=#{status}"
121
+
122
+ status == 0 ? build_worked(output) : build_failed('', output)
123
+ rescue Object => e
124
+ puts "Exception building: #{e.message} (#{e.class})"
125
+ build_failed('', e.to_s)
126
+ end
127
+
128
+ # shellin' out
129
+ def runner_command
130
+ runner = Config.cijoe.runner.to_s
131
+ runner == '' ? "rake -s test:units" : runner
132
+ end
133
+
134
+ def git_sha
135
+ `git rev-parse origin/#{git_branch}`.chomp
136
+ end
137
+
138
+ def git_update
139
+ `git fetch origin && git reset --hard origin/#{git_branch}`
140
+ run_hook "after-reset"
141
+ end
142
+
143
+ def git_user_and_project
144
+ Config.remote.origin.url.to_s.chomp('.git').split(':')[-1].split('/')[-2, 2]
145
+ end
146
+
147
+ def git_branch
148
+ branch = Config.cijoe.branch.to_s
149
+ branch == '' ? "master" : branch
150
+ end
151
+
152
+ # massage our repo
153
+ def run_hook(hook)
154
+ if File.exists?(file=".git/hooks/#{hook}") && File.executable?(file)
155
+ data =
156
+ if @last_build && @last_build.commit
157
+ {
158
+ "MESSAGE" => @last_build.commit.message,
159
+ "AUTHOR" => @last_build.commit.author,
160
+ "SHA" => @last_build.commit.sha,
161
+ "OUTPUT" => @last_build.clean_output
162
+ }
163
+ else
164
+ {}
165
+ end
166
+ env = data.collect { |k, v| %(#{k}=#{v.inspect}) }.join(" ")
167
+ `#{env} sh #{file}`
168
+ end
169
+ end
170
+
171
+ # restore current / last build state from disk.
172
+ def restore
173
+ unless @last_build
174
+ @last_build = read_build('last')
175
+ end
176
+
177
+ unless @current_build
178
+ @current_build = read_build('current')
179
+ end
180
+
181
+ Process.kill(0, @current_build.pid) if @current_build && @current_build.pid
182
+ rescue Errno::ESRCH
183
+ # build pid isn't running anymore. assume previous
184
+ # server died and reset.
185
+ @current_build = nil
186
+ end
187
+
188
+ # write build info for build to file.
189
+ def write_build(name, build)
190
+ filename = ".git/builds/#{name}"
191
+ Dir.mkdir '.git/builds' unless File.directory?('.git/builds')
192
+ if build
193
+ build.dump filename
194
+ elsif File.exist?(filename)
195
+ File.unlink filename
196
+ end
197
+ end
198
+
199
+ # load build info from file.
200
+ def read_build(name)
201
+ Build.load(".git/builds/#{name}")
202
+ end
203
+ end