rhomobile-cijoe 0.2.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 +9 -0
- data/LICENSE +20 -0
- data/README.markdown +153 -0
- data/Rakefile +59 -0
- data/VERSION +1 -0
- data/bin/cijoe +49 -0
- data/deps.rip +4 -0
- data/examples/cijoe.ru +15 -0
- data/examples/cijoed +53 -0
- data/lib/cijoe.rb +264 -0
- data/lib/cijoe/build.rb +60 -0
- data/lib/cijoe/commit.rb +27 -0
- data/lib/cijoe/config.rb +42 -0
- data/lib/cijoe/email.rb +66 -0
- data/lib/cijoe/public/favicon.ico +0 -0
- data/lib/cijoe/public/octocat.png +0 -0
- data/lib/cijoe/public/screen.css +234 -0
- data/lib/cijoe/server.rb +90 -0
- data/lib/cijoe/version.rb +3 -0
- data/lib/cijoe/views/template.erb +120 -0
- data/lib/mmmail.rb +291 -0
- data/lib/smtp_tls.rb +111 -0
- data/spec/email_spec.rb +60 -0
- data/test/helper.rb +9 -0
- data/test/test_cijoe.rb +17 -0
- metadata +137 -0
data/lib/cijoe/build.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
class CIJoe
|
4
|
+
class Build < Struct.new(:user, :project, :started_at, :finished_at, :sha, :status, :output, :pid, :total, :passes, :fails, :faillog)
|
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 short_sha
|
24
|
+
if sha
|
25
|
+
sha[0,7]
|
26
|
+
else
|
27
|
+
"<unknown>"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def clean_output
|
32
|
+
output.gsub(/\e\[.+?m/, '').strip
|
33
|
+
end
|
34
|
+
|
35
|
+
def commit
|
36
|
+
return if sha.nil?
|
37
|
+
@commit ||= Commit.new(sha, user, project)
|
38
|
+
end
|
39
|
+
|
40
|
+
def dump(file)
|
41
|
+
config = [user, project, started_at, finished_at, sha, status, output, pid, total, passes, fails, faillog]
|
42
|
+
data = YAML.dump(config)
|
43
|
+
File.open(file, 'wb') { |io| io.write(data) }
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.load(file)
|
47
|
+
if File.exist?(file)
|
48
|
+
config = YAML.load(File.read(file))
|
49
|
+
|
50
|
+
if config[8].to_s == "" or config[9].to_s == "" or config[10].to_s == ""
|
51
|
+
config[8] = $1 if config[6] =~ /Total: ([0-9]+)/
|
52
|
+
config[9] = $1 if config[6] =~ /Passed: ([0-9]+)/
|
53
|
+
config[10] = $1 if config[6] =~ /Failed: ([0-9]+)/
|
54
|
+
end
|
55
|
+
|
56
|
+
new *config
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
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.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
|
data/lib/cijoe/config.rb
ADDED
@@ -0,0 +1,42 @@
|
|
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_command = "git config #{config_string}"
|
18
|
+
result = `#{git_command} 2>&1`.chomp
|
19
|
+
process_status = $?
|
20
|
+
|
21
|
+
if successful_command?(process_status) || config_command_with_empty_value?(result,process_status)
|
22
|
+
return result
|
23
|
+
else
|
24
|
+
raise "Error calling git config, is a recent version of git installed? Command: #{git_command}, Error: #{result}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def config_string
|
29
|
+
@parent ? "#{@parent.config_string}.#{@command}" : @command
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def successful_command?(process_status)
|
35
|
+
process_status.exitstatus.to_i == 0
|
36
|
+
end
|
37
|
+
|
38
|
+
def config_command_with_empty_value?(result, process_status)
|
39
|
+
process_status.exitstatus.to_i == 1 && result.empty?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/cijoe/email.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../mmmail'
|
2
|
+
|
3
|
+
class CIJoe
|
4
|
+
module Email
|
5
|
+
def self.activate
|
6
|
+
if valid_config?
|
7
|
+
CIJoe::Build.class_eval do
|
8
|
+
include CIJoe::Email
|
9
|
+
end
|
10
|
+
|
11
|
+
puts "Loaded Email notifier"
|
12
|
+
else
|
13
|
+
puts "Can't load Email notifier."
|
14
|
+
puts "Please add the following to your project's .git/config:"
|
15
|
+
puts "[email]"
|
16
|
+
puts "\tto = build@example.com"
|
17
|
+
puts "\tuser = horst"
|
18
|
+
puts "\tpass = passw0rd"
|
19
|
+
puts "\thost = mail.example.com"
|
20
|
+
puts "\tauthtype = plain"
|
21
|
+
puts "\tenabletls = 1"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.config
|
26
|
+
@config ||= {
|
27
|
+
:to => Config.email.to.to_s,
|
28
|
+
:user => Config.email.user.to_s,
|
29
|
+
:pass => Config.email.pass.to_s,
|
30
|
+
:host => Config.email.host.to_s,
|
31
|
+
:auth_type => Config.email.authtype.to_s,
|
32
|
+
:enable_tls => Config.email.enabletls.to_s
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.valid_config?
|
37
|
+
%w( host user pass to auth_type ).all? do |key|
|
38
|
+
!config[key.intern].empty?
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def notify
|
43
|
+
options = {
|
44
|
+
:to => Email.config[:to],
|
45
|
+
:from => Email.config[:to],
|
46
|
+
:subject => "(#{project}) Build failed",
|
47
|
+
:body => mail_body
|
48
|
+
}
|
49
|
+
MmMail.mail(options, mail_config) if failed?
|
50
|
+
end
|
51
|
+
|
52
|
+
def mail_config
|
53
|
+
config = MmMail::Transport::Config.new
|
54
|
+
config.auth_user = Email.config[:user]
|
55
|
+
config.auth_pass = Email.config[:pass]
|
56
|
+
config.auth_type = Email.config[:auth_type].to_sym
|
57
|
+
config.host = Email.config[:host]
|
58
|
+
config.enable_tls = Email.config[:enable_tls] == "1" ? true : false
|
59
|
+
config
|
60
|
+
end
|
61
|
+
|
62
|
+
def mail_body
|
63
|
+
"The commit '#{commit.message}' (#{commit.url}) by #{commit.author} caused the build to fail."
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
Binary file
|
Binary file
|
@@ -0,0 +1,234 @@
|
|
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
|
+
ul.posts .total {
|
93
|
+
color: blue;
|
94
|
+
font-size: 66%;
|
95
|
+
}
|
96
|
+
|
97
|
+
ul.posts .pass {
|
98
|
+
color: green;
|
99
|
+
font-size: 66%;
|
100
|
+
}
|
101
|
+
|
102
|
+
ul.posts .fail {
|
103
|
+
color: red;
|
104
|
+
font-size: 66%;
|
105
|
+
}
|
106
|
+
|
107
|
+
ul.posts .showlog {
|
108
|
+
font-size: 66%;
|
109
|
+
}
|
110
|
+
|
111
|
+
/*****************************************************************************/
|
112
|
+
/*
|
113
|
+
/* Site
|
114
|
+
/*
|
115
|
+
/*****************************************************************************/
|
116
|
+
|
117
|
+
.site {
|
118
|
+
font-size: 110%;
|
119
|
+
text-align: justify;
|
120
|
+
width: 50em;
|
121
|
+
margin: 3em auto 2em auto;
|
122
|
+
line-height: 1.5em;
|
123
|
+
}
|
124
|
+
|
125
|
+
.title {
|
126
|
+
color: #a00;
|
127
|
+
font-weight: bold;
|
128
|
+
margin-bottom: 2em;
|
129
|
+
}
|
130
|
+
|
131
|
+
.site .title a {
|
132
|
+
color: #a00;
|
133
|
+
text-decoration: none;
|
134
|
+
}
|
135
|
+
|
136
|
+
.site .title a:hover {
|
137
|
+
color: black;
|
138
|
+
}
|
139
|
+
|
140
|
+
.site .title .extra {
|
141
|
+
color: #aaa;
|
142
|
+
text-decoration: none;
|
143
|
+
margin-left: 1em;
|
144
|
+
font-size: 0.9em;
|
145
|
+
}
|
146
|
+
|
147
|
+
.site .title a.extra:hover {
|
148
|
+
color: black;
|
149
|
+
}
|
150
|
+
|
151
|
+
.site .meta {
|
152
|
+
color: #aaa;
|
153
|
+
}
|
154
|
+
|
155
|
+
.site .footer {
|
156
|
+
font-size: 80%;
|
157
|
+
color: #666;
|
158
|
+
border-top: 4px solid #eee;
|
159
|
+
margin-top: 2em;
|
160
|
+
overflow: hidden;
|
161
|
+
}
|
162
|
+
|
163
|
+
.site .footer .contact {
|
164
|
+
float: left;
|
165
|
+
margin-right: 3em;
|
166
|
+
}
|
167
|
+
|
168
|
+
.site .footer .contact a {
|
169
|
+
color: #8085C1;
|
170
|
+
}
|
171
|
+
|
172
|
+
.site .footer .rss {
|
173
|
+
margin-top: 1.1em;
|
174
|
+
margin-right: -.2em;
|
175
|
+
float: right;
|
176
|
+
}
|
177
|
+
|
178
|
+
.site .footer .rss img {
|
179
|
+
border: 0;
|
180
|
+
}
|
181
|
+
|
182
|
+
/*****************************************************************************/
|
183
|
+
/*
|
184
|
+
/* Posts
|
185
|
+
/*
|
186
|
+
/*****************************************************************************/
|
187
|
+
|
188
|
+
#post {
|
189
|
+
|
190
|
+
}
|
191
|
+
|
192
|
+
/* standard */
|
193
|
+
|
194
|
+
#post pre {
|
195
|
+
border: 1px solid #ddd;
|
196
|
+
background-color: #eef;
|
197
|
+
padding: 0 .4em;
|
198
|
+
}
|
199
|
+
|
200
|
+
#post ul,
|
201
|
+
#post ol {
|
202
|
+
margin-left: 1.25em;
|
203
|
+
}
|
204
|
+
|
205
|
+
#post code {
|
206
|
+
border: 1px solid #ddd;
|
207
|
+
background-color: #eef;
|
208
|
+
font-size: 95%;
|
209
|
+
padding: 0 .2em;
|
210
|
+
}
|
211
|
+
|
212
|
+
#post pre code {
|
213
|
+
border: none;
|
214
|
+
}
|
215
|
+
|
216
|
+
/* terminal */
|
217
|
+
|
218
|
+
pre.terminal {
|
219
|
+
border: 1px solid black;
|
220
|
+
background-color: #333;
|
221
|
+
color: white;
|
222
|
+
padding: 5px;
|
223
|
+
overflow: scroll;
|
224
|
+
overflow-y: hidden;
|
225
|
+
word-wrap: normal;
|
226
|
+
line-height: 1em;
|
227
|
+
}
|
228
|
+
|
229
|
+
pre.terminal code {
|
230
|
+
font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace;
|
231
|
+
background-color: #333;
|
232
|
+
font-size: 12px;
|
233
|
+
}
|
234
|
+
|
data/lib/cijoe/server.rb
ADDED
@@ -0,0 +1,90 @@
|
|
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
|
+
set :lock, true
|
12
|
+
|
13
|
+
before { @joe.restore }
|
14
|
+
|
15
|
+
get '/?' do
|
16
|
+
erb(:template, {}, :joe => @joe)
|
17
|
+
end
|
18
|
+
|
19
|
+
get '/logfail/:log_name' do
|
20
|
+
ansi_color_codes(@joe.failure_for_time params[:log_name])
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
get '/log/:log_name' do
|
25
|
+
ansi_color_codes(@joe.log_for_time params[:log_name])
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
|
31
|
+
post '/?' do
|
32
|
+
payload = params[:payload].to_s
|
33
|
+
if payload.empty? || payload.include?(@joe.git_branch)
|
34
|
+
@joe.build
|
35
|
+
end
|
36
|
+
redirect request.path
|
37
|
+
end
|
38
|
+
|
39
|
+
user, pass = Config.cijoe.user.to_s, Config.cijoe.pass.to_s
|
40
|
+
if user != '' && pass != ''
|
41
|
+
use Rack::Auth::Basic do |username, password|
|
42
|
+
[ username, password ] == [ user, pass ]
|
43
|
+
end
|
44
|
+
puts "Using HTTP basic auth"
|
45
|
+
end
|
46
|
+
|
47
|
+
helpers do
|
48
|
+
include Rack::Utils
|
49
|
+
alias_method :h, :escape_html
|
50
|
+
|
51
|
+
# thanks integrity!
|
52
|
+
def ansi_color_codes(string)
|
53
|
+
string.gsub("\e[0m", '</span>').
|
54
|
+
gsub(/\e\[(\d+)m/, "<span class=\"color\\1\">")
|
55
|
+
end
|
56
|
+
|
57
|
+
def pretty_time(time)
|
58
|
+
return time if time.nil?
|
59
|
+
time.strftime("%Y-%m-%d %H:%M")
|
60
|
+
end
|
61
|
+
|
62
|
+
def cijoe_root
|
63
|
+
root = request.path
|
64
|
+
root = "" if root == "/"
|
65
|
+
root
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def initialize(*args)
|
70
|
+
super
|
71
|
+
check_project
|
72
|
+
@joe = CIJoe.new(options.project_path)
|
73
|
+
|
74
|
+
CIJoe::Email.activate
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.start(host, port, project_path)
|
78
|
+
set :project_path, project_path
|
79
|
+
CIJoe::Server.run! :host => host, :port => port
|
80
|
+
end
|
81
|
+
|
82
|
+
def check_project
|
83
|
+
if options.project_path.nil? || !File.exists?(File.expand_path(options.project_path))
|
84
|
+
puts "Whoops! I need the path to a Git repo."
|
85
|
+
puts " $ git clone git@github.com:username/project.git project"
|
86
|
+
abort " $ cijoe project"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|