ciquantum 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ .DS_Store
2
+ *.tmproj
3
+ tmp/**/*
4
+ tmp/*
5
+ *.gem
6
+ *.rbc
7
+ .bundle
8
+ .config
9
+ .yardoc
10
+ Gemfile.lock
11
+ InstalledFiles
12
+ _yardoc
13
+ coverage
14
+ doc/
15
+ lib/bundler/man
16
+ pkg
17
+ rdoc
18
+ spec/reports
19
+ test/tmp
20
+ test/version_tmp
21
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ciquantum.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Anton Sidelnikov, Social Quantum
2
+
3
+ Copyright (c) 2009 Chris Wanstrath
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,18 @@
1
+ CI Quantum
2
+ ======
3
+
4
+ CI Quantum is a [Continuous
5
+ Integration](http://en.wikipedia.org/wiki/Continuous_integration),
6
+ based on CIJoe gem.
7
+
8
+
9
+ Quickstart
10
+ ----------
11
+
12
+ RubyGems:
13
+
14
+ $ gem install ciquantum
15
+ $ git clone git://github.com/you/yourrepo.git
16
+ $ ciquantum yourrepo
17
+
18
+ Anvanced details see on [original repo](https://github.com/defunkt/cijoe)
data/Rakefile ADDED
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rake/testtask'
5
+ Rake::TestTask.new(:test) do |test|
6
+ test.libs << 'lib' << 'test'
7
+ test.pattern = 'test/**/test_*.rb'
8
+ test.verbose = true
9
+ end
10
+
11
+ begin
12
+ require 'rcov/rcovtask'
13
+ Rcov::RcovTask.new do |test|
14
+ test.libs << 'test'
15
+ test.pattern = 'test/**/test_*.rb'
16
+ test.verbose = true
17
+ end
18
+ rescue LoadError
19
+ task :rcov do
20
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
21
+ end
22
+ end
23
+
24
+ task :default => :test
25
+
26
+ require 'rdoc/task'
27
+ Rake::RDocTask.new do |rdoc|
28
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
29
+
30
+ rdoc.rdoc_dir = 'rdoc'
31
+ rdoc.title = "someproject #{version}"
32
+ rdoc.rdoc_files.include('README*')
33
+ rdoc.rdoc_files.include('lib/**/*.rb')
34
+ end
data/bin/ciquantum ADDED
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
3
+
4
+ require 'choice'
5
+ require 'ciquantum'
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#{CIQuantum::Version}"
43
+ exit
44
+ end
45
+ end
46
+ end
47
+
48
+ options = Choice.choices
49
+
50
+ CIQuantum::Server.start(options[:host], options[:port], File.expand_path(Choice.rest[0].to_s))
data/ciquantum.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/ciquantum/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+
6
+ gem.name = "ciquantum"
7
+ gem.date = Time.now.strftime('%Y-%m-%d')
8
+ gem.summary = "Continius Integration server for internal usage at Social Quantum"
9
+ gem.description = "Ciquantum is a sinatra-based continuous integration server, based on cijoe gem"
10
+ gem.homepage = "https://github.com/meredian/ciquantum"
11
+ gem.email = "ndmeredian@gmail.com"
12
+ gem.authors = [ "Anton Sidelnikov", "Chris Wanstrath", "Josh Owens" ]
13
+ gem.has_rdoc = false
14
+
15
+ gem.executables = %w( ciquantum )
16
+ gem.files = `git ls-files`.split($\)
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+ gem.version = CIQuantum::VERSION
20
+
21
+ gem.add_runtime_dependency 'bundler'
22
+ gem.add_runtime_dependency 'rake'
23
+ gem.add_runtime_dependency 'json'
24
+ gem.add_runtime_dependency 'tinder', '>= 1.4.0'
25
+ gem.add_runtime_dependency 'sinatra'
26
+ gem.add_runtime_dependency 'choice'
27
+ gem.add_development_dependency 'rack-test'
28
+ gem.add_development_dependency 'mocha'
29
+
30
+ end
@@ -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 Quantum rackup config. Drop a ciquantum.ru file
2
+ # in your projects direct
3
+ require 'ciquantum'
4
+
5
+ # setup middleware
6
+ use Rack::CommonLogger
7
+
8
+ # configure quantum
9
+ CIQuantum::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 CIQuantum::Server
data/examples/cijoed ADDED
@@ -0,0 +1,53 @@
1
+ #!/bin/sh
2
+ ### BEGIN INIT INFO
3
+ # Provides: ciquantum
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 CIQuantum CI server.
9
+ ### END INIT INFO
10
+
11
+ . /lib/lsb/init-functions
12
+
13
+ REPO=/path/to/your/git/repository
14
+ PORT=4567
15
+
16
+ NAME=ciquantum
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 ciquantum" "ciquantum"
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 ciquantum" "ciquantum"
35
+ start-stop-daemon --stop --pidfile $PIDFILE --quiet --retry 10
36
+ log_end_msg $?
37
+ ;;
38
+ restart)
39
+ log_daemon_msg "Restarting ciquantum" "ciquantum"
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/ciquantum (start|stop|restart)"
49
+ exit 2
50
+ ;;
51
+ esac
52
+
53
+ exit 0
data/lib/ciquantum.rb ADDED
@@ -0,0 +1,227 @@
1
+
2
+ require 'ciquantum/version'
3
+ require 'ciquantum/config'
4
+ require 'ciquantum/adapter'
5
+ require 'ciquantum/commit'
6
+ require 'ciquantum/build'
7
+ require 'ciquantum/server'
8
+ require 'ciquantum/queue'
9
+
10
+
11
+ class CIQuantum
12
+ attr_reader :user
13
+ attr_reader :project
14
+ attr_reader :url
15
+ attr_reader :current_build
16
+ attr_reader :last_build
17
+ attr_reader :campfire
18
+ attr_reader :projectname
19
+ attr_reader :coverage_path
20
+
21
+ def initialize(project_path)
22
+ @project_path = File.expand_path(project_path)
23
+
24
+ @user, @project = git_user_and_project
25
+ @url = project_url
26
+ @projectname = repo_config.projectname.to_s
27
+ @coverage_path = repo_config.coverage_path.to_s
28
+
29
+ @last_build = nil
30
+ @current_build = nil
31
+ @queue = Queue.new(!repo_config.buildqueue.to_s.empty?, true)
32
+
33
+ trap("INT") { stop }
34
+ end
35
+
36
+ # is a build running?
37
+ def building?
38
+ !!@current_build
39
+ end
40
+
41
+ # the pid of the running child process
42
+ def pid
43
+ building? and current_build.pid
44
+ end
45
+
46
+ # kill the child and exit
47
+ def stop
48
+ Process.kill(9, pid) if pid
49
+ exit!
50
+ end
51
+
52
+ # build callbacks
53
+ def build_failed(output, error)
54
+ finish_build :failed, "#{error}\n\n#{output}"
55
+ run_hook "build-failed"
56
+ end
57
+
58
+ def build_worked(output)
59
+ finish_build :worked, output
60
+ run_hook "build-worked"
61
+ end
62
+
63
+ def finish_build(status, output)
64
+ @current_build.finished_at = Time.now
65
+ @current_build.status = status
66
+ @current_build.output = output
67
+ @last_build = @current_build
68
+
69
+ @current_build = nil
70
+ write_build 'current', @current_build
71
+ write_build 'last', @last_build
72
+
73
+ build(@queue.next_branch_to_build) if @queue.waiting?
74
+ end
75
+
76
+ # run the build but make sure only one is running
77
+ # at a time (if new one comes in we will park it)
78
+ def build(branch=nil)
79
+ if building?
80
+ @queue.append_unless_already_exists(branch)
81
+ # leave anyway because a current build runs
82
+ return
83
+ end
84
+ @current_build = Build.new(@project_path, @user, @project)
85
+ write_build 'current', @current_build
86
+ Thread.new { build!(branch) }
87
+ end
88
+
89
+ def open_pipe(cmd)
90
+ read, write = IO.pipe
91
+
92
+ pid = fork do
93
+ read.close
94
+ $stdout.reopen write
95
+ exec cmd
96
+ end
97
+
98
+ write.close
99
+
100
+ yield read, pid
101
+ end
102
+
103
+ # update git then run the build
104
+ def build!(branch=nil)
105
+ @git_branch = branch
106
+ build = @current_build
107
+ output = ''
108
+ git_update
109
+ build.sha = git_sha
110
+ build.branch = git_branch
111
+ write_build 'current', build
112
+
113
+ open_pipe("cd #{@project_path} && #{runner_command} 2>&1") do |pipe, pid|
114
+ puts "#{Time.now.to_i}: Building #{build.branch} at #{build.short_sha}: pid=#{pid}"
115
+
116
+ build.pid = pid
117
+ write_build 'current', build
118
+ output = pipe.read
119
+ end
120
+
121
+ Process.waitpid(build.pid, 1)
122
+ status = $?.exitstatus.to_i
123
+ @current_build = build
124
+ puts "#{Time.now.to_i}: Built #{build.short_sha}: status=#{status}"
125
+
126
+ status == 0 ? build_worked(output) : build_failed('', output)
127
+ rescue Object => e
128
+ puts "Exception building: #{e.message} (#{e.class})"
129
+ build_failed('', e.to_s)
130
+ end
131
+
132
+ # shellin' out
133
+ def runner_command
134
+ runner = repo_config.runner.to_s
135
+ runner == '' ? "rake -s test:units" : runner
136
+ end
137
+
138
+ def git_sha
139
+ `cd #{@project_path} && git rev-parse origin/#{git_branch}`.chomp
140
+ end
141
+
142
+ def git_update
143
+ `cd #{@project_path} && git fetch origin && git reset --hard origin/#{git_branch}`
144
+ run_hook "after-reset"
145
+ end
146
+
147
+ def git_user_and_project
148
+ Adapter.default_adapter.git_user_and_project
149
+ end
150
+
151
+ def project_url
152
+ Adapter.default_adapter.project_url
153
+ end
154
+
155
+ def git_branches
156
+ branches = `cd #{@project_path} && git branch -r`.split("\n")
157
+ branches.collect{|s| s =~ /(?:\s|^)origin\/(?!HEAD)(\w+)(?:\s|$)/ ? $1.strip : nil}.compact
158
+ end
159
+
160
+ def git_branch
161
+ return @git_branch if @git_branch
162
+ branch = repo_config.branch.to_s
163
+ @git_branch = branch == '' ? "master" : branch
164
+ end
165
+
166
+ # massage our repo
167
+ def run_hook(hook)
168
+ if File.exists?(file=path_in_project(".git/hooks/#{hook}")) && File.executable?(file)
169
+ data =
170
+ if @last_build && @last_build.commit
171
+ {
172
+ "MESSAGE" => @last_build.commit.message,
173
+ "AUTHOR" => @last_build.commit.author,
174
+ "SHA" => @last_build.commit.sha,
175
+ "OUTPUT" => @last_build.env_output
176
+ }
177
+ else
178
+ {}
179
+ end
180
+
181
+ orig_ENV = ENV.to_hash
182
+ ENV.clear
183
+ data.each{ |k, v| ENV[k] = v }
184
+ output = `cd #{@project_path} && #{file}`
185
+
186
+ ENV.clear
187
+ orig_ENV.to_hash.each{ |k, v| ENV[k] = v}
188
+ output
189
+ end
190
+ end
191
+
192
+ # restore current / last build state from disk.
193
+ def restore
194
+ @last_build = read_build('last')
195
+ @current_build = read_build('current')
196
+
197
+ Process.kill(0, @current_build.pid) if @current_build && @current_build.pid
198
+ rescue Errno::ESRCH
199
+ # build pid isn't running anymore. assume previous
200
+ # server died and reset.
201
+ @current_build = nil
202
+ end
203
+
204
+ def path_in_project(path)
205
+ File.join(@project_path, path)
206
+ end
207
+
208
+ # write build info for build to file.
209
+ def write_build(name, build)
210
+ filename = path_in_project(".git/builds/#{name}")
211
+ Dir.mkdir path_in_project('.git/builds') unless File.directory?(path_in_project('.git/builds'))
212
+ if build
213
+ build.dump filename
214
+ elsif File.exist?(filename)
215
+ File.unlink filename
216
+ end
217
+ end
218
+
219
+ def repo_config
220
+ Config.ciquantum(@project_path)
221
+ end
222
+
223
+ # load build info from file.
224
+ def read_build(name)
225
+ Build.load(path_in_project(".git/builds/#{name}"), @project_path)
226
+ end
227
+ end