ciquantum 0.0.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,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