joshuapinter-cijoe 0.9.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.
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.md ADDED
@@ -0,0 +1,177 @@
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)](http://nerduo.com/thebattle/)
11
+
12
+ ([Buy the shirt](http://nerduo.com/thebattle/))
13
+
14
+ Quickstart
15
+ ----------
16
+
17
+ RubyGems:
18
+
19
+ $ gem install cijoe
20
+ $ git clone git://github.com/you/yourrepo.git
21
+ $ cijoe yourrepo
22
+
23
+ Boom. Navigate to <http://localhost:4567> to see Joe in action.
24
+ Check `cijoe -h` for other options.
25
+
26
+ Basically you need to run `cijoe` and hand it the path to a git
27
+ repo. Make sure this isn't a shared repo: Joe needs to own it.
28
+
29
+ Joe looks for various git config settings in the repo you hand it. For
30
+ instance, you can tell Joe what command to run by setting
31
+ `cijoe.runner`:
32
+
33
+ $ git config --add cijoe.runner "rake -s test:units"
34
+
35
+ Joe doesn't care about Ruby, Python, or whatever. As long as the
36
+ runner returns a non-zero exit status on fail and a zero on success,
37
+ everyone is happy.
38
+
39
+ Need to do some massaging of your repo before the tests run, like
40
+ maybe swapping in a new database.yml? No problem - Joe will try to
41
+ run `.git/hooks/after-reset` if it exists before each build phase.
42
+ Do it in there. Just make sure it's executable.
43
+
44
+ Want to notify IRC or email on test pass or failure? Joe will run
45
+ `.git/hooks/build-failed` or `.git/hooks/build-worked` if they exist
46
+ and are executable on build pass / fail. They're just shell scripts -
47
+ put whatever you want in there.
48
+
49
+ Tip: your repo's `HEAD` will point to the commit used to run the
50
+ build. Pull any metadata you want out of that scro.
51
+
52
+ ** WARNING ** Do not run this against a git repo that has unpushed
53
+ commits, as this will do a hard reset against the github remote and
54
+ wipe out unpushed changes.
55
+
56
+ Other Branches
57
+ ----------------------
58
+
59
+ Want joe to run against a branch other than `master`? No problem:
60
+
61
+ $ git config --add cijoe.branch deploy
62
+
63
+
64
+ Queueing
65
+ ----------------------------------------
66
+
67
+ Joe runs just one build at the time. If you expect concurrent push's
68
+ to your repo and want joe to build each in a kind of queue, just set:
69
+
70
+ $ git config --add cijoe.buildqueue true
71
+
72
+ Joe will save requests while another build runs. If more than one push
73
+ hits joe, he just picks the last after finishing the prior.
74
+
75
+
76
+ Campfire
77
+ -------------
78
+
79
+ Campfire notification is included, because it's what we use. Want Joe
80
+ notify your Campfire? Put this in your repo's `.git/config`:
81
+
82
+ [campfire]
83
+ token = abcd1234
84
+ subdomain = whatever
85
+ room = Awesomeness
86
+ ssl = false
87
+
88
+ Or do it the old fashion way:
89
+
90
+ $ cd yourrepo
91
+ $ git config --add campfire.token abcd1234
92
+ $ git config --add campfire.subdomain github
93
+ etc.
94
+
95
+
96
+ Checkin' Status
97
+ ----------------------
98
+
99
+ Want to see how your build's doing without any of this fancy UI crap?
100
+ Ping Joe for the lowdown:
101
+
102
+ curl http://localhost:4567/ping
103
+
104
+ Joe will return `200 OK` if all is quiet on the Western Front. If
105
+ Joe's busy building or your last build failed, you'll get `412
106
+ PRECONDITION FAILED`.
107
+
108
+
109
+ Multiple Projects
110
+ ------------------------
111
+
112
+ Want CI for multiple projects? Just start multiple instances of Joe!
113
+ He can run on any port - try `cijoe -h` for more options.
114
+
115
+ If you're using Passenger, see [this blog post](http://chrismdp.github.com/2010/03/multiple-ci-joes-with-rack-and-passenger/).
116
+
117
+
118
+ HTTP Auth
119
+ ----------------
120
+
121
+ Worried about people triggering your builds? Setup HTTP auth:
122
+
123
+ $ git config --add cijoe.user chris
124
+ $ git config --add cijoe.pass secret
125
+
126
+
127
+ GitHub Integration
128
+ --------------------------
129
+
130
+ Any POST to Joe will trigger a build. If you are hiding Joe behind
131
+ HTTP auth, that's okay - GitHub knows how to authenticate properly.
132
+
133
+ ![Post-Receive URL](http://img.skitch.com/20090806-d2bxrk733gqu8m11tf4kyir5d8.png)
134
+
135
+ You can find the Post-Receive option under the 'Service Hooks' subtab
136
+ of your project's "Admin" tab.
137
+
138
+
139
+ Daemonize
140
+ ----------------
141
+
142
+ Want to run Joe as a daemon? Use `nohup`:
143
+
144
+ $ nohup cijoe -p 4444 repo &
145
+
146
+
147
+ Other CI Servers
148
+ ------------------------
149
+
150
+ Need more features? More notifiers? Check out one of these bad boys:
151
+
152
+ * [Jenkins](http://jenkins-ci.org/)
153
+ * [Cerberus](http://cerberus.rubyforge.org/)
154
+ * [Integrity](http://integrityapp.com/)
155
+ * [CruiseControl.rb](http://cruisecontrolrb.thoughtworks.com/)
156
+ * [BuildBot](http://buildbot.net/trac)
157
+ * [Signal](http://www.github.com/dcrec1/signal)
158
+
159
+
160
+ Does GitHub use cijoe?
161
+ ---------------------------------
162
+
163
+ No. We use [Jenkins](http://jenkins-ci.org/).
164
+
165
+
166
+ Screenshots
167
+ ------------------
168
+
169
+ ![Building](http://img.skitch.com/20090806-ryw34ksi5ixnrdwxcptqy28iy7.png)
170
+
171
+ ![Built](http://img.skitch.com/20090806-f7j3r65yecaq13hdcxqwtc5krd.)
172
+
173
+
174
+ Questions? Concerns?
175
+ ---------------------------------
176
+
177
+ [Issues](http://github.com/defunkt/cijoe/issues)
data/Rakefile ADDED
@@ -0,0 +1,31 @@
1
+ require 'rake/testtask'
2
+ Rake::TestTask.new(:test) do |test|
3
+ test.libs << 'lib' << 'test'
4
+ test.pattern = 'test/**/test_*.rb'
5
+ test.verbose = true
6
+ end
7
+
8
+ begin
9
+ require 'rcov/rcovtask'
10
+ Rcov::RcovTask.new do |test|
11
+ test.libs << 'test'
12
+ test.pattern = 'test/**/test_*.rb'
13
+ test.verbose = true
14
+ end
15
+ rescue LoadError
16
+ task :rcov do
17
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
18
+ end
19
+ end
20
+
21
+ task :default => :test
22
+
23
+ require 'rake/rdoctask'
24
+ Rake::RDocTask.new do |rdoc|
25
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
26
+
27
+ rdoc.rdoc_dir = 'rdoc'
28
+ rdoc.title = "someproject #{version}"
29
+ rdoc.rdoc_files.include('README*')
30
+ rdoc.rdoc_files.include('lib/**/*.rb')
31
+ end
data/bin/cijoe ADDED
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
3
+
4
+ require 'choice'
5
+ require 'cijoe'
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
+
50
+ CIJoe::Server.start(options[:host], options[:port], File.expand_path(Choice.rest[0].to_s))
@@ -0,0 +1,67 @@
1
+ require 'yaml'
2
+
3
+ class CIJoe
4
+ class Build < Struct.new(:project_path, :user, :project, :started_at, :finished_at, :sha, :status, :output, :pid, :branch)
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 env_output
45
+ out = clean_output
46
+ out.size > 100_000 ? out[-100_000,100_000] : out
47
+ end
48
+
49
+ def commit
50
+ return if sha.nil?
51
+ @commit ||= Commit.new(sha, user, project, project_path)
52
+ end
53
+
54
+ def dump(file)
55
+ config = [user, project, started_at, finished_at, sha, status, output, pid, branch]
56
+ data = YAML.dump(config)
57
+ File.open(file, 'wb') { |io| io.write(data) }
58
+ end
59
+
60
+ def self.load(file, project_path)
61
+ if File.exist?(file)
62
+ config = YAML.load(File.read(file)).unshift(project_path)
63
+ new *config
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,74 @@
1
+ class CIJoe
2
+ class Campfire
3
+ attr_reader :project_path, :build
4
+
5
+ def initialize(project_path)
6
+ @project_path = project_path
7
+ if valid?
8
+ require 'tinder'
9
+ puts "Loaded Campfire notifier for #{project_path}."
10
+ elsif ENV['RACK_ENV'] != 'test'
11
+ puts "Can't load Campfire notifier for #{project_path}."
12
+ puts "Please add the following to your project's .git/config:"
13
+ puts "[campfire]"
14
+ puts "\ttoken = abcd1234"
15
+ puts "\tsubdomain = whatever"
16
+ puts "\troom = Awesomeness"
17
+ puts "\tssl = false"
18
+ end
19
+ end
20
+
21
+ def campfire_config
22
+ campfire_config = Config.new('campfire', project_path)
23
+ @config = {
24
+ :subdomain => campfire_config.subdomain.to_s,
25
+ :token => campfire_config.token.to_s,
26
+ :room => campfire_config.room.to_s,
27
+ :ssl => campfire_config.ssl.to_s.strip == 'true'
28
+ }
29
+ end
30
+
31
+ def valid?
32
+ %w( subdomain token room ).all? do |key|
33
+ !campfire_config[key.intern].empty?
34
+ end
35
+ end
36
+
37
+ def notify(build)
38
+ begin
39
+ @build = build
40
+ room.speak "#{short_message}. #{build.commit.url}"
41
+ room.paste full_message if build.failed?
42
+ room.leave
43
+ rescue
44
+ puts "Please check your campfire config for #{project_path}."
45
+ end
46
+ end
47
+
48
+ private
49
+ def room
50
+ @room ||= begin
51
+ config = campfire_config
52
+ campfire = Tinder::Campfire.new(config[:subdomain],
53
+ :token => config[:token],
54
+ :ssl => config[:ssl] || false)
55
+ campfire.find_room_by_name(config[:room])
56
+ end
57
+ end
58
+
59
+ def short_message
60
+ "#{build.branch} at #{build.short_sha} of #{build.project} " +
61
+ (build.worked? ? "passed" : "failed") + " (#{build.duration.to_i}s)"
62
+ end
63
+
64
+ def full_message
65
+ <<-EOM
66
+ Commit Message: #{build.commit.message}
67
+ Commit Date: #{build.commit.committed_at}
68
+ Commit Author: #{build.commit.author}
69
+
70
+ #{build.clean_output}
71
+ EOM
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,31 @@
1
+ class CIJoe
2
+ class Commit < Struct.new(:sha, :user, :project, :project_path)
3
+ def url
4
+ # BeanStalk
5
+ "https://oxygenwebs.beanstalkapp.com/#{project}/changesets/#{sha}"
6
+
7
+ # GitHub
8
+ # "http://github.com/#{user}/#{project}/commit/#{sha}"
9
+ end
10
+
11
+ def author
12
+ raw_commit_lines.grep(/Author:/).first.split(':', 2)[-1]
13
+ end
14
+
15
+ def committed_at
16
+ raw_commit_lines.grep(/Date:/).first.split(':', 2)[-1]
17
+ end
18
+
19
+ def message
20
+ raw_commit.split("\n\n", 3)[1].to_s.strip
21
+ end
22
+
23
+ def raw_commit
24
+ @raw_commit ||= `cd \"#{project_path}\" && git show #{sha}`.chomp
25
+ end
26
+
27
+ def raw_commit_lines
28
+ @raw_commit_lines ||= raw_commit.split("\n")
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,43 @@
1
+ class CIJoe
2
+ class Config
3
+ def self.method_missing(command, *args)
4
+ new(command, *args)
5
+ end
6
+
7
+ def initialize(command, project_path = nil, parent = nil)
8
+ @command = command
9
+ @parent = parent
10
+ @project_path = project_path || File.join(File.dirname(__FILE__), '../../')
11
+ end
12
+
13
+ def method_missing(command, *args)
14
+ Config.new(command, @project_path, self)
15
+ end
16
+
17
+ def to_s
18
+ git_command = "cd \"#{@project_path}\" && git config #{config_string}"
19
+ result = `#{git_command} 2>&1`.chomp
20
+ process_status = $?
21
+
22
+ if successful_command?(process_status) || config_command_with_empty_value?(result,process_status)
23
+ return result
24
+ else
25
+ raise "Error calling git config, is a recent version of git installed? Command: #{git_command.inspect}, Error: #{result.inspect}, Status: #{process_status.inspect}"
26
+ end
27
+ end
28
+
29
+ def config_string
30
+ @parent ? "#{@parent.config_string}.#{@command}" : @command
31
+ end
32
+
33
+ private
34
+
35
+ def successful_command?(process_status)
36
+ process_status.exitstatus.to_i == 0
37
+ end
38
+
39
+ def config_command_with_empty_value?(result, process_status)
40
+ process_status.exitstatus.to_i == 1 && result.empty?
41
+ end
42
+ end
43
+ end
Binary file
Binary file
@@ -0,0 +1,213 @@
1
+ /*****************************************************************************/
2
+ /*
3
+ /* Common
4
+ /*
5
+ /*****************************************************************************/
6
+
7
+ /* Global Reset */
8
+
9
+ * {
10
+ margin: 0;
11
+ padding: 0;
12
+ }
13
+
14
+ html, body {
15
+ height: 100%;
16
+ }
17
+
18
+ body {
19
+ background-color: white;
20
+ font: 13.34px helvetica, arial, clean, sans-serif;
21
+ *font-size: small;
22
+ text-align: center;
23
+ }
24
+
25
+ h1, h2, h3, h4, h5, h6 {
26
+ font-size: 100%;
27
+ }
28
+
29
+ h1 {
30
+ margin-bottom: 1em;
31
+ }
32
+
33
+ h1 a {
34
+ text-decoration: none;
35
+ color: #000;
36
+ }
37
+
38
+ .failed, .color31 {
39
+ color: red !important;
40
+ }
41
+
42
+ .worked, .color32 {
43
+ color: green !important;
44
+ }
45
+
46
+ .errored, .color33 {
47
+ color: yellow !important;
48
+ }
49
+
50
+ p {
51
+ margin: 1em 0;
52
+ }
53
+
54
+ a {
55
+ color: #00a;
56
+ }
57
+
58
+ a:hover {
59
+ color: black;
60
+ }
61
+
62
+ a:visited {
63
+ color: #a0a;
64
+ }
65
+
66
+ table {
67
+ font-size: inherit;
68
+ font: 100%;
69
+ }
70
+
71
+ /*****************************************************************************/
72
+ /*
73
+ /* Home
74
+ /*
75
+ /*****************************************************************************/
76
+
77
+ ul.posts {
78
+ list-style-type: none;
79
+ margin-bottom: 2em;
80
+ }
81
+
82
+ ul.posts li {
83
+ line-height: 1.75em;
84
+ }
85
+
86
+ ul.posts .date,
87
+ ul.posts .duration {
88
+ color: #aaa;
89
+ font-family: Monaco, "Courier New", monospace;
90
+ font-size: 80%;
91
+ }
92
+
93
+ /*****************************************************************************/
94
+ /*
95
+ /* Site
96
+ /*
97
+ /*****************************************************************************/
98
+
99
+ .site {
100
+ font-size: 110%;
101
+ text-align: justify;
102
+ width: 80%;
103
+ margin: 3em auto 2em auto;
104
+ line-height: 1.5em;
105
+ }
106
+
107
+ .title {
108
+ color: #a00;
109
+ font-weight: bold;
110
+ margin-bottom: 2em;
111
+ }
112
+
113
+ .site .title a {
114
+ color: #a00;
115
+ text-decoration: none;
116
+ }
117
+
118
+ .site .title a:hover {
119
+ color: black;
120
+ }
121
+
122
+ .site .title .extra {
123
+ color: #aaa;
124
+ text-decoration: none;
125
+ margin-left: 1em;
126
+ font-size: 0.9em;
127
+ }
128
+
129
+ .site .title a.extra:hover {
130
+ color: black;
131
+ }
132
+
133
+ .site .meta {
134
+ color: #aaa;
135
+ }
136
+
137
+ .site .footer {
138
+ font-size: 80%;
139
+ color: #666;
140
+ border-top: 4px solid #eee;
141
+ margin-top: 2em;
142
+ overflow: hidden;
143
+ }
144
+
145
+ .site .footer .contact {
146
+ float: left;
147
+ margin-right: 3em;
148
+ }
149
+
150
+ .site .footer .contact a {
151
+ color: #8085C1;
152
+ }
153
+
154
+ .site .footer .rss {
155
+ margin-top: 1.1em;
156
+ margin-right: -.2em;
157
+ float: right;
158
+ }
159
+
160
+ .site .footer .rss img {
161
+ border: 0;
162
+ }
163
+
164
+ /*****************************************************************************/
165
+ /*
166
+ /* Posts
167
+ /*
168
+ /*****************************************************************************/
169
+
170
+ #post {
171
+
172
+ }
173
+
174
+ /* standard */
175
+
176
+ #post pre {
177
+ border: 1px solid #ddd;
178
+ background-color: #eef;
179
+ padding: 0 .4em;
180
+ }
181
+
182
+ #post ul,
183
+ #post ol {
184
+ margin-left: 1.25em;
185
+ }
186
+
187
+ #post code {
188
+ border: 1px solid #ddd;
189
+ background-color: #eef;
190
+ font-size: 95%;
191
+ padding: 0 .2em;
192
+ }
193
+
194
+ #post pre code {
195
+ border: none;
196
+ }
197
+
198
+ /* terminal */
199
+
200
+ pre.terminal {
201
+ border: 1px solid black;
202
+ background-color: #333;
203
+ color: white;
204
+ padding: 5px;
205
+ overflow: auto;
206
+ word-wrap: break-word;
207
+ }
208
+
209
+ pre.terminal code {
210
+ font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace;
211
+ background-color: #333;
212
+ }
213
+