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.
@@ -0,0 +1,11 @@
1
+ require 'ciquantum/unfuddle_adapter'
2
+
3
+ class CIQuantum
4
+ class Adapter
5
+
6
+ def self.default_adapter
7
+ CIQuantum::UnfuddleAdapter
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,67 @@
1
+ require 'yaml'
2
+
3
+ class CIQuantum
4
+ class Build < Struct.new(:project_path, :user, :project, :started_at, :finished_at, :sha, :status, :output, :pid, :branch)
5
+ def initialize(*args)
6
+ super
7
+ self.started_at ||= Time.now
8
+ end
9
+
10
+ def status
11
+ return super if started_at && finished_at
12
+ :building
13
+ end
14
+
15
+ def failed?
16
+ status == :failed
17
+ end
18
+
19
+ def worked?
20
+ status == :worked
21
+ end
22
+
23
+ def building?
24
+ status == :building
25
+ end
26
+
27
+ def duration
28
+ return if building?
29
+ finished_at - started_at
30
+ end
31
+
32
+ def short_sha
33
+ if sha
34
+ sha[0,7]
35
+ else
36
+ "<unknown>"
37
+ end
38
+ end
39
+
40
+ def clean_output
41
+ output.gsub(/\e\[.+?m/, '').strip
42
+ end
43
+
44
+ def env_output
45
+ out = clean_output
46
+ out.size > 100_000 ? out[-100_000,100_000] : out
47
+ end
48
+
49
+ def commit
50
+ return if sha.nil?
51
+ @commit ||= Commit.new(sha, user, project, project_path)
52
+ end
53
+
54
+ def dump(file)
55
+ config = [user, project, started_at, finished_at, sha, status, output, pid, branch]
56
+ data = YAML.dump(config)
57
+ File.open(file, 'wb') { |io| io.write(data) }
58
+ end
59
+
60
+ def self.load(file, project_path)
61
+ if File.exist?(file)
62
+ config = YAML.load(File.read(file)).unshift(project_path)
63
+ new *config
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,28 @@
1
+ class CIQuantum
2
+ class Commit < Struct.new(:sha, :user, :project, :project_path)
3
+ def url
4
+ Adapter::default_adapter.commit_url self
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 ||= `cd #{project_path} && git show #{sha}`.chomp
21
+ end
22
+
23
+ def raw_commit_lines
24
+ @raw_commit_lines ||= raw_commit.split("\n")
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,43 @@
1
+ class CIQuantum
2
+ class Config
3
+ def self.method_missing(command, *args)
4
+ new(command, *args)
5
+ end
6
+
7
+ def initialize(command, project_path = nil, parent = nil)
8
+ @command = command
9
+ @parent = parent
10
+ @project_path = project_path || File.join(File.dirname(__FILE__), '../../')
11
+ end
12
+
13
+ def method_missing(command, *args)
14
+ Config.new(command, @project_path, self)
15
+ end
16
+
17
+ def to_s
18
+ git_command = "cd #{@project_path} && git config #{config_string}"
19
+ result = `#{git_command} 2>&1`.chomp
20
+ process_status = $?
21
+
22
+ if successful_command?(process_status) || config_command_with_empty_value?(result,process_status)
23
+ return result
24
+ else
25
+ return ""
26
+ end
27
+ end
28
+
29
+ def config_string
30
+ @parent ? "#{@parent.config_string}.#{@command}" : @command
31
+ end
32
+
33
+ private
34
+
35
+ def successful_command?(process_status)
36
+ process_status.exitstatus.to_i == 0
37
+ end
38
+
39
+ def config_command_with_empty_value?(result, process_status)
40
+ process_status.exitstatus.to_i == 1 && result.empty?
41
+ end
42
+ end
43
+ end
Binary file
Binary file
@@ -0,0 +1,213 @@
1
+ /*****************************************************************************/
2
+ /*
3
+ /* Common
4
+ /*
5
+ /*****************************************************************************/
6
+
7
+ /* Global Reset */
8
+
9
+ * {
10
+ margin: 0;
11
+ padding: 0;
12
+ }
13
+
14
+ html, body {
15
+ height: 100%;
16
+ }
17
+
18
+ body {
19
+ background-color: white;
20
+ font: 13.34px helvetica, arial, clean, sans-serif;
21
+ *font-size: small;
22
+ text-align: center;
23
+ }
24
+
25
+ h1, h2, h3, h4, h5, h6 {
26
+ font-size: 100%;
27
+ }
28
+
29
+ h1 {
30
+ margin-bottom: 1em;
31
+ }
32
+
33
+ h1 a {
34
+ text-decoration: none;
35
+ color: #000;
36
+ }
37
+
38
+ .failed, .color31 {
39
+ color: red !important;
40
+ }
41
+
42
+ .worked, .color32 {
43
+ color: green !important;
44
+ }
45
+
46
+ .errored, .color33 {
47
+ color: yellow !important;
48
+ }
49
+
50
+ p {
51
+ margin: 1em 0;
52
+ }
53
+
54
+ a {
55
+ color: #00a;
56
+ }
57
+
58
+ a:hover {
59
+ color: black;
60
+ }
61
+
62
+ a:visited {
63
+ color: #a0a;
64
+ }
65
+
66
+ table {
67
+ font-size: inherit;
68
+ font: 100%;
69
+ }
70
+
71
+ /*****************************************************************************/
72
+ /*
73
+ /* Home
74
+ /*
75
+ /*****************************************************************************/
76
+
77
+ ul.posts {
78
+ list-style-type: none;
79
+ margin-bottom: 2em;
80
+ }
81
+
82
+ ul.posts li {
83
+ line-height: 1.75em;
84
+ }
85
+
86
+ ul.posts .date,
87
+ ul.posts .duration {
88
+ color: #aaa;
89
+ font-family: Monaco, "Courier New", monospace;
90
+ font-size: 80%;
91
+ }
92
+
93
+ /*****************************************************************************/
94
+ /*
95
+ /* Site
96
+ /*
97
+ /*****************************************************************************/
98
+
99
+ .site {
100
+ font-size: 110%;
101
+ text-align: justify;
102
+ width: 80%;
103
+ margin: 3em auto 2em auto;
104
+ line-height: 1.5em;
105
+ }
106
+
107
+ .title {
108
+ color: #a00;
109
+ font-weight: bold;
110
+ margin-bottom: 2em;
111
+ }
112
+
113
+ .site .title a {
114
+ color: #a00;
115
+ text-decoration: none;
116
+ }
117
+
118
+ .site .title a:hover {
119
+ color: black;
120
+ }
121
+
122
+ .site .title .extra {
123
+ color: #aaa;
124
+ text-decoration: none;
125
+ margin-left: 1em;
126
+ font-size: 0.9em;
127
+ }
128
+
129
+ .site .title a.extra:hover {
130
+ color: black;
131
+ }
132
+
133
+ .site .meta {
134
+ color: #aaa;
135
+ }
136
+
137
+ .site .footer {
138
+ font-size: 80%;
139
+ color: #666;
140
+ border-top: 4px solid #eee;
141
+ margin-top: 2em;
142
+ overflow: hidden;
143
+ }
144
+
145
+ .site .footer .contact {
146
+ float: left;
147
+ margin-right: 3em;
148
+ }
149
+
150
+ .site .footer .contact a {
151
+ color: #8085C1;
152
+ }
153
+
154
+ .site .footer .rss {
155
+ margin-top: 1.1em;
156
+ margin-right: -.2em;
157
+ float: right;
158
+ }
159
+
160
+ .site .footer .rss img {
161
+ border: 0;
162
+ }
163
+
164
+ /*****************************************************************************/
165
+ /*
166
+ /* Posts
167
+ /*
168
+ /*****************************************************************************/
169
+
170
+ #post {
171
+
172
+ }
173
+
174
+ /* standard */
175
+
176
+ #post pre {
177
+ border: 1px solid #ddd;
178
+ background-color: #eef;
179
+ padding: 0 .4em;
180
+ }
181
+
182
+ #post ul,
183
+ #post ol {
184
+ margin-left: 1.25em;
185
+ }
186
+
187
+ #post code {
188
+ border: 1px solid #ddd;
189
+ background-color: #eef;
190
+ font-size: 95%;
191
+ padding: 0 .2em;
192
+ }
193
+
194
+ #post pre code {
195
+ border: none;
196
+ }
197
+
198
+ /* terminal */
199
+
200
+ pre.terminal {
201
+ border: 1px solid black;
202
+ background-color: #333;
203
+ color: white;
204
+ padding: 5px;
205
+ overflow: auto;
206
+ word-wrap: break-word;
207
+ }
208
+
209
+ pre.terminal code {
210
+ font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace;
211
+ background-color: #333;
212
+ }
213
+
@@ -0,0 +1,55 @@
1
+ class CIQuantum
2
+ # An in memory queue used for maintaining an order list of requested
3
+ # builds.
4
+ class Queue
5
+ # enabled - determines whether builds should be queued or not.
6
+ def initialize(enabled, verbose=false)
7
+ @enabled = enabled
8
+ @verbose = verbose
9
+ @queue = []
10
+
11
+ log("Build queueing enabled") if enabled
12
+ end
13
+
14
+ # Public: Appends a branch to be built, unless it already exists
15
+ # within the queue.
16
+ #
17
+ # branch - the name of the branch to build or nil if the default
18
+ # should be built.
19
+ #
20
+ # Returns nothing
21
+ def append_unless_already_exists(branch)
22
+ return unless enabled?
23
+ unless @queue.include? branch
24
+ @queue << branch
25
+ log "#{Time.now.to_i}: Queueing #{branch}"
26
+ end
27
+ end
28
+
29
+ # Returns a String of the next branch to build
30
+ def next_branch_to_build
31
+ branch = @queue.shift
32
+ log "#{Time.now.to_i}: De-queueing #{branch}"
33
+ branch
34
+ end
35
+
36
+ # Returns true if there are requested builds waiting and false
37
+ # otherwise.
38
+ def waiting?
39
+ if enabled?
40
+ not @queue.empty?
41
+ else
42
+ false
43
+ end
44
+ end
45
+
46
+ protected
47
+ def log(msg)
48
+ puts msg if @verbose
49
+ end
50
+
51
+ def enabled?
52
+ @enabled
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,117 @@
1
+ require 'sinatra/base'
2
+ require 'erb'
3
+ require 'json'
4
+
5
+ class CIQuantum
6
+ class Server < Sinatra::Base
7
+ attr_reader :quantum
8
+
9
+ dir = File.dirname(File.expand_path(__FILE__))
10
+
11
+ set :views, "#{dir}/views"
12
+ set :public_folder, "#{dir}/public"
13
+ set :static, true
14
+ set :lock, true
15
+
16
+ before { quantum.restore }
17
+
18
+ get '/ping' do
19
+ if quantum.building? || !quantum.last_build || !quantum.last_build.worked?
20
+ halt 412, (quantum.building? || quantum.last_build.nil?) ? "building" : quantum.last_build.sha
21
+ end
22
+
23
+ quantum.last_build.sha
24
+ end
25
+
26
+ get '/?' do
27
+ erb(:template, {}, :quantum => quantum)
28
+ end
29
+
30
+ post '/?' do
31
+ unless params[:rebuild]
32
+ payload = JSON.parse(params[:payload])
33
+ pushed_branch = payload["ref"].split('/').last
34
+ end
35
+
36
+ # Only build if we were given an explicit branch via `?branch=blah`
37
+ # or the payload exists and the "ref" property matches our
38
+ # specified build branch.
39
+ if params[:branch] || params[:rebuild] || pushed_branch == quantum.git_branch
40
+ quantum.build(params[:branch])
41
+ end
42
+
43
+ redirect request.path
44
+ end
45
+
46
+ get '/api/json' do
47
+ response = [200, {'Content-Type' => 'application/json'}]
48
+ response_json = erb(:json, {}, :quantum => quantum)
49
+ if params[:jsonp]
50
+ response << params[:jsonp] + '(' + response_json + ')'
51
+ else
52
+ response << response_json
53
+ end
54
+ response
55
+ end
56
+
57
+ get '/coverage' do
58
+ redirect quantum.coverage_path
59
+ end
60
+
61
+ helpers do
62
+ include Rack::Utils
63
+ alias_method :h, :escape_html
64
+
65
+ # thanks integrity!
66
+ def ansi_color_codes(string)
67
+ string.gsub("\e[0m", '</span>').
68
+ gsub(/\e\[(\d+)m/, "<span class=\"color\\1\">")
69
+ end
70
+
71
+ def pretty_time(time)
72
+ time.strftime("%Y-%m-%d %H:%M")
73
+ end
74
+
75
+ def ciquantum_root
76
+ root = request.path
77
+ root = "" if root == "/"
78
+ root
79
+ end
80
+ end
81
+
82
+ def initialize(*args)
83
+ super
84
+ check_project
85
+ @quantum = CIQuantum.new(settings.project_path)
86
+ end
87
+
88
+ def self.start(host, port, project_path)
89
+ set :project_path, project_path
90
+ CIQuantum::Server.run! :host => host, :port => port
91
+ end
92
+
93
+ def self.rack_start(project_path)
94
+ set :project_path, project_path
95
+ self.new
96
+ end
97
+
98
+ def self.project_path=(project_path)
99
+ user, pass = Config.ciquantum(project_path).user.to_s, Config.ciquantum(project_path).pass.to_s
100
+ if user != '' && pass != ''
101
+ use Rack::Auth::Basic do |username, password|
102
+ [ username, password ] == [ user, pass ]
103
+ end
104
+ puts "Using HTTP basic auth"
105
+ end
106
+ set :project_path, Proc.new{project_path}, true
107
+ end
108
+
109
+ def check_project
110
+ if settings.project_path.nil? || !File.exists?(File.expand_path(settings.project_path))
111
+ puts "Whoops! I need the path to a Git repo."
112
+ puts " $ git clone git@github.com:username/project.git project"
113
+ abort " $ ciquantum project"
114
+ end
115
+ end
116
+ end
117
+ end