glennr-cijoe 0.4.3

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,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,82 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{glennr-cijoe}
8
+ s.version = "0.4.3"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Chris Wanstrath"]
12
+ s.date = %q{2010-07-22}
13
+ s.default_executable = %q{cijoe}
14
+ s.description = %q{CI Joe is a simple Continuous Integration server.}
15
+ s.email = %q{chris@ozmm.org}
16
+ s.executables = ["cijoe"]
17
+ s.extra_rdoc_files = [
18
+ "LICENSE",
19
+ "README.markdown"
20
+ ]
21
+ s.files = [
22
+ ".gitignore",
23
+ "LICENSE",
24
+ "README.markdown",
25
+ "Rakefile",
26
+ "bin/cijoe",
27
+ "cijoe.gemspec",
28
+ "deps.rip",
29
+ "examples/build-failed",
30
+ "examples/build-worked",
31
+ "examples/cijoe.ru",
32
+ "examples/cijoed",
33
+ "glennr-cijoe.gemspec",
34
+ "lib/cijoe.rb",
35
+ "lib/cijoe/build.rb",
36
+ "lib/cijoe/campfire.rb",
37
+ "lib/cijoe/commit.rb",
38
+ "lib/cijoe/config.rb",
39
+ "lib/cijoe/public/favicon.ico",
40
+ "lib/cijoe/public/octocat.png",
41
+ "lib/cijoe/public/screen.css",
42
+ "lib/cijoe/server.rb",
43
+ "lib/cijoe/version.rb",
44
+ "lib/cijoe/views/template.erb",
45
+ "test/helper.rb",
46
+ "test/test_cijoe.rb",
47
+ "test/test_cijoe_server.rb"
48
+ ]
49
+ s.homepage = %q{http://github.com/defunkt/cijoe}
50
+ s.rdoc_options = ["--charset=UTF-8"]
51
+ s.require_paths = ["lib"]
52
+ s.rubygems_version = %q{1.3.7}
53
+ s.summary = %q{CI Joe is a simple Continuous Integration server.}
54
+ s.test_files = [
55
+ "test/helper.rb",
56
+ "test/test_cijoe.rb",
57
+ "test/test_cijoe_server.rb"
58
+ ]
59
+
60
+ if s.respond_to? :specification_version then
61
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
62
+ s.specification_version = 3
63
+
64
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
65
+ s.add_runtime_dependency(%q<choice>, [">= 0"])
66
+ s.add_runtime_dependency(%q<sinatra>, [">= 0"])
67
+ s.add_runtime_dependency(%q<tinder>, [">= 0"])
68
+ s.add_development_dependency(%q<rack-test>, [">= 0"])
69
+ else
70
+ s.add_dependency(%q<choice>, [">= 0"])
71
+ s.add_dependency(%q<sinatra>, [">= 0"])
72
+ s.add_dependency(%q<tinder>, [">= 0"])
73
+ s.add_dependency(%q<rack-test>, [">= 0"])
74
+ end
75
+ else
76
+ s.add_dependency(%q<choice>, [">= 0"])
77
+ s.add_dependency(%q<sinatra>, [">= 0"])
78
+ s.add_dependency(%q<tinder>, [">= 0"])
79
+ s.add_dependency(%q<rack-test>, [">= 0"])
80
+ end
81
+ end
82
+
@@ -0,0 +1,233 @@
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/server'
22
+
23
+ class CIJoe
24
+ attr_reader :user, :project, :url, :current_build, :last_build
25
+
26
+ def initialize(project_path)
27
+ @project_path = File.expand_path(project_path)
28
+
29
+ @user, @project = git_user_and_project
30
+ @url = "http://github.com/#{@user}/#{@project}"
31
+
32
+ @last_build = nil
33
+ @current_build = nil
34
+
35
+ trap("INT") { stop }
36
+ end
37
+
38
+ # is a build running?
39
+ def building?
40
+ !!@current_build
41
+ end
42
+
43
+ # the pid of the running child process
44
+ def pid
45
+ building? and current_build.pid
46
+ end
47
+
48
+ # kill the child and exit
49
+ def stop
50
+ # another build waits
51
+ if repo_config.buildallfile && File.exist?(repo_config.buildallfile.to_s)
52
+ # clean out on stop
53
+ FileUtils.rm(repo_config.buildallfile.to_s)
54
+ end
55
+
56
+ Process.kill(9, pid) if pid
57
+ exit!
58
+ end
59
+
60
+ # build callbacks
61
+ def build_failed(output, error)
62
+ finish_build :failed, "#{error}\n\n#{output}"
63
+ run_hook "build-failed"
64
+ end
65
+
66
+ def build_worked(output)
67
+ finish_build :worked, output
68
+ run_hook "build-worked"
69
+ end
70
+
71
+ def finish_build(status, output)
72
+ @current_build.finished_at = Time.now
73
+ @current_build.status = status
74
+ @current_build.output = output
75
+ @last_build = @current_build
76
+
77
+ @current_build = nil
78
+ write_build 'current', @current_build
79
+ write_build 'last', @last_build
80
+ @last_build.notify if @last_build.respond_to? :notify
81
+
82
+ # another build waits
83
+ if repo_config.buildallfile && File.exist?(repo_config.buildallfile.to_s)
84
+ # clean out before new build
85
+ FileUtils.rm(repo_config.buildallfile.to_s)
86
+ build
87
+ end
88
+ end
89
+
90
+ # run the build but make sure only one is running
91
+ # at a time (if new one comes in we will park it)
92
+ def build
93
+ if building?
94
+ # only if switched on to build all incoming requests
95
+ if repo_config.buildallfile
96
+ # and there is no previous request
97
+ return if File.exist?(repo_config.buildallfile.to_s)
98
+ # we will mark awaiting builds
99
+ FileUtils.touch(repo_config.buildallfile.to_s)
100
+ end
101
+ # leave anyway because a current build runs
102
+ return
103
+ end
104
+ @current_build = Build.new(@project_path, @user, @project)
105
+ write_build 'current', @current_build
106
+ Thread.new { build! }
107
+ end
108
+
109
+ def open_pipe(cmd)
110
+ read, write = IO.pipe
111
+
112
+ pid = fork do
113
+ read.close
114
+ $stdout.reopen write
115
+ exec cmd
116
+ end
117
+
118
+ write.close
119
+
120
+ yield read, pid
121
+ end
122
+
123
+ # update git then run the build
124
+ def build!
125
+ build = @current_build
126
+ output = ''
127
+ git_update
128
+ build.sha = git_sha
129
+ write_build 'current', build
130
+
131
+ open_pipe("cd #{@project_path} && #{runner_command} 2>&1") do |pipe, pid|
132
+ puts "#{Time.now.to_i}: Building #{build.short_sha}: pid=#{pid}"
133
+
134
+ build.pid = pid
135
+ write_build 'current', build
136
+ output = pipe.read
137
+ end
138
+
139
+ Process.waitpid(build.pid)
140
+ status = $?.exitstatus.to_i
141
+ puts "#{Time.now.to_i}: Built #{build.short_sha}: status=#{status}"
142
+
143
+ status == 0 ? build_worked(output) : build_failed('', output)
144
+ rescue Object => e
145
+ puts "Exception building: #{e.message} (#{e.class})"
146
+ build_failed('', e.to_s)
147
+ end
148
+
149
+ # shellin' out
150
+ def runner_command
151
+ runner = repo_config.runner.to_s
152
+ runner == '' ? "rake -s test:units" : runner
153
+ end
154
+
155
+ def git_sha
156
+ `cd #{@project_path} && git rev-parse origin/#{git_branch}`.chomp
157
+ end
158
+
159
+ def git_update
160
+ `cd #{@project_path} && git fetch origin && git reset --hard origin/#{git_branch}`
161
+ run_hook "after-reset"
162
+ end
163
+
164
+ def git_user_and_project
165
+ Config.remote(@project_path).origin.url.to_s.chomp('.git').split(':')[-1].split('/')[-2, 2]
166
+ end
167
+
168
+ def git_branch
169
+ branch = repo_config.branch.to_s
170
+ branch == '' ? "master" : branch
171
+ end
172
+
173
+ # massage our repo
174
+ def run_hook(hook)
175
+ if File.exists?(file=path_in_project(".git/hooks/#{hook}")) && File.executable?(file)
176
+ data =
177
+ if @last_build && @last_build.commit
178
+ {
179
+ "MESSAGE" => @last_build.commit.message,
180
+ "AUTHOR" => @last_build.commit.author,
181
+ "SHA" => @last_build.commit.sha,
182
+ "OUTPUT" => @last_build.clean_output
183
+ }
184
+ else
185
+ {}
186
+ end
187
+
188
+ data.each{ |k, v| ENV[k] = v }
189
+ `cd #{@project_path} && sh #{file}`
190
+ end
191
+ end
192
+
193
+ # restore current / last build state from disk.
194
+ def restore
195
+ unless @last_build
196
+ @last_build = read_build('last')
197
+ end
198
+
199
+ unless @current_build
200
+ @current_build = read_build('current')
201
+ end
202
+
203
+ Process.kill(0, @current_build.pid) if @current_build && @current_build.pid
204
+ rescue Errno::ESRCH
205
+ # build pid isn't running anymore. assume previous
206
+ # server died and reset.
207
+ @current_build = nil
208
+ end
209
+
210
+ def path_in_project(path)
211
+ File.join(@project_path, path)
212
+ end
213
+
214
+ # write build info for build to file.
215
+ def write_build(name, build)
216
+ filename = path_in_project(".git/builds/#{name}")
217
+ Dir.mkdir path_in_project('.git/builds') unless File.directory?(path_in_project('.git/builds'))
218
+ if build
219
+ build.dump filename
220
+ elsif File.exist?(filename)
221
+ File.unlink filename
222
+ end
223
+ end
224
+
225
+ def repo_config
226
+ Config.cijoe(@project_path)
227
+ end
228
+
229
+ # load build info from file.
230
+ def read_build(name)
231
+ Build.load(path_in_project(".git/builds/#{name}"), @project_path)
232
+ end
233
+ end
@@ -0,0 +1,62 @@
1
+ require 'yaml'
2
+
3
+ class CIJoe
4
+ class Build < Struct.new(:project_path, :user, :project, :started_at, :finished_at, :sha, :status, :output, :pid)
5
+ def initialize(*args)
6
+ super
7
+ self.started_at ||= Time.now
8
+ end
9
+
10
+ def status
11
+ return super if started_at && finished_at
12
+ :building
13
+ end
14
+
15
+ def failed?
16
+ status == :failed
17
+ end
18
+
19
+ def worked?
20
+ status == :worked
21
+ end
22
+
23
+ def building?
24
+ status == :building
25
+ end
26
+
27
+ def duration
28
+ return if building?
29
+ finished_at - started_at
30
+ end
31
+
32
+ def short_sha
33
+ if sha
34
+ sha[0,7]
35
+ else
36
+ "<unknown>"
37
+ end
38
+ end
39
+
40
+ def clean_output
41
+ output.gsub(/\e\[.+?m/, '').strip
42
+ end
43
+
44
+ def commit
45
+ return if sha.nil?
46
+ @commit ||= Commit.new(sha, user, project, project_path)
47
+ end
48
+
49
+ def dump(file)
50
+ config = [user, project, started_at, finished_at, sha, status, output, pid]
51
+ data = YAML.dump(config)
52
+ File.open(file, 'wb') { |io| io.write(data) }
53
+ end
54
+
55
+ def self.load(file, project_path)
56
+ if File.exist?(file)
57
+ config = YAML.load(File.read(file)).unshift(project_path)
58
+ new *config
59
+ end
60
+ end
61
+ end
62
+ end