glennr-cijoe 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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