mattmatt-cijoe 0.1.2
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 +2 -0
- data/LICENSE +20 -0
- data/README.markdown +147 -0
- data/Rakefile +25 -0
- data/VERSION +1 -0
- data/bin/cijoe +49 -0
- data/deps.rip +5 -0
- data/examples/cijoe.ru +15 -0
- data/examples/cijoed +53 -0
- data/lib/cijoe.rb +180 -0
- data/lib/cijoe/build.rb +49 -0
- data/lib/cijoe/commit.rb +27 -0
- data/lib/cijoe/config.rb +24 -0
- data/lib/cijoe/email.rb +62 -0
- data/lib/cijoe/public/octocat.png +0 -0
- data/lib/cijoe/public/screen.css +212 -0
- data/lib/cijoe/server.rb +77 -0
- data/lib/cijoe/version.rb +3 -0
- data/lib/cijoe/views/template.erb +64 -0
- data/lib/mmmail.rb +286 -0
- data/spec/email_spec.rb +60 -0
- metadata +105 -0
data/lib/cijoe/build.rb
ADDED
@@ -0,0 +1,49 @@
|
|
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 short_sha
|
24
|
+
sha[0,7] if sha
|
25
|
+
end
|
26
|
+
|
27
|
+
def clean_output
|
28
|
+
output.gsub(/\e\[.+?m/, '').strip
|
29
|
+
end
|
30
|
+
|
31
|
+
def commit
|
32
|
+
return if sha.nil?
|
33
|
+
@commit ||= Commit.new(sha, user, project)
|
34
|
+
end
|
35
|
+
|
36
|
+
def dump(file)
|
37
|
+
config = [user, project, started_at, finished_at, sha, status, output, pid]
|
38
|
+
data = YAML.dump(config)
|
39
|
+
File.open(file, 'wb') { |io| io.write(data) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.load(file)
|
43
|
+
if File.exist?(file)
|
44
|
+
config = YAML.load(File.read(file))
|
45
|
+
new *config
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
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,24 @@
|
|
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 config #{config_string}`.chomp
|
18
|
+
end
|
19
|
+
|
20
|
+
def config_string
|
21
|
+
@parent ? "#{@parent.config_string}.#{@command}" : @command
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/cijoe/email.rb
ADDED
@@ -0,0 +1,62 @@
|
|
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 "\ttype = login"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.config
|
25
|
+
@config ||= {
|
26
|
+
:to => Config.email.to.to_s,
|
27
|
+
:user => Config.email.user.to_s,
|
28
|
+
:pass => Config.email.pass.to_s,
|
29
|
+
:host => Config.email.host.to_s,
|
30
|
+
:type => Config.email.type || 'plain'
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.valid_config?
|
35
|
+
%w( host user pass to ).all? do |key|
|
36
|
+
!config[key.intern].empty?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def notify
|
41
|
+
options = {
|
42
|
+
:to => Email.config[:to],
|
43
|
+
:from => Email.config[:to],
|
44
|
+
:subject => "(#{commit.project}) Build failed",
|
45
|
+
:body => mail_body
|
46
|
+
}
|
47
|
+
MmMail.send(options, mail_config) if failed?
|
48
|
+
end
|
49
|
+
|
50
|
+
def mail_config
|
51
|
+
config = MmMail::Config.new
|
52
|
+
config.auth_user = Email.config[:user]
|
53
|
+
config.auth_pass = Email.config[:pass]
|
54
|
+
config.auth_type = Email.config[:type]
|
55
|
+
config
|
56
|
+
end
|
57
|
+
|
58
|
+
def mail_body
|
59
|
+
"The commit '#{commit.message}' (#{commit.url}) by #{commit.author} caused the build to fail."
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
Binary file
|
@@ -0,0 +1,212 @@
|
|
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
|
+
/*****************************************************************************/
|
93
|
+
/*
|
94
|
+
/* Site
|
95
|
+
/*
|
96
|
+
/*****************************************************************************/
|
97
|
+
|
98
|
+
.site {
|
99
|
+
font-size: 110%;
|
100
|
+
text-align: justify;
|
101
|
+
width: 40em;
|
102
|
+
margin: 3em auto 2em auto;
|
103
|
+
line-height: 1.5em;
|
104
|
+
}
|
105
|
+
|
106
|
+
.title {
|
107
|
+
color: #a00;
|
108
|
+
font-weight: bold;
|
109
|
+
margin-bottom: 2em;
|
110
|
+
}
|
111
|
+
|
112
|
+
.site .title a {
|
113
|
+
color: #a00;
|
114
|
+
text-decoration: none;
|
115
|
+
}
|
116
|
+
|
117
|
+
.site .title a:hover {
|
118
|
+
color: black;
|
119
|
+
}
|
120
|
+
|
121
|
+
.site .title .extra {
|
122
|
+
color: #aaa;
|
123
|
+
text-decoration: none;
|
124
|
+
margin-left: 1em;
|
125
|
+
font-size: 0.9em;
|
126
|
+
}
|
127
|
+
|
128
|
+
.site .title a.extra:hover {
|
129
|
+
color: black;
|
130
|
+
}
|
131
|
+
|
132
|
+
.site .meta {
|
133
|
+
color: #aaa;
|
134
|
+
}
|
135
|
+
|
136
|
+
.site .footer {
|
137
|
+
font-size: 80%;
|
138
|
+
color: #666;
|
139
|
+
border-top: 4px solid #eee;
|
140
|
+
margin-top: 2em;
|
141
|
+
overflow: hidden;
|
142
|
+
}
|
143
|
+
|
144
|
+
.site .footer .contact {
|
145
|
+
float: left;
|
146
|
+
margin-right: 3em;
|
147
|
+
}
|
148
|
+
|
149
|
+
.site .footer .contact a {
|
150
|
+
color: #8085C1;
|
151
|
+
}
|
152
|
+
|
153
|
+
.site .footer .rss {
|
154
|
+
margin-top: 1.1em;
|
155
|
+
margin-right: -.2em;
|
156
|
+
float: right;
|
157
|
+
}
|
158
|
+
|
159
|
+
.site .footer .rss img {
|
160
|
+
border: 0;
|
161
|
+
}
|
162
|
+
|
163
|
+
/*****************************************************************************/
|
164
|
+
/*
|
165
|
+
/* Posts
|
166
|
+
/*
|
167
|
+
/*****************************************************************************/
|
168
|
+
|
169
|
+
#post {
|
170
|
+
|
171
|
+
}
|
172
|
+
|
173
|
+
/* standard */
|
174
|
+
|
175
|
+
#post pre {
|
176
|
+
border: 1px solid #ddd;
|
177
|
+
background-color: #eef;
|
178
|
+
padding: 0 .4em;
|
179
|
+
}
|
180
|
+
|
181
|
+
#post ul,
|
182
|
+
#post ol {
|
183
|
+
margin-left: 1.25em;
|
184
|
+
}
|
185
|
+
|
186
|
+
#post code {
|
187
|
+
border: 1px solid #ddd;
|
188
|
+
background-color: #eef;
|
189
|
+
font-size: 95%;
|
190
|
+
padding: 0 .2em;
|
191
|
+
}
|
192
|
+
|
193
|
+
#post pre code {
|
194
|
+
border: none;
|
195
|
+
}
|
196
|
+
|
197
|
+
/* terminal */
|
198
|
+
|
199
|
+
pre.terminal {
|
200
|
+
border: 1px solid black;
|
201
|
+
background-color: #333;
|
202
|
+
color: white;
|
203
|
+
padding: 5px;
|
204
|
+
overflow: auto;
|
205
|
+
word-wrap: break-word;
|
206
|
+
}
|
207
|
+
|
208
|
+
pre.terminal code {
|
209
|
+
font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace;
|
210
|
+
background-color: #333;
|
211
|
+
}
|
212
|
+
|
data/lib/cijoe/server.rb
ADDED
@@ -0,0 +1,77 @@
|
|
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
|
+
post '/?' do
|
20
|
+
payload = params[:payload].to_s
|
21
|
+
if payload.empty? || payload.include?(@joe.git_branch)
|
22
|
+
@joe.build
|
23
|
+
end
|
24
|
+
redirect request.path
|
25
|
+
end
|
26
|
+
|
27
|
+
user, pass = Config.cijoe.user.to_s, Config.cijoe.pass.to_s
|
28
|
+
if user != '' && pass != ''
|
29
|
+
use Rack::Auth::Basic do |username, password|
|
30
|
+
[ username, password ] == [ user, pass ]
|
31
|
+
end
|
32
|
+
puts "Using HTTP basic auth"
|
33
|
+
end
|
34
|
+
|
35
|
+
helpers do
|
36
|
+
include Rack::Utils
|
37
|
+
alias_method :h, :escape_html
|
38
|
+
|
39
|
+
# thanks integrity!
|
40
|
+
def ansi_color_codes(string)
|
41
|
+
string.gsub("\e[0m", '</span>').
|
42
|
+
gsub(/\e\[(\d+)m/, "<span class=\"color\\1\">")
|
43
|
+
end
|
44
|
+
|
45
|
+
def pretty_time(time)
|
46
|
+
time.strftime("%Y-%m-%d %H:%M")
|
47
|
+
end
|
48
|
+
|
49
|
+
def cijoe_root
|
50
|
+
root = request.path
|
51
|
+
root = "" if root == "/"
|
52
|
+
root
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def initialize(*args)
|
57
|
+
super
|
58
|
+
check_project
|
59
|
+
@joe = CIJoe.new(options.project_path)
|
60
|
+
|
61
|
+
CIJoe::Email.activate
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.start(host, port, project_path)
|
65
|
+
set :project_path, project_path
|
66
|
+
CIJoe::Server.run! :host => host, :port => port
|
67
|
+
end
|
68
|
+
|
69
|
+
def check_project
|
70
|
+
if options.project_path.nil? || !File.exists?(File.expand_path(options.project_path))
|
71
|
+
puts "Whoops! I need the path to a Git repo."
|
72
|
+
puts " $ git clone git@github.com:username/project.git project"
|
73
|
+
abort " $ cijoe project"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|