This repository has been archived by the owner on Apr 14, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathwaylon.rb
259 lines (219 loc) · 7.07 KB
/
waylon.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
require 'sinatra'
require 'cgi'
require 'date'
require 'jenkins_api_client'
require 'yaml'
require 'deterministic'
$LOAD_PATH << File.join(File.dirname(__FILE__), 'lib')
class Waylon < Sinatra::Application
require 'waylon/root_config'
include Deterministic
helpers do
def h(text)
CGI::escape(text)
end
# get_views() does just that, gets a list of views from
# the config file and returns an array of strings.
def get_views()
gen_config.views.map(&:name)
end
def gen_config
root = File.dirname(__FILE__)
config = YAML.load_file(File.join(root, 'config/waylon.yml'))
Waylon::RootConfig.from_hash(config)
end
# weather() returns an img src, alt, and title, based on build stability
def weather(score)
case score.to_i
when 100
{
'src' => '/img/sun.png',
'alt' => '[sun]',
'title' => 'No recent builds failed'
}
when 80
{
'src' => '/img/cloud.png',
'alt' => '[cloud]',
'title' => '1 of the last 5 builds failed'
}
else
{
'src' => '/img/umbrella.png',
'alt' => '[umbrella]',
'title' => '2 or more of the last 5 builds failed'
}
end
end
def manadic(monad)
if monad.success?
status 200
body(monad.value.to_json)
elsif monad.value.is_a? Waylon::Errors::NotFound
status 404
body({"errors" => [monad.value.message]}.to_json)
else
raise monad.value
end
end
end
# Landing page (`/`)
# Print a list of views available on this Waylon instance.
get '/' do
@this_view = 'index'
erb :base do
erb :index
end
end
# Individual views (`/view/foo`)
# These are the pages the user actually interacts with. Code here and
# in the ERB templates for `view/foo` should be _just enough_ to refresh
# the contents of <div class="waylon container"> with the latest data.
get '/view/:name' do
config = gen_config
@this_view = CGI.unescape(params[:name])
@refresh_interval = config.app_config.refresh_interval
view_config = config.views.find { |view| view.name == @this_view }
if view_config.nil?
@errors = [ "Couldn't find view #{@this_view}!"]
end
erb :base do
erb :view
end
end
get '/x/:name' do
@view_name = CGI.unescape(params[:name])
erb :base do
erb :x
end
end
# Individual views' data (`/view/foo/data`)
# When navigating to 'view/foo/data', queries config/waylon.yml for that
# view's config (servers to connect to, and jobs to display). Returns an
# HTML table for the jQuery in '/view/:name' to load and display.
get '/view/:name/data' do
@this_view = CGI.unescape(params[:name])
# For each Jenkins instance in a view, connect to the server, and get the
# status of the jobs specified in the config. Append job details to each
# applicable array: successful, failed, and building.
@errors = []
@warnings = []
@failed_jobs = []
@failed_builds = []
@building_jobs = []
@job_progress = []
@successful_jobs = []
view_config = gen_config.views.find { |view| view.name == @this_view }
if view_config.nil?
@errors << "Couldn't find view #{@this_view}!"
halt 404
end
view_config.servers.each do |server|
begin
server.verify_client!
rescue SocketError
@errors << "Unable to connect to server: #{server}"
next
rescue Errno::ETIMEDOUT
@errors << "Timed out while connecting to server: #{server}"
next
end
server.jobs.each do |job|
# jenkins_api_client won't throw an Unauthorized exception until
# we really try doing something, like calling list_details()
begin
job.query!
job_details = job.job_details
rescue JenkinsApi::Exceptions::Unauthorized
@errors << "Incorrect username or password for server: #{server.url}"
break
rescue JenkinsApi::Exceptions::NotFound
@warnings << "Non-existent job \"#{job.name}\" on server #{server.url}"
next
end
case job.status
when 'running'
@building_jobs << job_details
@job_progress << {
'job_name' => job.name,
'progress_pct' => job.progress_pct,
'eta' => job.eta
}
when 'failure'
@failed_jobs << job_details
@failed_builds << {
'job_name' => job.name,
'build_number' => job.last_build_num,
'is_under_investigation' => job.under_investigation?
}
when 'success'
@successful_jobs << job_details
end
end
end
erb :data
end
get '/api/view/:view.json' do
view_name = CGI.unescape(params[:view])
manadic(Either.attempt_all(self) do
try { gen_config.view(view_name) }
try { |view| view.to_config }
end)
end
get '/api/view/:view/server/:server.json' do
view_name = CGI.unescape(params[:view])
server_name = CGI.unescape(params[:server])
manadic(Either.attempt_all(self) do
try { gen_config.view(view_name) }
try { |view| view.server(server_name) }
try { |server| server.to_config }
end)
end
get '/api/view/:view/server/:server/jobs.json' do
view_name = CGI.unescape(params[:view])
server_name = CGI.unescape(params[:server])
manadic(Either.attempt_all(self) do
try { gen_config.view(view_name) }
try { |view| view.server(server_name) }
try { |server| server.jobs.map(&:name) }
end)
end
get '/api/view/:view/server/:server/job/:job.json' do
view_name = CGI.unescape(params[:view])
server_name = CGI.unescape(params[:server])
job_name = CGI.unescape(params[:job])
manadic(Either.attempt_all(self) do
try { gen_config.view(view_name) }
try { |view| view.server(server_name) }
try { |server| server.job(job_name) }
try { |job| job.query!.to_hash }
end)
end
# Investigate a failed build
#
get '/view/:view/:server/:job/:build/investigate' do
server = CGI.unescape(params[:server])
job = CGI.unescape(params[:job])
build = CGI.unescape(params[:build])
postdata = { 'description' => 'Under investigation' }
prefix = "/job/#{job}/#{build}"
# We need to get the server URL from the configuration file, based on just
# the hostname, to keep the server's full URL (and all its special chars)
# out of the URI visible to the user. This whole thing is a hack and should
# be improved someday.
gen_config.views.each do |view|
view.servers.each do |config_server|
if config_server.url =~ /#{server}/
config_server.client.api_post_request("#{prefix}/submitDescription", postdata)
redirect "#{config_server.url}/#{prefix}/"
end
end
end
@errors = []
@errors << "We couldn't find a server in our config for #{server}!"
@this_view = 'index'
erb :base do
erb :index
end
end
end