snake_eyes 0.1.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 +20 -0
- data/README.markdown +212 -0
- data/Rakefile +25 -0
- data/bin/snake_eyes +49 -0
- data/deps.rip +5 -0
- data/examples/cijoed +53 -0
- data/lib/cijoe.rb +148 -0
- data/lib/cijoe/build.rb +33 -0
- data/lib/cijoe/campfire.rb +72 -0
- data/lib/cijoe/commit.rb +27 -0
- data/lib/cijoe/config.rb +24 -0
- data/lib/cijoe/gmail.rb +142 -0
- data/lib/cijoe/public/octocat.png +0 -0
- data/lib/cijoe/public/screen.css +212 -0
- data/lib/cijoe/server.rb +84 -0
- data/lib/cijoe/version.rb +3 -0
- data/lib/cijoe/views/template.erb +64 -0
- metadata +103 -0
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,212 @@
|
|
|
1
|
+
Snake Eyes
|
|
2
|
+
==========
|
|
3
|
+
|
|
4
|
+
Snake Eyes adds [ninja power](http://drmcninja.com/) to [CI Joe](http://github.com/defunkt/cijoe).
|
|
5
|
+
|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
CI Joe
|
|
9
|
+
======
|
|
10
|
+
|
|
11
|
+
Joe is a [Continuous
|
|
12
|
+
Integration](http://en.wikipedia.org/wiki/Continuous_integration)
|
|
13
|
+
server that'll run your tests on demand and report their pass/fail status.
|
|
14
|
+
|
|
15
|
+
Because knowing is half the battle.
|
|
16
|
+
|
|
17
|
+

|
|
18
|
+
|
|
19
|
+
Quickstart
|
|
20
|
+
----------
|
|
21
|
+
|
|
22
|
+
Rip:
|
|
23
|
+
|
|
24
|
+
$ rip install git://github.com/defunkt/cijoe.git
|
|
25
|
+
$ git clone git://github.com/you/yourrepo.git
|
|
26
|
+
$ cijoe yourrepo
|
|
27
|
+
|
|
28
|
+
Gemcutter:
|
|
29
|
+
|
|
30
|
+
$ gem install cijoe
|
|
31
|
+
$ git clone git://github.com/you/yourrepo.git
|
|
32
|
+
$ cijoe yourrepo
|
|
33
|
+
|
|
34
|
+
Boom. Navigate to http://localhost:4567 to see Joe in action.
|
|
35
|
+
Check `cijoe -h` for other options.
|
|
36
|
+
|
|
37
|
+
Basically you need to run `cijoe` and hand it the path to a git
|
|
38
|
+
repo. Make sure this isn't a shared repo: Joe needs to own it.
|
|
39
|
+
|
|
40
|
+
Joe looks for various git config settings in the repo you hand it. For
|
|
41
|
+
instance, you can tell Joe what command to run by setting
|
|
42
|
+
`cijoe.runner`:
|
|
43
|
+
|
|
44
|
+
$ git config --add cijoe.runner "rake -s test:units"
|
|
45
|
+
|
|
46
|
+
Joe doesn't care about Ruby, Python, or whatever. As long as the
|
|
47
|
+
runner returns a non-zero exit status on fail and a zero on success,
|
|
48
|
+
everyone is happy.
|
|
49
|
+
|
|
50
|
+
Need to do some massaging of your repo before the tests run, like
|
|
51
|
+
maybe swapping in a new database.yml? No problem - Joe will try to
|
|
52
|
+
run `.git/hooks/after-reset` if it exists before each build phase.
|
|
53
|
+
Do it in there. Just make sure it's executable.
|
|
54
|
+
|
|
55
|
+
Want to notify IRC or email on test pass or failure? Joe will run
|
|
56
|
+
`.git/hooks/build-failed` or `.git/hooks/build-worked` if they exist
|
|
57
|
+
and are executable on build pass / fail. They're just shell scripts -
|
|
58
|
+
put whatever you want in there.
|
|
59
|
+
|
|
60
|
+
Tip: your repo's `HEAD` will point to the commit used to run the
|
|
61
|
+
build. Pull any metadata you want out of that scro.
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
Other Branches
|
|
65
|
+
--------------
|
|
66
|
+
|
|
67
|
+
Want Joe to run against a branch other than `master`? No problem:
|
|
68
|
+
|
|
69
|
+
$ git config --add cijoe.branch deploy
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
Notifiers
|
|
73
|
+
---------
|
|
74
|
+
|
|
75
|
+
CI Joe includes Campfire notification, because it's what they use at GitHub,
|
|
76
|
+
where CI Joe came into being. Want Joe to notify your Campfire? Put this in
|
|
77
|
+
your repo's `.git/config`:
|
|
78
|
+
|
|
79
|
+
[campfire]
|
|
80
|
+
user = your@campfire.email
|
|
81
|
+
pass = passw0rd
|
|
82
|
+
subdomain = whatever
|
|
83
|
+
room = Awesomeness
|
|
84
|
+
ssl = false
|
|
85
|
+
|
|
86
|
+
Or do it the old-fashioned way:
|
|
87
|
+
|
|
88
|
+
$ cd yourrepo
|
|
89
|
+
$ git config --add campfire.user chris@ozmm.org
|
|
90
|
+
$ git config --add campfire.subdomain github
|
|
91
|
+
etc.
|
|
92
|
+
|
|
93
|
+
Snake Eyes gives you an additional option: Gmail. In `.git/config`:
|
|
94
|
+
|
|
95
|
+
[gmail]
|
|
96
|
+
user = your_ci_joe@email
|
|
97
|
+
pass = passw0rd
|
|
98
|
+
recipient = developers@your-company.com
|
|
99
|
+
|
|
100
|
+
Or:
|
|
101
|
+
|
|
102
|
+
$ cd yourrepo
|
|
103
|
+
$ git config --add campfire.user your.ci.server@gmail.com
|
|
104
|
+
$ git config --add campfire.password s3cr3t
|
|
105
|
+
etc
|
|
106
|
+
|
|
107
|
+
If it's not obvious, you do in fact need to have this gmail account and
|
|
108
|
+
the password does in fact need to be valid.
|
|
109
|
+
|
|
110
|
+
CI Joe gives you Campfire (and only Campfire) by default, but Snake Eyes
|
|
111
|
+
makes you specify your notifier in your project's `.git/config`. Sorry for
|
|
112
|
+
the extra work.
|
|
113
|
+
|
|
114
|
+
[cijoe]
|
|
115
|
+
notifier = CIJoe::Gmail
|
|
116
|
+
|
|
117
|
+
Or:
|
|
118
|
+
|
|
119
|
+
[cijoe]
|
|
120
|
+
notifier = CIJoe::Campfire
|
|
121
|
+
|
|
122
|
+
Extending Snake Eyes to support additional notifiers is very, very easy.
|
|
123
|
+
Even though Chris Wanstraäth, CI Joe's author, was very, very explicit
|
|
124
|
+
about not supporting any kind of notifier except the Campfire notifier,
|
|
125
|
+
his code furnishes an API which is very, very extensible and very, very
|
|
126
|
+
friendly to repurposing.
|
|
127
|
+
|
|
128
|
+
The CI Joe Campfire notifier uses a `valid_config?` method to check that the
|
|
129
|
+
notifier will be able to work. If so, it loads the Campfire module right into
|
|
130
|
+
CI Joe, so that when CI Joe calls `notify`, it's calling the `notify` on the
|
|
131
|
+
Campfire module. If you're creating your own notifier module, all you need
|
|
132
|
+
to support is an `activate` method on the module itself and a `notify` instance
|
|
133
|
+
method. Copying the `valid_config?` pattern is strongly advised but absolutely
|
|
134
|
+
not required.
|
|
135
|
+
|
|
136
|
+
Warning: Snake Eyes enables arbitrary notifiers using a language feature
|
|
137
|
+
called `eval`. Many people believe `eval` is evil. All who know it fear
|
|
138
|
+
its power. Snake Eyes is comfortable with `eval` because Snake Eyes is a
|
|
139
|
+
fucking ninja. If you cut yourself on a sharp piece of `eval`, don't come crying
|
|
140
|
+
to Snake Eyes. You'll never find him. Because he's a ninja.
|
|
141
|
+
|
|
142
|
+
Multiple Projects
|
|
143
|
+
-----------------
|
|
144
|
+
|
|
145
|
+
Want CI for multiple projects? Just start multiple instances of Joe!
|
|
146
|
+
He can run on any port - try `cijoe -h` for more options.
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
HTTP Auth
|
|
150
|
+
---------
|
|
151
|
+
|
|
152
|
+
Worried about people triggering your builds? Setup HTTP auth:
|
|
153
|
+
|
|
154
|
+
$ git config --add cijoe.user chris
|
|
155
|
+
$ git config --add cijoe.pass secret
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
GitHub Integration
|
|
159
|
+
------------------
|
|
160
|
+
|
|
161
|
+
Any POST to Joe will trigger a build. If you are hiding Joe behind
|
|
162
|
+
HTTP auth, that's okay - GitHub knows how to authenticate properly.
|
|
163
|
+
|
|
164
|
+

|
|
165
|
+
|
|
166
|
+
You can find the Post-Receive option under the 'Service Hooks' subtab
|
|
167
|
+
of your project's "Admin" tab.
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
Daemonize
|
|
171
|
+
---------
|
|
172
|
+
|
|
173
|
+
Want to run Joe as a daemon? Use `nohup`:
|
|
174
|
+
|
|
175
|
+
$ nohup cijoe -p 4444 repo &
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
Other CI Servers
|
|
179
|
+
----------------
|
|
180
|
+
|
|
181
|
+
Need more features? Check out one of these bad boys:
|
|
182
|
+
|
|
183
|
+
* [Cerberus](http://cerberus.rubyforge.org/)
|
|
184
|
+
* [Integrity](http://integrityapp.com/)
|
|
185
|
+
* [CruiseControl.rb](http://cruisecontrolrb.thoughtworks.com/)
|
|
186
|
+
* [BuildBot](http://buildbot.net/trac)
|
|
187
|
+
|
|
188
|
+
Need less notifiers? Check out the original CI Joe:
|
|
189
|
+
|
|
190
|
+
* [CI Joe](http://github.com/defunkt/cijoe)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
Screenshots
|
|
194
|
+
-----------
|
|
195
|
+
|
|
196
|
+

|
|
197
|
+
|
|
198
|
+

|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
Questions? Concerns?
|
|
202
|
+
--------------------
|
|
203
|
+
|
|
204
|
+
For CI Joe:
|
|
205
|
+
|
|
206
|
+
[Issues](http://github.com/defunkt/cijoe/issues) or [the mailing list](http://groups.google.com/group/cijoe).
|
|
207
|
+
|
|
208
|
+
( Chris Wanstrath :: chris@ozmm.org )
|
|
209
|
+
|
|
210
|
+
For Snake Eyes: do not attempt to contact Snake Eyes. Snake Eyes is a ninja.
|
|
211
|
+
|
|
212
|
+
( Giles Bowkett :: gilesb@gmail.com )
|
data/Rakefile
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
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 = "snake_eyes"
|
|
12
|
+
gemspec.summary = "Snake Eyes is a fork of CI Joe, a simple Continuous Integration server."
|
|
13
|
+
gemspec.description = "Snake Eyes is a fork of CI Joe, a simple Continuous Integration server."
|
|
14
|
+
gemspec.email = "gilesb@gmail.com"
|
|
15
|
+
gemspec.homepage = "http://github.com/gilesbowkett/snake_eyes"
|
|
16
|
+
gemspec.authors = ["Chris Wanstrath", "Giles Bowkett", "Dr. McNinja"]
|
|
17
|
+
gemspec.add_dependency 'choice'
|
|
18
|
+
gemspec.add_dependency 'sinatra'
|
|
19
|
+
gemspec.add_dependency 'open4'
|
|
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
|
data/bin/snake_eyes
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
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,148 @@
|
|
|
1
|
+
##
|
|
2
|
+
# Chris said:
|
|
3
|
+
# Seriously, I'm gonna be nuts about keeping this simple.
|
|
4
|
+
#
|
|
5
|
+
# Cool.
|
|
6
|
+
#
|
|
7
|
+
# He also said:
|
|
8
|
+
# It only notifies to Campfire.
|
|
9
|
+
#
|
|
10
|
+
# Wait. What? No. I need it notifying via Gmail.
|
|
11
|
+
#
|
|
12
|
+
# And thus was born Snake Eyes, which brings ninja power to CI Joe.
|
|
13
|
+
#
|
|
14
|
+
# Specifically, the ninja power of Gmail. And of arbitrary notifiers
|
|
15
|
+
# to anywhere, which really required very very little effort. Despite Chris'
|
|
16
|
+
# anti-other-notifiers stance, he wrote one of the easiest APIs to extend
|
|
17
|
+
# to other notifiers that I've ever seen.
|
|
18
|
+
#
|
|
19
|
+
# btw, I suppose I should have called it Cnake Eyes, but it's too late now.
|
|
20
|
+
|
|
21
|
+
begin
|
|
22
|
+
require 'open4'
|
|
23
|
+
rescue LoadError
|
|
24
|
+
abort "** Please install open4"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
require 'cijoe/version'
|
|
28
|
+
require 'cijoe/config'
|
|
29
|
+
require 'cijoe/commit'
|
|
30
|
+
require 'cijoe/build'
|
|
31
|
+
require 'cijoe/campfire'
|
|
32
|
+
require 'cijoe/gmail'
|
|
33
|
+
require 'cijoe/server'
|
|
34
|
+
|
|
35
|
+
class CIJoe
|
|
36
|
+
attr_reader :user, :project, :url, :current_build, :last_build
|
|
37
|
+
|
|
38
|
+
def initialize(project_path)
|
|
39
|
+
project_path = File.expand_path(project_path)
|
|
40
|
+
Dir.chdir(project_path)
|
|
41
|
+
|
|
42
|
+
@user, @project = git_user_and_project
|
|
43
|
+
@url = "http://github.com/#{@user}/#{@project}"
|
|
44
|
+
|
|
45
|
+
@last_build = nil
|
|
46
|
+
@current_build = nil
|
|
47
|
+
|
|
48
|
+
trap("INT") { stop }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# is a build running?
|
|
52
|
+
def building?
|
|
53
|
+
!!@current_build
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# the pid of the running child process
|
|
57
|
+
def pid
|
|
58
|
+
building? and @pid
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# kill the child and exit
|
|
62
|
+
def stop
|
|
63
|
+
Process.kill(9, pid) if pid
|
|
64
|
+
exit!
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# build callbacks
|
|
68
|
+
def build_failed(output, error)
|
|
69
|
+
finish_build :failed, "#{error}\n\n#{output}"
|
|
70
|
+
run_hook "build-failed"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def build_worked(output)
|
|
74
|
+
finish_build :worked, output
|
|
75
|
+
run_hook "build-worked"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def finish_build(status, output)
|
|
79
|
+
@current_build.finished_at = Time.now
|
|
80
|
+
@current_build.status = status
|
|
81
|
+
@current_build.output = output
|
|
82
|
+
@last_build = @current_build
|
|
83
|
+
@current_build = nil
|
|
84
|
+
@last_build.notify if @last_build.respond_to? :notify
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# run the build but make sure only
|
|
88
|
+
# one is running at a time
|
|
89
|
+
def build
|
|
90
|
+
return if building?
|
|
91
|
+
@current_build = Build.new(@user, @project)
|
|
92
|
+
Thread.new { build! }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# update git then run the build
|
|
96
|
+
def build!
|
|
97
|
+
out, err, status = '', '', nil
|
|
98
|
+
git_update
|
|
99
|
+
@current_build.sha = git_sha
|
|
100
|
+
|
|
101
|
+
status = Open4.popen4(runner_command) do |pid, stdin, stdout, stderr|
|
|
102
|
+
@pid = pid
|
|
103
|
+
err, out = stderr.read.strip, stdout.read.strip
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
status.exitstatus.to_i == 0 ? build_worked(out) : build_failed(out, err)
|
|
107
|
+
rescue Object => e
|
|
108
|
+
build_failed('', e.to_s)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# shellin' out
|
|
112
|
+
def runner_command
|
|
113
|
+
runner = Config.cijoe.runner.to_s
|
|
114
|
+
runner == '' ? "rake -s test:units" : runner
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def git_sha
|
|
118
|
+
`git rev-parse origin/#{git_branch}`.chomp
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def git_update
|
|
122
|
+
`git fetch origin && git reset --hard origin/#{git_branch}`
|
|
123
|
+
run_hook "after-reset"
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def git_user_and_project
|
|
127
|
+
Config.remote.origin.url.to_s.chomp('.git').split(':')[-1].split('/')[-2, 2]
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def git_branch
|
|
131
|
+
branch = Config.cijoe.branch.to_s
|
|
132
|
+
branch == '' ? "master" : branch
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# massage our repo
|
|
136
|
+
def run_hook(hook)
|
|
137
|
+
if File.exists?(file=".git/hooks/#{hook}") && File.executable?(file)
|
|
138
|
+
data = {
|
|
139
|
+
"MESSAGE" => @last_build.commit.message,
|
|
140
|
+
"AUTHOR" => @last_build.commit.author,
|
|
141
|
+
"SHA" => @last_build.commit.sha,
|
|
142
|
+
"OUTPUT" => @last_build.clean_output
|
|
143
|
+
}
|
|
144
|
+
env = data.collect { |k, v| %(#{k}=#{v.inspect}) }.join(" ")
|
|
145
|
+
`#{env} sh #{file}`
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
data/lib/cijoe/build.rb
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
class CIJoe
|
|
2
|
+
class Build < Struct.new(:user, :project, :started_at, :finished_at, :sha, :status, :output)
|
|
3
|
+
def initialize(*args)
|
|
4
|
+
super
|
|
5
|
+
self.started_at = Time.now
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def status
|
|
9
|
+
return super if started_at && finished_at
|
|
10
|
+
:building
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def failed?
|
|
14
|
+
status == :failed
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def worked?
|
|
18
|
+
status == :worked
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def short_sha
|
|
22
|
+
sha[0,7] if sha
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def clean_output
|
|
26
|
+
output.gsub(/\e\[.+?m/, '').strip
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def commit
|
|
30
|
+
@commit ||= Commit.new(sha, user, project)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
class CIJoe
|
|
2
|
+
module Campfire
|
|
3
|
+
def self.activate
|
|
4
|
+
if valid_config?
|
|
5
|
+
require 'tinder'
|
|
6
|
+
|
|
7
|
+
CIJoe::Build.class_eval do
|
|
8
|
+
include CIJoe::Campfire
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
puts "Loaded Campfire notifier"
|
|
12
|
+
else
|
|
13
|
+
puts "Can't load Campfire notifier."
|
|
14
|
+
puts "Please add the following to your project's .git/config:"
|
|
15
|
+
puts "[campfire]"
|
|
16
|
+
puts "\tuser = your@campfire.email"
|
|
17
|
+
puts "\tpass = passw0rd"
|
|
18
|
+
puts "\tsubdomain = whatever"
|
|
19
|
+
puts "\troom = Awesomeness"
|
|
20
|
+
puts "\tssl = false"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.config
|
|
25
|
+
@config ||= {
|
|
26
|
+
:subdomain => Config.campfire.subdomain.to_s,
|
|
27
|
+
:user => Config.campfire.user.to_s,
|
|
28
|
+
:pass => Config.campfire.pass.to_s,
|
|
29
|
+
:room => Config.campfire.room.to_s,
|
|
30
|
+
:ssl => Config.campfire.ssl.to_s.strip == 'true'
|
|
31
|
+
}
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.valid_config?
|
|
35
|
+
%w( subdomain user pass room ).all? do |key|
|
|
36
|
+
!config[key.intern].empty?
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def notify
|
|
41
|
+
room.speak "#{short_message}. #{commit.url}"
|
|
42
|
+
room.paste full_message if failed?
|
|
43
|
+
room.leave
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
def room
|
|
48
|
+
@room ||= begin
|
|
49
|
+
config = Campfire.config
|
|
50
|
+
options = {}
|
|
51
|
+
options[:ssl] = config[:ssl] ? true : false
|
|
52
|
+
campfire = Tinder::Campfire.new(config[:subdomain], options)
|
|
53
|
+
campfire.login(config[:user], config[:pass])
|
|
54
|
+
campfire.find_room_by_name(config[:room])
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def short_message
|
|
59
|
+
"Build #{short_sha} of #{project} #{worked? ? "was successful" : "failed"}"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def full_message
|
|
63
|
+
<<-EOM
|
|
64
|
+
Commit Message: #{commit.message}
|
|
65
|
+
Commit Date: #{commit.committed_at}
|
|
66
|
+
Commit Author: #{commit.author}
|
|
67
|
+
|
|
68
|
+
#{clean_output}
|
|
69
|
+
EOM
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
data/lib/cijoe/commit.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
class CIJoe
|
|
2
|
+
class Commit < Struct.new(:sha, :user, :project)
|
|
3
|
+
def url
|
|
4
|
+
"http://github.com/#{user}/#{project}/commit/#{sha}"
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def author
|
|
8
|
+
raw_commit_lines[1].split(':')[-1]
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def committed_at
|
|
12
|
+
raw_commit_lines[2].split(':', 2)[-1]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def message
|
|
16
|
+
raw_commit_lines[4].split(':')[-1].strip
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def raw_commit
|
|
20
|
+
@raw_commit ||= `git show #{sha}`.chomp
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def raw_commit_lines
|
|
24
|
+
@raw_commit_lines ||= raw_commit.split("\n")
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
data/lib/cijoe/config.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
class CIJoe
|
|
2
|
+
class Config
|
|
3
|
+
def self.method_missing(command, *args)
|
|
4
|
+
new(command)
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def initialize(command, parent = nil)
|
|
8
|
+
@command = command
|
|
9
|
+
@parent = parent
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def method_missing(command, *args)
|
|
13
|
+
Config.new(command, self)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def to_s
|
|
17
|
+
`git config #{config_string}`.chomp
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def config_string
|
|
21
|
+
@parent ? "#{@parent.config_string}.#{@command}" : @command
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
data/lib/cijoe/gmail.rb
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# The CI Joe Gmail notifier is a mashup of two pieces of code.
|
|
2
|
+
#
|
|
3
|
+
# 1) This gist:
|
|
4
|
+
#
|
|
5
|
+
# http://gist.github.com/122071
|
|
6
|
+
#
|
|
7
|
+
# 2) The CI Joe Campfire notifier.
|
|
8
|
+
#
|
|
9
|
+
# The CI Joe Campfire notifier uses a valid_config? method to check that the notifier will be able to work. If so, it loads
|
|
10
|
+
# the Campfire module right into CI Joe, so that when CI Joe calls #notify, it's calling the #notify on the Campfire module.
|
|
11
|
+
# Obviously, even though Chris was very explicit about not supporting any kind of notifier except the Campfire notifier, this
|
|
12
|
+
# is a very, very extensible API which is very, very friendly to repurposing. So I made with the repurpose. All you need to
|
|
13
|
+
# support is an #activate method on the module itself and a #notify instance method. Copying the #valid_config? pattern is
|
|
14
|
+
# strongly advised but absolutely not required.
|
|
15
|
+
|
|
16
|
+
class CIJoe
|
|
17
|
+
module Gmail
|
|
18
|
+
def self.activate
|
|
19
|
+
if valid_config?
|
|
20
|
+
require "openssl"
|
|
21
|
+
require "net/smtp"
|
|
22
|
+
|
|
23
|
+
# http://www.jamesbritt.com/2007/12/18/sending-mail-through-gmail-with-ruby-s-net-smtp
|
|
24
|
+
# http://d.hatena.ne.jp/zorio/20060416
|
|
25
|
+
|
|
26
|
+
Net::SMTP.class_eval do
|
|
27
|
+
private
|
|
28
|
+
def do_start(helodomain, user, secret, authtype)
|
|
29
|
+
raise IOError, 'SMTP session already started' if @started
|
|
30
|
+
check_auth_args user, secret, authtype if user or secret
|
|
31
|
+
|
|
32
|
+
sock = timeout(@open_timeout) { TCPSocket.open(@address, @port) }
|
|
33
|
+
@socket = Net::InternetMessageIO.new(sock)
|
|
34
|
+
@socket.read_timeout = 60 #@read_timeout
|
|
35
|
+
@socket.debug_output = STDERR #@debug_output
|
|
36
|
+
|
|
37
|
+
check_response(critical { recv_response() })
|
|
38
|
+
do_helo(helodomain)
|
|
39
|
+
|
|
40
|
+
raise 'openssl library not installed' unless defined?(OpenSSL)
|
|
41
|
+
starttls
|
|
42
|
+
ssl = OpenSSL::SSL::SSLSocket.new(sock)
|
|
43
|
+
ssl.sync_close = true
|
|
44
|
+
ssl.connect
|
|
45
|
+
@socket = Net::InternetMessageIO.new(ssl)
|
|
46
|
+
@socket.read_timeout = 60 #@read_timeout
|
|
47
|
+
@socket.debug_output = STDERR #@debug_output
|
|
48
|
+
do_helo(helodomain)
|
|
49
|
+
|
|
50
|
+
authenticate user, secret, authtype if user
|
|
51
|
+
@started = true
|
|
52
|
+
ensure
|
|
53
|
+
unless @started
|
|
54
|
+
# authentication failed, cancel connection.
|
|
55
|
+
@socket.close if not @started and @socket and not @socket.closed?
|
|
56
|
+
@socket = nil
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def do_helo(helodomain)
|
|
61
|
+
begin
|
|
62
|
+
if @esmtp
|
|
63
|
+
ehlo helodomain
|
|
64
|
+
else
|
|
65
|
+
helo helodomain
|
|
66
|
+
end
|
|
67
|
+
rescue Net::ProtocolError
|
|
68
|
+
if @esmtp
|
|
69
|
+
@esmtp = false
|
|
70
|
+
@error_occured = false
|
|
71
|
+
retry
|
|
72
|
+
end
|
|
73
|
+
raise
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def starttls
|
|
78
|
+
getok('STARTTLS')
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
CIJoe::Build.class_eval do
|
|
83
|
+
include CIJoe::Gmail
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
puts "Loaded Gmail notifier"
|
|
87
|
+
else
|
|
88
|
+
puts "Can't load Gmail notifier."
|
|
89
|
+
puts "Please add the following to your project's .git/config:"
|
|
90
|
+
puts "[gmail]"
|
|
91
|
+
puts "\tuser = your_ci_joe@email"
|
|
92
|
+
puts "\tpass = passw0rd"
|
|
93
|
+
puts "\trecipient = developers@your-company.com"
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def self.config
|
|
98
|
+
@config ||= {
|
|
99
|
+
:user => Config.gmail.user.to_s,
|
|
100
|
+
:pass => Config.gmail.pass.to_s
|
|
101
|
+
}
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def self.valid_config?
|
|
105
|
+
%w( user pass recipient ).all? do |key|
|
|
106
|
+
!config[key.intern].empty?
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def notify
|
|
111
|
+
send_email(@config[:user],
|
|
112
|
+
"Vidli Continuous Integration",
|
|
113
|
+
@config[:recipient],
|
|
114
|
+
@config[:recipient],
|
|
115
|
+
short_message,
|
|
116
|
+
"#{full_message}\n#{commit.url}")
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
# http://snippets.dzone.com/posts/show/2362
|
|
121
|
+
def send_email(from, from_alias, to, to_alias, subject, message)
|
|
122
|
+
msg = <<END_OF_MESSAGE
|
|
123
|
+
From: #{from_alias} <#{from}>
|
|
124
|
+
To: #{to_alias} <#{to}>
|
|
125
|
+
Subject: #{subject}
|
|
126
|
+
|
|
127
|
+
#{message}
|
|
128
|
+
END_OF_MESSAGE
|
|
129
|
+
|
|
130
|
+
Net::SMTP.start("smtp.gmail.com",
|
|
131
|
+
587,
|
|
132
|
+
"localhost.localdomain",
|
|
133
|
+
@config[:user],
|
|
134
|
+
@config[:pass],
|
|
135
|
+
"plain") do |smtp|
|
|
136
|
+
smtp.send_message msg, from, to
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
Binary file
|
|
@@ -0,0 +1,212 @@
|
|
|
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
|
+
color: #aaa;
|
|
88
|
+
font-family: Monaco, "Courier New", monospace;
|
|
89
|
+
font-size: 80%;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/*****************************************************************************/
|
|
93
|
+
/*
|
|
94
|
+
/* Site
|
|
95
|
+
/*
|
|
96
|
+
/*****************************************************************************/
|
|
97
|
+
|
|
98
|
+
.site {
|
|
99
|
+
font-size: 110%;
|
|
100
|
+
text-align: justify;
|
|
101
|
+
width: 40em;
|
|
102
|
+
margin: 3em auto 2em auto;
|
|
103
|
+
line-height: 1.5em;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.title {
|
|
107
|
+
color: #a00;
|
|
108
|
+
font-weight: bold;
|
|
109
|
+
margin-bottom: 2em;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.site .title a {
|
|
113
|
+
color: #a00;
|
|
114
|
+
text-decoration: none;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.site .title a:hover {
|
|
118
|
+
color: black;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.site .title .extra {
|
|
122
|
+
color: #aaa;
|
|
123
|
+
text-decoration: none;
|
|
124
|
+
margin-left: 1em;
|
|
125
|
+
font-size: 0.9em;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.site .title a.extra:hover {
|
|
129
|
+
color: black;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.site .meta {
|
|
133
|
+
color: #aaa;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.site .footer {
|
|
137
|
+
font-size: 80%;
|
|
138
|
+
color: #666;
|
|
139
|
+
border-top: 4px solid #eee;
|
|
140
|
+
margin-top: 2em;
|
|
141
|
+
overflow: hidden;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.site .footer .contact {
|
|
145
|
+
float: left;
|
|
146
|
+
margin-right: 3em;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.site .footer .contact a {
|
|
150
|
+
color: #8085C1;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.site .footer .rss {
|
|
154
|
+
margin-top: 1.1em;
|
|
155
|
+
margin-right: -.2em;
|
|
156
|
+
float: right;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.site .footer .rss img {
|
|
160
|
+
border: 0;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/*****************************************************************************/
|
|
164
|
+
/*
|
|
165
|
+
/* Posts
|
|
166
|
+
/*
|
|
167
|
+
/*****************************************************************************/
|
|
168
|
+
|
|
169
|
+
#post {
|
|
170
|
+
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/* standard */
|
|
174
|
+
|
|
175
|
+
#post pre {
|
|
176
|
+
border: 1px solid #ddd;
|
|
177
|
+
background-color: #eef;
|
|
178
|
+
padding: 0 .4em;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
#post ul,
|
|
182
|
+
#post ol {
|
|
183
|
+
margin-left: 1.25em;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
#post code {
|
|
187
|
+
border: 1px solid #ddd;
|
|
188
|
+
background-color: #eef;
|
|
189
|
+
font-size: 95%;
|
|
190
|
+
padding: 0 .2em;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
#post pre code {
|
|
194
|
+
border: none;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/* terminal */
|
|
198
|
+
|
|
199
|
+
pre.terminal {
|
|
200
|
+
border: 1px solid black;
|
|
201
|
+
background-color: #333;
|
|
202
|
+
color: white;
|
|
203
|
+
padding: 5px;
|
|
204
|
+
overflow: auto;
|
|
205
|
+
word-wrap: break-word;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
pre.terminal code {
|
|
209
|
+
font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace;
|
|
210
|
+
background-color: #333;
|
|
211
|
+
}
|
|
212
|
+
|
data/lib/cijoe/server.rb
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
require 'sinatra/base'
|
|
2
|
+
require 'erb'
|
|
3
|
+
|
|
4
|
+
class CIJoe
|
|
5
|
+
class Server < Sinatra::Base
|
|
6
|
+
dir = File.dirname(File.expand_path(__FILE__))
|
|
7
|
+
|
|
8
|
+
set :views, "#{dir}/views"
|
|
9
|
+
set :public, "#{dir}/public"
|
|
10
|
+
set :static, true
|
|
11
|
+
|
|
12
|
+
get '/?' do
|
|
13
|
+
erb(:template, {}, :joe => @joe)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
post '/?' do
|
|
17
|
+
payload = params[:payload].to_s
|
|
18
|
+
if payload.empty? || payload.include?(@joe.git_branch)
|
|
19
|
+
@joe.build
|
|
20
|
+
end
|
|
21
|
+
redirect request.path
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
user, pass = Config.cijoe.user.to_s, Config.cijoe.pass.to_s
|
|
25
|
+
if user != '' && pass != ''
|
|
26
|
+
use Rack::Auth::Basic do |username, password|
|
|
27
|
+
[ username, password ] == [ user, pass ]
|
|
28
|
+
end
|
|
29
|
+
puts "Using HTTP basic auth"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
helpers do
|
|
33
|
+
include Rack::Utils
|
|
34
|
+
alias_method :h, :escape_html
|
|
35
|
+
|
|
36
|
+
# thanks integrity!
|
|
37
|
+
def ansi_color_codes(string)
|
|
38
|
+
string.gsub("\e[0m", '</span>').
|
|
39
|
+
gsub(/\e\[(\d+)m/, "<span class=\"color\\1\">")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def pretty_time(time)
|
|
43
|
+
time.strftime("%Y-%m-%d %H:%M")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def cijoe_root
|
|
47
|
+
root = request.path
|
|
48
|
+
root = "" if root == "/"
|
|
49
|
+
root
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def initialize(*args)
|
|
54
|
+
super
|
|
55
|
+
check_project
|
|
56
|
+
@joe = CIJoe.new(options.project_path)
|
|
57
|
+
|
|
58
|
+
# example .gitconfig:
|
|
59
|
+
# [cijoe]
|
|
60
|
+
# notifier = CIJoe::Campfire
|
|
61
|
+
#
|
|
62
|
+
# or
|
|
63
|
+
# [cijoe]
|
|
64
|
+
# notifier = CIJoe::Gmail
|
|
65
|
+
#
|
|
66
|
+
# this also gives you the option of running CI Joe without any notifier at all.
|
|
67
|
+
|
|
68
|
+
eval(Config.cijoe.notifier.to_s).activate unless Config.cijoe.notifier.nil?
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def self.start(host, port, project_path)
|
|
72
|
+
set :project_path, project_path
|
|
73
|
+
CIJoe::Server.run! :host => host, :port => port
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def check_project
|
|
77
|
+
if options.project_path.nil? || !File.exists?(File.expand_path(options.project_path))
|
|
78
|
+
puts "Whoops! I need the path to a Git repo."
|
|
79
|
+
puts " $ git clone git@github.com:username/project.git project"
|
|
80
|
+
abort " $ cijoe project"
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<link href="<%= cijoe_root %>/screen.css" media="screen" rel="stylesheet" type="text/css" />
|
|
5
|
+
<title><%= h(joe.project) %>: CI Joe</title>
|
|
6
|
+
</head>
|
|
7
|
+
<body>
|
|
8
|
+
<div class="site">
|
|
9
|
+
<div class="title">
|
|
10
|
+
<a href="<%= cijoe_root %>/">CI Joe</a>
|
|
11
|
+
<span class="extra">because knowing is half the battle</span>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<div id="home">
|
|
15
|
+
<h1><a href="<%= joe.url %>"><%= joe.project %></a></h1>
|
|
16
|
+
<ul class="posts">
|
|
17
|
+
<% if joe.current_build %>
|
|
18
|
+
<li>
|
|
19
|
+
<span class="date"><%= pretty_time(joe.current_build.started_at) if joe.current_build %></span> »
|
|
20
|
+
<% if joe.current_build.sha %>
|
|
21
|
+
Building <a href="<%= joe.url %>/commits/<%= joe.current_build.sha %>"><%= joe.current_build.short_sha %></a> <small>(pid: <%= joe.pid %>)</small>
|
|
22
|
+
<% else %>
|
|
23
|
+
Build starting...
|
|
24
|
+
<% end %>
|
|
25
|
+
</li>
|
|
26
|
+
<% else %>
|
|
27
|
+
<li><form method="POST"><input type="submit" value="Build"/></form></li>
|
|
28
|
+
<% end %>
|
|
29
|
+
|
|
30
|
+
<% if joe.last_build %>
|
|
31
|
+
<li><span class="date"><%= pretty_time(joe.last_build.finished_at) %></span> » Built <a href="<%= joe.url %>/commits/<%= joe.last_build.sha %>"><%= joe.last_build.short_sha %></a> <span class="<%= joe.last_build.status %>">(<%= joe.last_build.status %>)</span></li>
|
|
32
|
+
<% if joe.last_build.failed? %>
|
|
33
|
+
<li><pre class="terminal"><code><%=ansi_color_codes h(joe.last_build.output) %></code></pre></li>
|
|
34
|
+
<% end %>
|
|
35
|
+
<% end %>
|
|
36
|
+
</ul>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div class="footer">
|
|
40
|
+
<div class="contact">
|
|
41
|
+
<p>
|
|
42
|
+
<a href="http://github.com/defunkt/cijoe/tree/master#readme">Documentation</a><br/>
|
|
43
|
+
<a href="http://github.com/defunkt/cijoe">Source</a><br/>
|
|
44
|
+
<a href="http://github.com/defunkt/cijoe/issues">Issues</a><br/>
|
|
45
|
+
<a href="http://twitter.com/defunkt">Twitter</a>
|
|
46
|
+
</p>
|
|
47
|
+
</div>
|
|
48
|
+
<div class="contact">
|
|
49
|
+
<p>
|
|
50
|
+
Designed by <a href="http://tom.preston-werner.com/">Tom Preston-Werner</a><br/>
|
|
51
|
+
Influenced by <a href="http://integrityapp.com/">Integrity</a><br/>
|
|
52
|
+
Built with <a href="http://sinatrarb.com/">Sinatra</a><br/>
|
|
53
|
+
Keep it simple, Sam.
|
|
54
|
+
</p>
|
|
55
|
+
</div>
|
|
56
|
+
<div class="rss">
|
|
57
|
+
<a href="http://github.com/defunkt/cijoe">
|
|
58
|
+
<img src="<%= cijoe_root %>/octocat.png" alt="Octocat!" />
|
|
59
|
+
</a>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</body>
|
|
64
|
+
</html>
|
metadata
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: snake_eyes
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.3
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Chris Wanstrath
|
|
8
|
+
- Giles Bowkett
|
|
9
|
+
- Dr. McNinja
|
|
10
|
+
autorequire:
|
|
11
|
+
bindir: bin
|
|
12
|
+
cert_chain: []
|
|
13
|
+
|
|
14
|
+
date: 2009-10-29 00:00:00 -07:00
|
|
15
|
+
default_executable: snake_eyes
|
|
16
|
+
dependencies:
|
|
17
|
+
- !ruby/object:Gem::Dependency
|
|
18
|
+
name: choice
|
|
19
|
+
type: :runtime
|
|
20
|
+
version_requirement:
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: "0"
|
|
26
|
+
version:
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: sinatra
|
|
29
|
+
type: :runtime
|
|
30
|
+
version_requirement:
|
|
31
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
32
|
+
requirements:
|
|
33
|
+
- - ">="
|
|
34
|
+
- !ruby/object:Gem::Version
|
|
35
|
+
version: "0"
|
|
36
|
+
version:
|
|
37
|
+
- !ruby/object:Gem::Dependency
|
|
38
|
+
name: open4
|
|
39
|
+
type: :runtime
|
|
40
|
+
version_requirement:
|
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
42
|
+
requirements:
|
|
43
|
+
- - ">="
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: "0"
|
|
46
|
+
version:
|
|
47
|
+
description: Snake Eyes is a fork of CI Joe, a simple Continuous Integration server.
|
|
48
|
+
email: gilesb@gmail.com
|
|
49
|
+
executables:
|
|
50
|
+
- snake_eyes
|
|
51
|
+
extensions: []
|
|
52
|
+
|
|
53
|
+
extra_rdoc_files:
|
|
54
|
+
- LICENSE
|
|
55
|
+
- README.markdown
|
|
56
|
+
files:
|
|
57
|
+
- LICENSE
|
|
58
|
+
- README.markdown
|
|
59
|
+
- Rakefile
|
|
60
|
+
- bin/snake_eyes
|
|
61
|
+
- deps.rip
|
|
62
|
+
- examples/cijoed
|
|
63
|
+
- lib/cijoe.rb
|
|
64
|
+
- lib/cijoe/build.rb
|
|
65
|
+
- lib/cijoe/campfire.rb
|
|
66
|
+
- lib/cijoe/commit.rb
|
|
67
|
+
- lib/cijoe/config.rb
|
|
68
|
+
- lib/cijoe/gmail.rb
|
|
69
|
+
- lib/cijoe/public/octocat.png
|
|
70
|
+
- lib/cijoe/public/screen.css
|
|
71
|
+
- lib/cijoe/server.rb
|
|
72
|
+
- lib/cijoe/version.rb
|
|
73
|
+
- lib/cijoe/views/template.erb
|
|
74
|
+
has_rdoc: true
|
|
75
|
+
homepage: http://github.com/gilesbowkett/snake_eyes
|
|
76
|
+
licenses: []
|
|
77
|
+
|
|
78
|
+
post_install_message:
|
|
79
|
+
rdoc_options:
|
|
80
|
+
- --charset=UTF-8
|
|
81
|
+
require_paths:
|
|
82
|
+
- lib
|
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
84
|
+
requirements:
|
|
85
|
+
- - ">="
|
|
86
|
+
- !ruby/object:Gem::Version
|
|
87
|
+
version: "0"
|
|
88
|
+
version:
|
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
90
|
+
requirements:
|
|
91
|
+
- - ">="
|
|
92
|
+
- !ruby/object:Gem::Version
|
|
93
|
+
version: "0"
|
|
94
|
+
version:
|
|
95
|
+
requirements: []
|
|
96
|
+
|
|
97
|
+
rubyforge_project:
|
|
98
|
+
rubygems_version: 1.3.5
|
|
99
|
+
signing_key:
|
|
100
|
+
specification_version: 3
|
|
101
|
+
summary: Snake Eyes is a fork of CI Joe, a simple Continuous Integration server.
|
|
102
|
+
test_files: []
|
|
103
|
+
|