ssp-cijoe 0.4.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.
data/.gitignore ADDED
@@ -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.
data/README.markdown ADDED
@@ -0,0 +1,169 @@
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
+ RubyGems:
16
+
17
+ $ gem install cijoe
18
+ $ git clone git://github.com/you/yourrepo.git
19
+ $ cijoe yourrepo
20
+
21
+ Boom. Navigate to <http://localhost:4567> to see Joe in action.
22
+ Check `cijoe -h` for other options.
23
+
24
+ Basically you need to run `cijoe` and hand it the path to a git
25
+ repo. Make sure this isn't a shared repo: Joe needs to own it.
26
+
27
+ Joe looks for various git config settings in the repo you hand it. For
28
+ instance, you can tell Joe what command to run by setting
29
+ `cijoe.runner`:
30
+
31
+ $ git config --add cijoe.runner "rake -s test:units"
32
+
33
+ Joe doesn't care about Ruby, Python, or whatever. As long as the
34
+ runner returns a non-zero exit status on fail and a zero on success,
35
+ everyone is happy.
36
+
37
+ Need to do some massaging of your repo before the tests run, like
38
+ maybe swapping in a new database.yml? No problem - Joe will try to
39
+ run `.git/hooks/after-reset` if it exists before each build phase.
40
+ Do it in there. Just make sure it's executable.
41
+
42
+ Want to notify IRC or email on test pass or failure? Joe will run
43
+ `.git/hooks/build-failed` or `.git/hooks/build-worked` if they exist
44
+ and are executable on build pass / fail. They're just shell scripts -
45
+ put whatever you want in there.
46
+
47
+ Tip: your repo's `HEAD` will point to the commit used to run the
48
+ build. Pull any metadata you want out of that scro.
49
+
50
+
51
+ Other Branches
52
+ ----------------------
53
+
54
+ Want joe to run against a branch other than `master`? No problem:
55
+
56
+ $ git config --add cijoe.branch deploy
57
+
58
+
59
+ Concurrent Push's - a kind of "queueing"
60
+ ----------------------------------------
61
+
62
+ Joe runs just one build at the time. If you expect concurrent push's
63
+ to your repo and want joe to build each in a kind of queue, just set:
64
+
65
+ $ git config --add cijoe.buildallfile tmp/cijoe.txt
66
+
67
+ Joe will save requests while another build runs. If more than one push
68
+ hits joe, he just picks the last after finishing the prior.
69
+
70
+
71
+ Campfire
72
+ -------------
73
+
74
+ Campfire notification is included, because it's what we use. Want Joe
75
+ notify your Campfire? Put this in your repo's `.git/config`:
76
+
77
+ [campfire]
78
+ user = your@campfire.email
79
+ pass = passw0rd
80
+ subdomain = whatever
81
+ room = Awesomeness
82
+ ssl = false
83
+
84
+ Or do it the old fashion way:
85
+
86
+ $ cd yourrepo
87
+ $ git config --add campfire.user chris@ozmm.org
88
+ $ git config --add campfire.subdomain github
89
+ etc.
90
+
91
+
92
+ Checkin' Status
93
+ ----------------------
94
+
95
+ Want to see how your build's doing without any of this fancy UI crap?
96
+ Ping Joe for the lowdown:
97
+
98
+ curl http://localhost:4567/ping
99
+
100
+ Joe will return `200 OK` if all is quiet on the Western Front. If
101
+ Joe's busy building or your last build failed, you'll get `412
102
+ PRECONDITION FAILED`.
103
+
104
+
105
+ Multiple Projects
106
+ ------------------------
107
+
108
+ Want CI for multiple projects? Just start multiple instances of Joe!
109
+ He can run on any port - try `cijoe -h` for more options.
110
+
111
+ If you're using Passenger, see [this blog post](http://chrismdp.github.com/2010/03/multiple-ci-joes-with-rack-and-passenger/).
112
+
113
+
114
+ HTTP Auth
115
+ ----------------
116
+
117
+ Worried about people triggering your builds? Setup HTTP auth:
118
+
119
+ $ git config --add cijoe.user chris
120
+ $ git config --add cijoe.pass secret
121
+
122
+
123
+ GitHub Integration
124
+ --------------------------
125
+
126
+ Any POST to Joe will trigger a build. If you are hiding Joe behind
127
+ HTTP auth, that's okay - GitHub knows how to authenticate properly.
128
+
129
+ ![Post-Receive URL](http://img.skitch.com/20090806-d2bxrk733gqu8m11tf4kyir5d8.png)
130
+
131
+ You can find the Post-Receive option under the 'Service Hooks' subtab
132
+ of your project's "Admin" tab.
133
+
134
+
135
+ Daemonize
136
+ ----------------
137
+
138
+ Want to run Joe as a daemon? Use `nohup`:
139
+
140
+ $ nohup cijoe -p 4444 repo &
141
+
142
+
143
+ Other CI Servers
144
+ ------------------------
145
+
146
+ Need more features? More notifiers? Check out one of these bad boys:
147
+
148
+ * [Cerberus](http://cerberus.rubyforge.org/)
149
+ * [Integrity](http://integrityapp.com/)
150
+ * [CruiseControl.rb](http://cruisecontrolrb.thoughtworks.com/)
151
+ * [BuildBot](http://buildbot.net/trac)
152
+ * [Signal](http://www.github.com/dcrec1/signal)
153
+
154
+
155
+ Screenshots
156
+ ------------------
157
+
158
+ ![Building](http://img.skitch.com/20090806-ryw34ksi5ixnrdwxcptqy28iy7.png)
159
+
160
+ ![Built](http://img.skitch.com/20090806-f7j3r65yecaq13hdcxqwtc5krd.)
161
+
162
+
163
+ Questions? Concerns?
164
+ ---------------------------------
165
+
166
+ [Issues](http://github.com/defunkt/cijoe/issues) or [the mailing list](http://groups.google.com/group/cijoe).
167
+
168
+
169
+ ( Chris Wanstrath :: chris@ozmm.org )
data/Rakefile ADDED
@@ -0,0 +1,67 @@
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 = "ssp-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", "András Tarsoly", "Secret Sauce Partners, Inc."]
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
+ rescue LoadError
23
+ puts "Jeweler not available."
24
+ puts "Install it with: gem install jeweler"
25
+ end
26
+
27
+ desc "Publish a RubyGem"
28
+ task :publish => :gem do
29
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/lib'
30
+ require 'cijoe/version'
31
+
32
+ sh "gem push pkg/ssp-cijoe-#{CIJoe::Version}.gem"
33
+ end
34
+
35
+ require 'rake/testtask'
36
+ Rake::TestTask.new(:test) do |test|
37
+ test.libs << 'lib' << 'test'
38
+ test.pattern = 'test/**/test_*.rb'
39
+ test.verbose = true
40
+ end
41
+
42
+ begin
43
+ require 'rcov/rcovtask'
44
+ Rcov::RcovTask.new do |test|
45
+ test.libs << 'test'
46
+ test.pattern = 'test/**/test_*.rb'
47
+ test.verbose = true
48
+ end
49
+ rescue LoadError
50
+ task :rcov do
51
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
52
+ end
53
+ end
54
+
55
+ task :test => :check_dependencies
56
+
57
+ task :default => :test
58
+
59
+ require 'rake/rdoctask'
60
+ Rake::RDocTask.new do |rdoc|
61
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
62
+
63
+ rdoc.rdoc_dir = 'rdoc'
64
+ rdoc.title = "someproject #{version}"
65
+ rdoc.rdoc_files.include('README*')
66
+ rdoc.rdoc_files.include('lib/**/*.rb')
67
+ end
data/bin/cijoe ADDED
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
3
+
4
+ require 'choice'
5
+
6
+ Choice.options do
7
+ banner "Usage: #{File.basename(__FILE__)} [-hpv] path_to_git_repo"
8
+ header ''
9
+ header 'Server options:'
10
+
11
+ option :host do
12
+ d = "0.0.0.0"
13
+ short '-h'
14
+ long '--host=HOST'
15
+ desc "The hostname or ip of the host to bind to (default #{d})"
16
+ default d
17
+ end
18
+
19
+ option :port do
20
+ d = 4567
21
+ short '-p'
22
+ long '--port=PORT'
23
+ desc "The port to listen on (default #{d})"
24
+ cast Integer
25
+ default d
26
+ end
27
+
28
+ separator ''
29
+ separator 'Common options: '
30
+
31
+ option :help do
32
+ long '--help'
33
+ desc 'Show this message'
34
+ end
35
+
36
+ option :version do
37
+ short '-v'
38
+ long '--version'
39
+ desc 'Show version'
40
+ action do
41
+ puts "#{File.basename(__FILE__)} v#{CIJoe::Version}"
42
+ exit
43
+ end
44
+ end
45
+ end
46
+
47
+ options = Choice.choices
48
+ $project_path = File.expand_path(Choice.rest[0])
49
+
50
+ require 'cijoe'
51
+
52
+ CIJoe::Server.start(options[:host], options[:port], $project_path)
data/deps.rip ADDED
@@ -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,22 @@
1
+ #!/bin/sh
2
+ #
3
+ # Put this file to $PROJECT/.git/hooks/ for email notifications.
4
+ #
5
+ # You should have mail command provided by mailutils package on Debian
6
+ # based systems.
7
+ #
8
+ # sudo apt-get install mailutils
9
+ #
10
+ # You should have mail server running
11
+ #
12
+ # Do not forget: chmod +x build-failed
13
+ #
14
+ echo "
15
+ Visit http://ci.example.org/ for details
16
+
17
+ Author: $AUTHOR
18
+ Message:
19
+ $MESSAGE
20
+
21
+ $OUTPUT
22
+ " | mail -s "[example.org] BUILD FAILED $SHA" --to first@gmail.com second@gmail.com
@@ -0,0 +1,22 @@
1
+ #!/bin/sh
2
+ #
3
+ # Put this file to $PROJECT/.git/hooks/ for email notifications.
4
+ #
5
+ # You should have mail command provided by mailutils package on Debian
6
+ # based systems.
7
+ #
8
+ # sudo apt-get install mailutils
9
+ #
10
+ # You should have mail server running
11
+ #
12
+ # Do not forget: chmod +x build-worked
13
+ #
14
+ echo "
15
+ Visit http://ci.example.org/ for details
16
+
17
+ Author: $AUTHOR
18
+ Message:
19
+ $MESSAGE
20
+
21
+ " | mail -s "[example.org] build OK" --to first@gmail.com second@gmail.com
22
+
data/examples/cijoe.ru ADDED
@@ -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
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
@@ -0,0 +1,74 @@
1
+ require 'yaml'
2
+
3
+ class CIJoe
4
+ class Build < Struct.new(: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 full_id
41
+ "#{short_sha}.#{finished_at.to_i.to_s}"
42
+ end
43
+
44
+ def metrics_url
45
+ "#{Config.cijoe.metricshost}/#{full_id}/metrics/index.html"
46
+ end
47
+
48
+ def coverage_url
49
+ "#{Config.cijoe.metricshost}/#{full_id}/coverage/index.html"
50
+ end
51
+
52
+ def clean_output
53
+ output.gsub(/\e\[.+?m/, '').strip
54
+ end
55
+
56
+ def commit
57
+ return if sha.nil?
58
+ @commit ||= Commit.new(sha, user, project)
59
+ end
60
+
61
+ def dump(file)
62
+ config = [user, project, started_at, finished_at, sha, status, output, pid]
63
+ data = YAML.dump(config)
64
+ File.open(file, 'wb') { |io| io.write(data) }
65
+ end
66
+
67
+ def self.load(file)
68
+ if File.exist?(file)
69
+ config = YAML.load(File.read(file))
70
+ new *config
71
+ end
72
+ end
73
+ end
74
+ 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
@@ -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.grep(/Author:/).first.split(':', 2)[-1]
9
+ end
10
+
11
+ def committed_at
12
+ raw_commit_lines.grep(/Date:/).first.split(':', 2)[-1]
13
+ end
14
+
15
+ def message
16
+ raw_commit.split("\n\n", 3)[1].to_s.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
@@ -0,0 +1,43 @@
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
+ @project_path = $project_path || File.join(File.dirname(__FILE__), '../../')
11
+ end
12
+
13
+ def method_missing(command, *args)
14
+ Config.new(command, 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}, Error: #{result}"
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
@@ -0,0 +1,16 @@
1
+ module Sinatra::Partials
2
+ def partial(template, *args)
3
+ template_array = template.to_s.split('/')
4
+ template = template_array[0..-2].join('/') + "/_#{template_array[-1]}"
5
+ options = args.last.is_a?(Hash) ? args.pop : {}
6
+ options.merge!(:layout => false)
7
+ if collection = options.delete(:collection) then
8
+ collection.inject([]) do |buffer, member|
9
+ buffer << erb(:"#{template}", options.merge(:layout =>
10
+ false, :locals => {template_array[-1].to_sym => member}))
11
+ end.join("\n")
12
+ else
13
+ erb(:"#{template}", options)
14
+ end
15
+ end
16
+ end
Binary file
Binary file