vet-ci 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/README.md +23 -0
- data/Rakefile +1 -0
- data/Vetfile +5 -0
- data/Vetfile.example +1 -0
- data/bin/vet-ci +7 -0
- data/lib/vet-ci.rb +20 -0
- data/lib/vet-ci/build.rb +40 -0
- data/lib/vet-ci/project.rb +197 -0
- data/lib/vet-ci/public/css/style.css +264 -0
- data/lib/vet-ci/public/js/application.js +55 -0
- data/lib/vet-ci/server.rb +61 -0
- data/lib/vet-ci/setup.rb +31 -0
- data/lib/vet-ci/util.rb +15 -0
- data/lib/vet-ci/version.rb +5 -0
- data/lib/vet-ci/views/index.erb +11 -0
- data/lib/vet-ci/views/layout.erb +22 -0
- data/lib/vet-ci/views/project.erb +38 -0
- data/spec/vet_spec.rb +11 -0
- data/vet-ci.gemspec +30 -0
- metadata +144 -0
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
VetCI
|
2
|
+
-----
|
3
|
+
|
4
|
+
VetCI makes it easy to take care of your code pets. It's a barebones continuous integration system that lets you run commands (usually testing or building) against as many projects as you need. It's heavily inspired by CIJoe, but is designed to be a bit more flexible.
|
5
|
+
|
6
|
+
This documentation isn't even close to complete. It will be a mess for a while until version 1.
|
7
|
+
|
8
|
+
|
9
|
+
The Vetfile
|
10
|
+
-----------
|
11
|
+
|
12
|
+
Your project's Vetfile is a crucial piece of the VetCI system. This file describes your system's overall structure to VetCI and explains how tests should be run. VetCI is very flexible regarding which test framework(s) you choose to use - the only requirement is that your test suite return 0 on success and some other integer on fail.
|
13
|
+
|
14
|
+
|
15
|
+
If you have a single project, you can include a Vetfile in the project root directory (at the same level as your Gemfile for Ruby or your package.json for Node).
|
16
|
+
|
17
|
+
Vetfile Options
|
18
|
+
|
19
|
+
name The display name of your project.
|
20
|
+
path The path to your project, relative to the Vetfile.
|
21
|
+
command The build/test command that should be run on your project.
|
22
|
+
autoupdate When set to true, automatically performs a git fetch origin and hard reset on the code prior to running the tests. Defaults to true.
|
23
|
+
default_branch Specifies the default branch VetCI should try to work with.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/Vetfile
ADDED
data/Vetfile.example
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Vetfiles are now yaml...
|
data/bin/vet-ci
ADDED
data/lib/vet-ci.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'vet-ci/version'
|
2
|
+
require 'vet-ci/project'
|
3
|
+
require 'vet-ci/build'
|
4
|
+
require 'vet-ci/setup'
|
5
|
+
require 'vet-ci/server'
|
6
|
+
require 'yaml'
|
7
|
+
require 'grit'
|
8
|
+
|
9
|
+
module VetCI
|
10
|
+
class Core
|
11
|
+
attr_accessor :vetfile
|
12
|
+
def initialize
|
13
|
+
self.vetfile = VetCI::Setup.go
|
14
|
+
end
|
15
|
+
|
16
|
+
def start
|
17
|
+
VetCI::Server.start
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/vet-ci/build.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
module VetCI
|
2
|
+
class Build
|
3
|
+
attr_accessor :id,
|
4
|
+
:output,
|
5
|
+
:status,
|
6
|
+
:date,
|
7
|
+
:commit,
|
8
|
+
:committer,
|
9
|
+
:payload,
|
10
|
+
:project
|
11
|
+
|
12
|
+
|
13
|
+
def initialize(attributes = {})
|
14
|
+
attributes.each do |key, value|
|
15
|
+
self.send("#{key}=", value)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def commit_info
|
20
|
+
commit = self.project.repo.commit(self.commit)
|
21
|
+
commit.nil? ? nil : commit.to_hash
|
22
|
+
end
|
23
|
+
|
24
|
+
def status_class
|
25
|
+
if self.status == 0
|
26
|
+
'passed'
|
27
|
+
else
|
28
|
+
'failed'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def friendly_time
|
33
|
+
self.date.strftime('%A, %B %e, %Y at %I:%M %p')
|
34
|
+
end
|
35
|
+
|
36
|
+
def dashboard_time
|
37
|
+
self.date.strftime('%D @ %r')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
require 'pstore'
|
2
|
+
require 'vet-ci/util'
|
3
|
+
require 'vet-ci/build'
|
4
|
+
require 'grit'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
module VetCI
|
8
|
+
class Project
|
9
|
+
|
10
|
+
class << self
|
11
|
+
# This shouldn't be here,
|
12
|
+
# but it was easy. We'll refactor it
|
13
|
+
# when that time comes...
|
14
|
+
|
15
|
+
def projects
|
16
|
+
@projects ||= {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def projects=(value)
|
20
|
+
@projects = value
|
21
|
+
end
|
22
|
+
|
23
|
+
def datastore
|
24
|
+
@datastore
|
25
|
+
end
|
26
|
+
|
27
|
+
def datastore=(value)
|
28
|
+
@datastore = value
|
29
|
+
end
|
30
|
+
|
31
|
+
#Returns a project in the list that is named ..
|
32
|
+
def named(value)
|
33
|
+
self.projects[value]
|
34
|
+
end
|
35
|
+
|
36
|
+
def parse_vetfile_contents(parameters)
|
37
|
+
parameters.each do |project|
|
38
|
+
name = project['name']
|
39
|
+
path = project['path']
|
40
|
+
command = project['command']
|
41
|
+
default_branch = project['default_branch']
|
42
|
+
autoupdate = (project['autoupdate'].nil? ? false : project['autoupdate'])
|
43
|
+
|
44
|
+
if path.nil?
|
45
|
+
path = Dir.pwd
|
46
|
+
else
|
47
|
+
path = File.expand_path path
|
48
|
+
end
|
49
|
+
|
50
|
+
project = Project.new(:name => name, :project_path => path, :build_command => command, :default_branch => default_branch, :autoupdate => autoupdate)
|
51
|
+
|
52
|
+
self.projects[project.name] = project
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
attr_accessor :name
|
58
|
+
attr_accessor :project_path
|
59
|
+
attr_accessor :build_command
|
60
|
+
attr_accessor :default_branch
|
61
|
+
attr_accessor :autoupdate
|
62
|
+
attr_accessor :building
|
63
|
+
|
64
|
+
|
65
|
+
def builds
|
66
|
+
@builds ||= []
|
67
|
+
end
|
68
|
+
|
69
|
+
def pagedBuilds(page=1, limit=10)
|
70
|
+
# We need to calculate the slice
|
71
|
+
page = page.nil? ? 1 : page.to_i
|
72
|
+
limit = limit.nil? ? 10 : limit.to_i
|
73
|
+
page = 1 if page < 0
|
74
|
+
start = (page-1) * limit
|
75
|
+
# limit is the length
|
76
|
+
result = self.builds[start,limit]
|
77
|
+
result.nil? ? [] : result
|
78
|
+
end
|
79
|
+
|
80
|
+
def builds=(value)
|
81
|
+
@builds = value
|
82
|
+
end
|
83
|
+
|
84
|
+
def insertBuild(build)
|
85
|
+
self.builds << build
|
86
|
+
@builds.sort! do |a, b|
|
87
|
+
if a.date == b.date
|
88
|
+
0
|
89
|
+
elsif a.date < b.date
|
90
|
+
1
|
91
|
+
else
|
92
|
+
-1
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def initialize(attributes = {})
|
98
|
+
attributes.each do |key, value|
|
99
|
+
self.send("#{key}=", value)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Now, let's load any existing builds in the list from the datastore
|
103
|
+
unless Project.datastore.nil?
|
104
|
+
Project.datastore.transaction(true) do
|
105
|
+
builds = Project.datastore[self.name]
|
106
|
+
self.builds.concat(builds) unless builds.nil?
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def save!
|
112
|
+
unless Project.datastore.nil?
|
113
|
+
Project.datastore.transaction do
|
114
|
+
Project.datastore[self.name] = @builds
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def git_update
|
120
|
+
reset_branch = self.default_branch.nil? ? "" : " origin/#{self.default_branch}"
|
121
|
+
`cd #{self.project_path} && git fetch origin && git reset --hard#{reset_branch}`
|
122
|
+
end
|
123
|
+
|
124
|
+
def repo
|
125
|
+
Grit::Repo.new(self.project_path)
|
126
|
+
end
|
127
|
+
|
128
|
+
def is_building?
|
129
|
+
self.building == true
|
130
|
+
end
|
131
|
+
|
132
|
+
def last_build_status
|
133
|
+
if is_building?
|
134
|
+
return 'running'
|
135
|
+
elsif self.builds.count > 0
|
136
|
+
return self.builds.first.status_class
|
137
|
+
else
|
138
|
+
return ''
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def build(faye=nil, payload=nil)
|
143
|
+
if is_building?
|
144
|
+
return
|
145
|
+
end
|
146
|
+
|
147
|
+
if payload
|
148
|
+
begin
|
149
|
+
payload = JSON.parse payload
|
150
|
+
rescue
|
151
|
+
payload = nil
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
Thread.new {build!(faye, payload)}
|
156
|
+
end
|
157
|
+
|
158
|
+
def build!(faye=nil, payload=nil)
|
159
|
+
self.building = true
|
160
|
+
|
161
|
+
unless faye.nil?
|
162
|
+
faye.publish '/all', :project => self.name, :status => 'running'
|
163
|
+
end
|
164
|
+
|
165
|
+
# First, let's reset the repo if we said we should in the Vetfile
|
166
|
+
if self.autoupdate == true
|
167
|
+
puts "Updating the repo"
|
168
|
+
self.git_update
|
169
|
+
end
|
170
|
+
|
171
|
+
@result = ''
|
172
|
+
repo = Grit::Repo.new @project_path
|
173
|
+
commit = repo.commits.first
|
174
|
+
Util.open_pipe("cd #{@project_path} && #{@build_command} 2>&1") do |pipe, process_id|
|
175
|
+
puts "#{Time.now.to_i}: Building with command '#{@build_command}'..."
|
176
|
+
@current_pid = process_id
|
177
|
+
@result = pipe.read
|
178
|
+
end
|
179
|
+
Process.waitpid(@current_pid)
|
180
|
+
puts $?.exitstatus.to_i
|
181
|
+
if commit.nil?
|
182
|
+
current_build = Build.new(:project => self, :status => $?.exitstatus.to_i, :output => @result, :date => Time.now, :payload => payload)
|
183
|
+
else
|
184
|
+
current_build = Build.new(:project => self, :status => $?.exitstatus.to_i, :output => @result, :date => Time.now, :commit => commit.id, :committer => commit.committer.name, :payload => payload)
|
185
|
+
end
|
186
|
+
|
187
|
+
puts "Saving build..."
|
188
|
+
unless faye.nil?
|
189
|
+
faye.publish '/all', :project => self.name, :status => current_build.status_class, :last_build => current_build.dashboard_time
|
190
|
+
end
|
191
|
+
|
192
|
+
self.insertBuild current_build
|
193
|
+
self.building = false
|
194
|
+
self.save!
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
@@ -0,0 +1,264 @@
|
|
1
|
+
/* VetCI Base Styles */
|
2
|
+
html {
|
3
|
+
font-family: 'Helvetica Neue', sans-serif;
|
4
|
+
}
|
5
|
+
|
6
|
+
body {
|
7
|
+
width: 960px;
|
8
|
+
margin: auto;
|
9
|
+
}
|
10
|
+
|
11
|
+
.logo {
|
12
|
+
float: left;
|
13
|
+
}
|
14
|
+
|
15
|
+
.logo a {
|
16
|
+
color: #000;
|
17
|
+
text-decoration: none;
|
18
|
+
}
|
19
|
+
|
20
|
+
.build_all {
|
21
|
+
float: right;
|
22
|
+
width: 100px;
|
23
|
+
height: 40px;
|
24
|
+
line-height: 40px;
|
25
|
+
-webkit-appearance: none;
|
26
|
+
border: none;
|
27
|
+
padding: 0;
|
28
|
+
margin: 0;
|
29
|
+
position: relative;
|
30
|
+
top: 25px;
|
31
|
+
font-weight: bold;
|
32
|
+
color: #999;
|
33
|
+
font-size: 16px;
|
34
|
+
background-color: #ddd;
|
35
|
+
-webkit-border-radius: 5px;
|
36
|
+
text-shadow: 0px 1px 0px #fff;
|
37
|
+
}
|
38
|
+
|
39
|
+
.build_all:hover {
|
40
|
+
cursor: pointer;
|
41
|
+
}
|
42
|
+
|
43
|
+
#main {
|
44
|
+
clear: both;
|
45
|
+
}
|
46
|
+
|
47
|
+
.project .status.passed, .build .status.passed {
|
48
|
+
background-color: #0f0;
|
49
|
+
}
|
50
|
+
|
51
|
+
.project .status.failed, .build .status.failed {
|
52
|
+
background-color: #f00;
|
53
|
+
}
|
54
|
+
|
55
|
+
.project .status.running, .build .status.running {
|
56
|
+
background-color: #FDD617;
|
57
|
+
}
|
58
|
+
|
59
|
+
/* LANDING PAGE */
|
60
|
+
.link_wrap {
|
61
|
+
text-decoration: none;
|
62
|
+
display: block;
|
63
|
+
}
|
64
|
+
|
65
|
+
.project {
|
66
|
+
width: 450px;
|
67
|
+
height: 75px;
|
68
|
+
background-color: #ddd;
|
69
|
+
-webkit-border-radius:5px;
|
70
|
+
background-image: -webkit-gradient(linear, left top, left bottom, from(#ddd), to(#ccc));
|
71
|
+
background-image: -webkit-linear-gradient(top, #ddd, #ccc);
|
72
|
+
border: 1px solid #ccc;
|
73
|
+
-webkit-box-shadow: 0px 0px 5px #bbb;
|
74
|
+
box-shadow: 0px 0px 5px #bbb;
|
75
|
+
position: relative;
|
76
|
+
overflow: hidden;
|
77
|
+
margin: 0px 15px 15px 0px;
|
78
|
+
}
|
79
|
+
|
80
|
+
.project h2 {
|
81
|
+
margin: 0px;
|
82
|
+
padding: 0px;
|
83
|
+
color: #666;
|
84
|
+
text-shadow: 0px 1px 0px #eee;
|
85
|
+
position: relative;
|
86
|
+
top: 14px;
|
87
|
+
left: 10px;
|
88
|
+
width: 400px;
|
89
|
+
font-size: 24px;
|
90
|
+
height: 26px;
|
91
|
+
overflow: hidden;
|
92
|
+
}
|
93
|
+
|
94
|
+
.project .status, .build .status {
|
95
|
+
position: absolute;
|
96
|
+
display: block;
|
97
|
+
height: 26px;
|
98
|
+
width: 26px;
|
99
|
+
background-color: #bbb;
|
100
|
+
top: 15px;
|
101
|
+
right: 10px;
|
102
|
+
border-radius: 13px;
|
103
|
+
-webkit-border-radius: 13px;
|
104
|
+
-moz-border-radius: 13px;
|
105
|
+
-webkit-box-shadow: 0px 0px 10px #333 inset;
|
106
|
+
}
|
107
|
+
|
108
|
+
.project .tab_button {
|
109
|
+
text-align: center;
|
110
|
+
position: absolute;
|
111
|
+
color: #888;
|
112
|
+
width: 60px;
|
113
|
+
display: block;
|
114
|
+
height: 18px;
|
115
|
+
line-height: 18px;
|
116
|
+
font-size: 12px;
|
117
|
+
font-weight: bold;
|
118
|
+
border-left: 1px inset #eee;
|
119
|
+
text-shadow: 0px 1px 0px #ccc;
|
120
|
+
text-decoration:none;
|
121
|
+
-webkit-transition: color .25s linear;
|
122
|
+
}
|
123
|
+
|
124
|
+
.project .tab_button:hover {
|
125
|
+
color: #666;
|
126
|
+
}
|
127
|
+
|
128
|
+
.project .build_project {
|
129
|
+
bottom: 0;
|
130
|
+
right: 0;
|
131
|
+
}
|
132
|
+
.project .view_project {
|
133
|
+
bottom: 0;
|
134
|
+
right: 60px;
|
135
|
+
}
|
136
|
+
|
137
|
+
.project .last_build {
|
138
|
+
position: absolute;
|
139
|
+
height: 18px;
|
140
|
+
line-height: 18px;
|
141
|
+
font-size: 12px;
|
142
|
+
bottom: 0;
|
143
|
+
left: 10px;
|
144
|
+
color: #888;
|
145
|
+
text-shadow: 0px 1px 0px #ccc;
|
146
|
+
}
|
147
|
+
|
148
|
+
.project .bottom_tab {
|
149
|
+
position: absolute;
|
150
|
+
width: 450px;
|
151
|
+
height: 18px;
|
152
|
+
background-color: #bbb;
|
153
|
+
-webkit-box-shadow: 0px 2px 3px #aaa inset;
|
154
|
+
bottom: 0;
|
155
|
+
left: 0;
|
156
|
+
-webkit-border-bottom-left-radius: 5px;
|
157
|
+
-webkit-border-bottom-right-radius: 5px;
|
158
|
+
}
|
159
|
+
|
160
|
+
|
161
|
+
|
162
|
+
/* BUILD PAGE */
|
163
|
+
|
164
|
+
.build {
|
165
|
+
position: relative;
|
166
|
+
background-color: #ddd;
|
167
|
+
-webkit-border-radius: 5px;
|
168
|
+
-moz-border-radius: 5px;
|
169
|
+
border-radius: 5px;
|
170
|
+
padding: 0px;
|
171
|
+
margin-bottom: 15px;
|
172
|
+
border: 1px solid #ccc;
|
173
|
+
-webkit-box-shadow: 0px 0px 5px #bbb;
|
174
|
+
box-shadow: 0px 0px 5px #bbb;
|
175
|
+
background-image: -webkit-gradient(linear, left top, left bottom, from(#ddd), to(#ccc));
|
176
|
+
background-image: -webkit-linear-gradient(top, #ddd, #ccc);
|
177
|
+
background-image: -moz-linear-gradient(top, #ddd, #ccc);
|
178
|
+
background-image: -ms-linear-gradient(top, #ddd, #ccc);
|
179
|
+
background-image: -o-linear-gradient(top, #ddd, #ccc);
|
180
|
+
background-image: linear-gradient(top, #ddd, #ccc);
|
181
|
+
filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='#ddd', EndColorStr='#ccc');
|
182
|
+
}
|
183
|
+
|
184
|
+
.build .delete {
|
185
|
+
position: absolute;
|
186
|
+
top: 5px;
|
187
|
+
right: 10px;
|
188
|
+
color: white;
|
189
|
+
font-weight: bold;
|
190
|
+
text-decoration: none;
|
191
|
+
background-color: #bbb;
|
192
|
+
height: 15px;
|
193
|
+
width: 15px;
|
194
|
+
text-align: center;
|
195
|
+
line-height: 15px;
|
196
|
+
border-radius: 8px;
|
197
|
+
box-shadow: 0px 0px 1px #555 inset;
|
198
|
+
font-size: 10px;
|
199
|
+
color: #eee;
|
200
|
+
text-shadow: 0px 1px 0px #777;
|
201
|
+
}
|
202
|
+
|
203
|
+
.build .type {
|
204
|
+
position: absolute;
|
205
|
+
top: 5px;
|
206
|
+
right: 5px;
|
207
|
+
color: #999;
|
208
|
+
text-shadow: 0px 1px 0px #fff;
|
209
|
+
}
|
210
|
+
|
211
|
+
.build .title {
|
212
|
+
margin: 0;
|
213
|
+
padding: 10px;
|
214
|
+
color: #666;
|
215
|
+
text-shadow: 0px 1px 0px #eee;
|
216
|
+
font-weight: bold;
|
217
|
+
}
|
218
|
+
|
219
|
+
|
220
|
+
.build .results_toggle {
|
221
|
+
margin: 0;
|
222
|
+
padding: 0;
|
223
|
+
color: #eee;
|
224
|
+
text-align: center;
|
225
|
+
font-size: 10px;
|
226
|
+
}
|
227
|
+
|
228
|
+
.build .results_toggle:hover {
|
229
|
+
cursor: pointer;
|
230
|
+
}
|
231
|
+
|
232
|
+
.build .results {
|
233
|
+
display: none;
|
234
|
+
}
|
235
|
+
|
236
|
+
.build_details {
|
237
|
+
color: #666;
|
238
|
+
text-shadow: 0px 1px 0px #fff;
|
239
|
+
padding: 5px 10px;
|
240
|
+
}
|
241
|
+
|
242
|
+
.build_details .label {
|
243
|
+
display: inline-block;
|
244
|
+
width: 150px;
|
245
|
+
}
|
246
|
+
|
247
|
+
footer {
|
248
|
+
clear: both;
|
249
|
+
}
|
250
|
+
|
251
|
+
pre.terminal {
|
252
|
+
border: 1px solid black;
|
253
|
+
background-color: #333;
|
254
|
+
color: white;
|
255
|
+
padding: 5px;
|
256
|
+
overflow: auto;
|
257
|
+
word-wrap: break-word;
|
258
|
+
margin: 0px;
|
259
|
+
}
|
260
|
+
|
261
|
+
pre.terminal code {
|
262
|
+
font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace;
|
263
|
+
background-color: #333;
|
264
|
+
}
|
@@ -0,0 +1,55 @@
|
|
1
|
+
var client;
|
2
|
+
var subscription;
|
3
|
+
|
4
|
+
function handleMessage(message){
|
5
|
+
var project = message.project;
|
6
|
+
var status = message.status;
|
7
|
+
var last_build = message.last_build;
|
8
|
+
$('#'+ project + ' .status').removeClass('running passed failed').addClass(status);
|
9
|
+
if(typeof(last_build) != 'undefined') {
|
10
|
+
$('.last_build').html("Latest Build: " + last_build);
|
11
|
+
}
|
12
|
+
}
|
13
|
+
|
14
|
+
function initializeFaye(channel){
|
15
|
+
var ch = (typeof(channel) == 'undefined') ? '/all' : channel;
|
16
|
+
client = new Faye.Client('/faye');
|
17
|
+
var subscription = client.subscribe(ch, function(message){
|
18
|
+
handleMessage(message);
|
19
|
+
});
|
20
|
+
}
|
21
|
+
|
22
|
+
function setupBuildToggles(){
|
23
|
+
$('.results_toggle').click(function(){
|
24
|
+
$(this).siblings('.results').slideToggle();
|
25
|
+
});
|
26
|
+
}
|
27
|
+
|
28
|
+
|
29
|
+
function setupBuildButtons(){
|
30
|
+
$('.build_all').click(function(){
|
31
|
+
$.post('/command/build_all');
|
32
|
+
});
|
33
|
+
|
34
|
+
$('.build_project').click(function(){
|
35
|
+
var project = $(this).closest('.project').attr('id');
|
36
|
+
if(typeof(project) != 'undefined'){
|
37
|
+
$.post('/' + project + '/build');
|
38
|
+
}
|
39
|
+
});
|
40
|
+
}
|
41
|
+
|
42
|
+
function linkProjectCells(){
|
43
|
+
$('.project').click(function(){
|
44
|
+
var project = $(this).attr('id');
|
45
|
+
if(typeof(project) != 'undefined'){
|
46
|
+
window.location = "/" + project;
|
47
|
+
}
|
48
|
+
});
|
49
|
+
}
|
50
|
+
|
51
|
+
$(function(){
|
52
|
+
initializeFaye();
|
53
|
+
setupBuildToggles(); //if we're on an individual project page.
|
54
|
+
setupBuildButtons();
|
55
|
+
});
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'faye'
|
3
|
+
require 'erb'
|
4
|
+
|
5
|
+
module VetCI
|
6
|
+
class Server < Sinatra::Base
|
7
|
+
|
8
|
+
lib_dir = File.dirname(File.expand_path(__FILE__))
|
9
|
+
|
10
|
+
set :views, "#{lib_dir}/views"
|
11
|
+
set :public, "#{lib_dir}/public"
|
12
|
+
set :static, true
|
13
|
+
|
14
|
+
use Faye::RackAdapter, :mount => '/faye',
|
15
|
+
:timeout => 25
|
16
|
+
|
17
|
+
helpers do
|
18
|
+
# Thanks CI-Joe, and Integrity by association.
|
19
|
+
def ansi_color_codes(string)
|
20
|
+
string.gsub("\e[0m", '</span>').
|
21
|
+
gsub(/\e\[(\d+)m/, "<span class=\"color\\1\">")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Triggers that a project should be built
|
26
|
+
post '/:project/build' do
|
27
|
+
project = Project.named(params[:project])
|
28
|
+
project.build(env['faye.client'], params[:payload]) unless project.nil?
|
29
|
+
status 200
|
30
|
+
end
|
31
|
+
|
32
|
+
# DELETES a project's build
|
33
|
+
#get '/:project/builds/:build_id/destroy' do
|
34
|
+
# redirect "/#{params[:project]}"
|
35
|
+
#end
|
36
|
+
|
37
|
+
#Renders the project's details page.
|
38
|
+
get '/:project' do
|
39
|
+
@project = Project.named(params[:project])
|
40
|
+
pass if @project.nil?
|
41
|
+
@builds = @project.pagedBuilds(params[:page], 5)
|
42
|
+
erb :project
|
43
|
+
end
|
44
|
+
|
45
|
+
post '/command/build_all' do
|
46
|
+
Project.projects.each do |name, project|
|
47
|
+
project.build(env['faye.client'])
|
48
|
+
end
|
49
|
+
status 200
|
50
|
+
end
|
51
|
+
|
52
|
+
get '/' do
|
53
|
+
@projects = Project.projects
|
54
|
+
erb :index
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.start
|
58
|
+
VetCI::Server.run! :host => '0.0.0.0', :port => 4567
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/vet-ci/setup.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# Our setup script.
|
2
|
+
|
3
|
+
module VetCI
|
4
|
+
class Setup
|
5
|
+
class << self
|
6
|
+
def go
|
7
|
+
unless Dir.exists? '.vetci'
|
8
|
+
Dir.mkdir '.vetci'
|
9
|
+
end
|
10
|
+
|
11
|
+
unless File.exists? 'Vetfile'
|
12
|
+
puts "No Vetfile found. Please create one and try again."
|
13
|
+
Process.exit
|
14
|
+
end
|
15
|
+
|
16
|
+
begin
|
17
|
+
parameters = YAML.load(File.read('Vetfile'))
|
18
|
+
rescue
|
19
|
+
puts "Unable to load Vetfile. Check its syntax and try again."
|
20
|
+
Process.exit
|
21
|
+
end
|
22
|
+
|
23
|
+
Project.datastore = PStore.new(File.join('.vetci', 'projects.pstore'))
|
24
|
+
|
25
|
+
Project.parse_vetfile_contents(parameters["projects"])
|
26
|
+
|
27
|
+
return parameters
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/vet-ci/util.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
<% @projects.each do |name, project| %>
|
2
|
+
<div class="project" id="<%= project.name %>">
|
3
|
+
<h2><%= project.name %></h2>
|
4
|
+
<span class="status <%= project.last_build_status %>"></span>
|
5
|
+
<div class="bottom_tab">
|
6
|
+
<span class="last_build">Latest Build: <%= project.builds.first.dashboard_time unless project.builds.first.nil? %></span>
|
7
|
+
<a class="tab_button view_project" href="/<%= project.name %>">Details</a>
|
8
|
+
<a class="tab_button build_project" href="#">Build</a>
|
9
|
+
</div>
|
10
|
+
</div>
|
11
|
+
<% end %>
|
@@ -0,0 +1,22 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>VetCI</title>
|
5
|
+
<link rel="stylesheet" href="/css/style.css" type="text/css" charset="utf-8">
|
6
|
+
</head>
|
7
|
+
<body>
|
8
|
+
<header>
|
9
|
+
<h1 class="logo"><a href="/">VetCI</a></h1>
|
10
|
+
<button class="build_all">Build All</button>
|
11
|
+
</header>
|
12
|
+
<section id="main">
|
13
|
+
<%= yield %>
|
14
|
+
</section>
|
15
|
+
<footer>
|
16
|
+
|
17
|
+
</footer>
|
18
|
+
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js" type="text/javascript" charset="utf-8"></script>
|
19
|
+
<script src="/faye.js" type="text/javascript" charset="utf-8"></script>
|
20
|
+
<script src="/js/application.js" type="text/javascript" charset="utf-8"></script>
|
21
|
+
</body>
|
22
|
+
</html>
|
@@ -0,0 +1,38 @@
|
|
1
|
+
<div class="project" id="<%= @project.name %>">
|
2
|
+
<h2>
|
3
|
+
<%= @project.name %>
|
4
|
+
</h2>
|
5
|
+
<span class="status <%= @project.last_build_status %>"></span>
|
6
|
+
<div class="bottom_tab">
|
7
|
+
<span class="last_build">Latest Build: <%= @project.builds.first.dashboard_time unless @project.builds.first.nil? %></span>
|
8
|
+
<a class="tab_button build_project" href="#">Build</a>
|
9
|
+
</div>
|
10
|
+
</div>
|
11
|
+
|
12
|
+
<% @builds.each do |build| %>
|
13
|
+
<div class="build">
|
14
|
+
<span class="status <%= build.status_class %>"></span>
|
15
|
+
<p class="title">
|
16
|
+
Build Time: <%= build.friendly_time %>
|
17
|
+
</p>
|
18
|
+
|
19
|
+
<div class="build_details">
|
20
|
+
<div><span class="label">Committer:</span><%= build.commit_info["committer"]["name"]%></div>
|
21
|
+
<div><span class="label">Comment:</span><%= build.commit_info["message"]%></div>
|
22
|
+
</div>
|
23
|
+
|
24
|
+
<% unless build.payload.nil? %>
|
25
|
+
<div class="build_details">
|
26
|
+
<div><span class="label">Github Project:</span><%= build.payload["repository"]["name"] %></div>
|
27
|
+
<div><span class="label">Compare URL:</span><%= build.payload["compare"]%></div>
|
28
|
+
</div>
|
29
|
+
<% end %>
|
30
|
+
|
31
|
+
<div class="results">
|
32
|
+
<pre class="terminal"><code><%=ansi_color_codes escape_html(build.output) %></code></pre>
|
33
|
+
</div>
|
34
|
+
<p class="results_toggle">
|
35
|
+
show more
|
36
|
+
</p>
|
37
|
+
</div>
|
38
|
+
<% end %>
|
data/spec/vet_spec.rb
ADDED
data/vet-ci.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "vet-ci/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "vet-ci"
|
7
|
+
s.version = Vet::Ci::VERSION
|
8
|
+
s.authors = ["Lee Adkins"]
|
9
|
+
s.email = ["lee@ravsonic.com"]
|
10
|
+
s.homepage = "http://github.com/leeadkins/vet-ci"
|
11
|
+
s.summary = %q{Make sure your little code pets get their checkups.}
|
12
|
+
s.description = %q{VetCI is a continuous intergration system. It's a work in progress, so documentation is slim right now. You'll probably want to wait until at least 0.0.5 to actually use it.}
|
13
|
+
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
#s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
s.executables = %w( vet-ci )
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
s.add_runtime_dependency 'sinatra'
|
24
|
+
s.add_runtime_dependency 'grit'
|
25
|
+
s.add_runtime_dependency 'choice'
|
26
|
+
s.add_runtime_dependency 'faye'
|
27
|
+
|
28
|
+
s.add_development_dependency 'rake'
|
29
|
+
s.add_development_dependency 'rspec'
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: vet-ci
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Lee Adkins
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-07-26 00:00:00.000000000 -04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: sinatra
|
17
|
+
requirement: &2152757940 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *2152757940
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: grit
|
28
|
+
requirement: &2152757300 !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: *2152757300
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: choice
|
39
|
+
requirement: &2152756680 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
type: :runtime
|
46
|
+
prerelease: false
|
47
|
+
version_requirements: *2152756680
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: faye
|
50
|
+
requirement: &2152755980 !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
type: :runtime
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: *2152755980
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: rake
|
61
|
+
requirement: &2152755560 !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ! '>='
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
type: :development
|
68
|
+
prerelease: false
|
69
|
+
version_requirements: *2152755560
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: rspec
|
72
|
+
requirement: &2152755140 !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
type: :development
|
79
|
+
prerelease: false
|
80
|
+
version_requirements: *2152755140
|
81
|
+
description: VetCI is a continuous intergration system. It's a work in progress, so
|
82
|
+
documentation is slim right now. You'll probably want to wait until at least 0.0.5
|
83
|
+
to actually use it.
|
84
|
+
email:
|
85
|
+
- lee@ravsonic.com
|
86
|
+
executables:
|
87
|
+
- vet-ci
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- .gitignore
|
92
|
+
- Gemfile
|
93
|
+
- README.md
|
94
|
+
- Rakefile
|
95
|
+
- Vetfile
|
96
|
+
- Vetfile.example
|
97
|
+
- bin/vet-ci
|
98
|
+
- lib/vet-ci.rb
|
99
|
+
- lib/vet-ci/build.rb
|
100
|
+
- lib/vet-ci/project.rb
|
101
|
+
- lib/vet-ci/public/css/style.css
|
102
|
+
- lib/vet-ci/public/js/application.js
|
103
|
+
- lib/vet-ci/server.rb
|
104
|
+
- lib/vet-ci/setup.rb
|
105
|
+
- lib/vet-ci/util.rb
|
106
|
+
- lib/vet-ci/version.rb
|
107
|
+
- lib/vet-ci/views/index.erb
|
108
|
+
- lib/vet-ci/views/layout.erb
|
109
|
+
- lib/vet-ci/views/project.erb
|
110
|
+
- spec/vet_spec.rb
|
111
|
+
- vet-ci.gemspec
|
112
|
+
has_rdoc: true
|
113
|
+
homepage: http://github.com/leeadkins/vet-ci
|
114
|
+
licenses: []
|
115
|
+
post_install_message:
|
116
|
+
rdoc_options: []
|
117
|
+
require_paths:
|
118
|
+
- lib
|
119
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
120
|
+
none: false
|
121
|
+
requirements:
|
122
|
+
- - ! '>='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
segments:
|
126
|
+
- 0
|
127
|
+
hash: 4042315856108701698
|
128
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
segments:
|
135
|
+
- 0
|
136
|
+
hash: 4042315856108701698
|
137
|
+
requirements: []
|
138
|
+
rubyforge_project:
|
139
|
+
rubygems_version: 1.6.2
|
140
|
+
signing_key:
|
141
|
+
specification_version: 3
|
142
|
+
summary: Make sure your little code pets get their checkups.
|
143
|
+
test_files:
|
144
|
+
- spec/vet_spec.rb
|