build-buddy 1.12.2 → 1.14.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/build-buddy +1 -2
- data/lib/build_buddy.rb +1 -1
- data/lib/build_buddy/build_data.rb +37 -4
- data/lib/build_buddy/builder.rb +78 -34
- data/lib/build_buddy/config.rb +5 -7
- data/lib/build_buddy/recorder.rb +19 -230
- data/lib/build_buddy/scheduler.rb +28 -7
- data/lib/build_buddy/server.rb +17 -115
- data/lib/build_buddy/slacker.rb +68 -23
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7db95cc8816c610a427233d3082ed8209c9a65b6
|
4
|
+
data.tar.gz: 235ad6277e52ad8be9747933133476562af77633
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: af52e4b43610c3ff26c5d76c99c3b069f7ba393cb374a020e8954b75ea890447a950451f52bd96da1820b3afb0b85ca2eb17d707739ba1cec51d45f84ae63a91
|
7
|
+
data.tar.gz: a82009f93b686be80f338175408dc5d03bc8374189f60985fbd44112e4adce023205733f6b5a94e0e7d03a04d3921688dd0040c0f1081590fa582ffbf04b62a5
|
data/bin/build-buddy
CHANGED
@@ -19,8 +19,7 @@ module BuildBuddy
|
|
19
19
|
|
20
20
|
load config_file_name
|
21
21
|
|
22
|
-
Dir.mkdir(Config.
|
23
|
-
Dir.mkdir(Config.hud_image_dir) unless Dir.exist?(Config.hud_image_dir)
|
22
|
+
Dir.mkdir(Config.build_output_dir) unless Dir.exist?(Config.build_output_dir)
|
24
23
|
|
25
24
|
Slack.configure do |config|
|
26
25
|
config.token = Config.slack_api_token
|
data/lib/build_buddy.rb
CHANGED
@@ -1,6 +1,25 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
1
3
|
module BuildBuddy
|
4
|
+
@@bb_id = 100
|
5
|
+
@@bb_id_mutex = Mutex.new
|
6
|
+
|
7
|
+
def self.bb_id
|
8
|
+
@@bb_id
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.bb_id=(bb_id)
|
12
|
+
@@bb_id = bb_id
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.bb_id_mutex
|
16
|
+
@@bb_id_mutex
|
17
|
+
end
|
18
|
+
|
2
19
|
class BuildData
|
3
20
|
attr_accessor :_id # Mongo ID
|
21
|
+
attr_accessor :bb_id # Build Buddy id
|
22
|
+
attr_accessor :create_time
|
4
23
|
attr_accessor :type # one of :master, :release or :pull_request
|
5
24
|
attr_accessor :repo_full_name
|
6
25
|
attr_accessor :branch
|
@@ -24,18 +43,32 @@ module BuildBuddy
|
|
24
43
|
end
|
25
44
|
end
|
26
45
|
|
27
|
-
|
28
|
-
|
46
|
+
BuildBuddy::bb_id_mutex.synchronize {
|
47
|
+
bb_id = BuildBuddy::bb_id
|
48
|
+
@bb_id = 'BB-' + bb_id.to_s
|
49
|
+
BuildBuddy::bb_id = bb_id + 1
|
50
|
+
}
|
51
|
+
@create_time = Time.now.utc
|
29
52
|
end
|
30
53
|
|
31
54
|
def to_h
|
32
55
|
hash = {}
|
33
56
|
instance_variables.each {|var| hash[var.to_s.delete("@")] = instance_variable_get(var) }
|
57
|
+
# Remove the bb_id field as it's only meaningful while the build-buddy is running
|
58
|
+
hash.delete(:bb_id)
|
34
59
|
hash
|
35
60
|
end
|
36
61
|
|
37
62
|
def server_log_uri
|
38
|
-
|
63
|
+
BuildData.server_log_uri(@_id)
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.server_log_uri(id)
|
67
|
+
Config.server_base_uri + '/build/' + id.to_s + '/log.html'
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.server_report_uri(id)
|
71
|
+
Config.server_base_uri + '/build/' + id.to_s + '/report.html'
|
39
72
|
end
|
40
73
|
|
41
74
|
def status_verb
|
@@ -52,7 +85,7 @@ module BuildBuddy
|
|
52
85
|
|
53
86
|
def log_file_name
|
54
87
|
return nil if @start_time.nil?
|
55
|
-
File.join(Config.
|
88
|
+
File.join(Config.build_output_dir, "#{@start_time.strftime('%Y%m%d-%H%M%S')}.log")
|
56
89
|
end
|
57
90
|
end
|
58
91
|
end
|
data/lib/build_buddy/builder.rb
CHANGED
@@ -16,6 +16,8 @@ module BuildBuddy
|
|
16
16
|
@gid = nil
|
17
17
|
@watcher = nil
|
18
18
|
@metrics_tempfile = nil
|
19
|
+
@build_output_dir = nil
|
20
|
+
@log_file_name = nil
|
19
21
|
end
|
20
22
|
|
21
23
|
def start_build(build_data)
|
@@ -26,6 +28,10 @@ module BuildBuddy
|
|
26
28
|
@build_data = build_data
|
27
29
|
@metrics_tempfile = Tempfile.new('build-metrics')
|
28
30
|
@metrics_tempfile.close()
|
31
|
+
@build_output_dir = File.join(Config.build_output_dir, @build_data._id.to_s)
|
32
|
+
@log_file_name = File.join(@build_output_dir, "log.txt")
|
33
|
+
|
34
|
+
FileUtils.mkdir(@build_output_dir)
|
29
35
|
|
30
36
|
repo_parts = @build_data.repo_full_name.split('/')
|
31
37
|
git_repo_owner = repo_parts[0]
|
@@ -33,8 +39,8 @@ module BuildBuddy
|
|
33
39
|
env = {}
|
34
40
|
build_script = %q(#!/bin/bash
|
35
41
|
|
36
|
-
if [[ -z "$
|
37
|
-
echo Must set
|
42
|
+
if [[ -z "$BB_GIT_REPO_OWNER" || -z "$BB_GIT_REPO_NAME" || -z "$BB_BUILD_SCRIPT" ]]; then
|
43
|
+
echo Must set BB_GIT_REPO_OWNER, BB_GIT_REPO_NAME, BB_GIT_PULL_REQUEST and BB_BUILD_SCRIPT before calling
|
38
44
|
exit 1
|
39
45
|
fi
|
40
46
|
)
|
@@ -42,14 +48,14 @@ fi
|
|
42
48
|
case build_data.type
|
43
49
|
when :pull_request
|
44
50
|
build_script += %q(
|
45
|
-
if [[ -z "$
|
46
|
-
echo Must set
|
51
|
+
if [[ -z "$BB_GIT_PULL_REQUEST" ]]; then
|
52
|
+
echo Must set BB_GIT_PULL_REQUEST before calling
|
47
53
|
fi
|
48
54
|
)
|
49
55
|
when :branch
|
50
56
|
build_script += %q(
|
51
|
-
if [[ -z "$
|
52
|
-
echo Must set
|
57
|
+
if [[ -z "$BB_GIT_BRANCH" ]]; then
|
58
|
+
echo Must set BB_GIT_BRANCH before calling
|
53
59
|
fi
|
54
60
|
)
|
55
61
|
else
|
@@ -57,18 +63,18 @@ fi
|
|
57
63
|
end
|
58
64
|
|
59
65
|
build_script += %q(
|
60
|
-
if [[ -d ${
|
61
|
-
echo WARNING: Deleting old clone directory $(pwd)/${
|
62
|
-
rm -rf ${
|
66
|
+
if [[ -d ${BB_GIT_REPO_NAME} ]]; then
|
67
|
+
echo WARNING: Deleting old clone directory $(pwd)/${BB_GIT_REPO_NAME}
|
68
|
+
rm -rf ${BB_GIT_REPO_NAME}
|
63
69
|
fi
|
64
70
|
|
65
|
-
echo Pulling sources to $(pwd)/${
|
66
|
-
if ! git clone git@github.com:${
|
71
|
+
echo Pulling sources to $(pwd)/${BB_GIT_REPO_NAME}
|
72
|
+
if ! git clone git@github.com:${BB_GIT_REPO_OWNER}/${BB_GIT_REPO_NAME}.git ${BB_GIT_REPO_NAME}; then
|
67
73
|
echo ERROR: Unable to clone repository
|
68
74
|
exit 1
|
69
75
|
fi
|
70
76
|
|
71
|
-
cd ${
|
77
|
+
cd ${BB_GIT_REPO_NAME}
|
72
78
|
)
|
73
79
|
|
74
80
|
build_root_dir = nil
|
@@ -77,35 +83,37 @@ cd ${GIT_REPO_NAME}
|
|
77
83
|
when :pull_request
|
78
84
|
build_root_dir = expand_vars(Config.pull_request_root_dir)
|
79
85
|
env.merge!({
|
80
|
-
"
|
81
|
-
"
|
86
|
+
"BB_GIT_PULL_REQUEST" => build_data.pull_request.to_s,
|
87
|
+
"BB_BUILD_SCRIPT" => Config.pull_request_build_script
|
82
88
|
})
|
83
89
|
# See https://gist.github.com/piscisaureus/3342247
|
84
90
|
build_script += %q(
|
85
|
-
echo Switching to pr/${
|
91
|
+
echo Switching to pr/${BB_GIT_PULL_REQUEST} branch
|
86
92
|
git config --add remote.origin.fetch '+refs/pull/*/head:refs/remotes/origin/pr/*'
|
87
93
|
git fetch -q origin
|
88
|
-
git checkout pr/$
|
94
|
+
git checkout pr/$BB_GIT_PULL_REQUEST
|
89
95
|
)
|
90
96
|
when :branch
|
91
97
|
build_root_dir = expand_vars(Config.branch_root_dir)
|
92
98
|
env.merge!({
|
93
|
-
"
|
94
|
-
"
|
99
|
+
"BB_GIT_BRANCH" => build_data.branch.to_s,
|
100
|
+
"BB_BUILD_SCRIPT" => Config.branch_build_script
|
95
101
|
})
|
96
102
|
build_script += %q(
|
97
|
-
echo Switching to ${
|
98
|
-
git checkout ${
|
103
|
+
echo Switching to ${BB_GIT_BRANCH} branch
|
104
|
+
git checkout ${BB_GIT_BRANCH}
|
99
105
|
)
|
100
106
|
end
|
101
107
|
|
102
108
|
build_script += %q(
|
103
|
-
source ${
|
109
|
+
source ${BB_BUILD_SCRIPT}
|
104
110
|
)
|
105
111
|
env.merge!({
|
106
|
-
"
|
107
|
-
"
|
108
|
-
"
|
112
|
+
"BB_GIT_REPO_OWNER" => git_repo_owner,
|
113
|
+
"BB_GIT_REPO_NAME" => git_repo_name,
|
114
|
+
"BB_METRICS_DATA_FILE" => @metrics_tempfile.path,
|
115
|
+
"BB_BUILD_OUTPUT_DIR" => @build_output_dir,
|
116
|
+
"BB_MONGO_URI" => Config.mongo_uri,
|
109
117
|
"RBENV_DIR" => nil,
|
110
118
|
"RBENV_VERSION" => nil,
|
111
119
|
"RBENV_HOOK_PATH" => nil,
|
@@ -115,12 +123,11 @@ source ${BUILD_SCRIPT}
|
|
115
123
|
|
116
124
|
unless build_data.flags.nil?
|
117
125
|
build_data.flags.each do |flag|
|
118
|
-
|
126
|
+
env["BB_BUILD_FLAG_#{flag.to_s.upcase}"] = 1
|
119
127
|
end
|
120
128
|
end
|
121
129
|
|
122
130
|
@build_data.start_time = Time.now.utc
|
123
|
-
log_file_name = @build_data.log_file_name
|
124
131
|
|
125
132
|
# Ensure build root and git user directory exists
|
126
133
|
clone_dir = File.join(build_root_dir, git_repo_owner)
|
@@ -139,10 +146,10 @@ source ${BUILD_SCRIPT}
|
|
139
146
|
|
140
147
|
# Run the build script
|
141
148
|
Bundler.with_clean_env do
|
142
|
-
@pid = Process.spawn(env, "bash #{script_filename}", :pgroup => true, :chdir => clone_dir, [:out, :err] => log_file_name)
|
149
|
+
@pid = Process.spawn(env, "bash #{script_filename}", :pgroup => true, :chdir => clone_dir, [:out, :err] => @log_file_name)
|
143
150
|
@gid = Process.getpgid(@pid)
|
144
151
|
end
|
145
|
-
info "Running build script (pid #{@pid}, gid #{@gid}) : Log #{log_file_name}"
|
152
|
+
info "Running build script (pid #{@pid}, gid #{@gid}) : Log #{@log_file_name}"
|
146
153
|
|
147
154
|
if @watcher
|
148
155
|
@watcher.terminate
|
@@ -152,6 +159,10 @@ source ${BUILD_SCRIPT}
|
|
152
159
|
@watcher.async.watch_pid
|
153
160
|
end
|
154
161
|
|
162
|
+
def self.get_log_file_name(build_data)
|
163
|
+
File.join(Config.build_output_dir, build_data._id.to_s, "log.txt")
|
164
|
+
end
|
165
|
+
|
155
166
|
def process_done(status)
|
156
167
|
@build_data.end_time = Time.now.utc
|
157
168
|
@build_data.termination_type = (status.signaled? ? :killed : :exited)
|
@@ -180,18 +191,51 @@ source ${BUILD_SCRIPT}
|
|
180
191
|
@build_data.server_log_uri)
|
181
192
|
end
|
182
193
|
|
194
|
+
# Create a log.html file
|
195
|
+
log_contents = ''
|
196
|
+
File.open(@log_file_name) do |io|
|
197
|
+
log_contents = io.read
|
198
|
+
end
|
199
|
+
html = %Q(
|
200
|
+
<!doctype html>
|
201
|
+
<html lang="en">
|
202
|
+
<head>
|
203
|
+
<meta charset="utf-8">
|
204
|
+
<title>Build Log</title>
|
205
|
+
<meta name="description" content="Build Log">
|
206
|
+
<style>
|
207
|
+
body {
|
208
|
+
background-color: black;
|
209
|
+
color: #f0f0f0;
|
210
|
+
}
|
211
|
+
pre {
|
212
|
+
font-family: "Menlo", "Courier New";
|
213
|
+
font-size: 10pt;
|
214
|
+
}
|
215
|
+
</style>
|
216
|
+
</head>
|
217
|
+
|
218
|
+
<body>
|
219
|
+
<pre>
|
220
|
+
#{log_contents}
|
221
|
+
</pre>
|
222
|
+
</body>
|
223
|
+
</html>
|
224
|
+
)
|
225
|
+
File.open(File.join(@build_output_dir, "log.html"), 'w') { |f| f.write(html)}
|
226
|
+
|
183
227
|
# Log the build completion and clean-up
|
184
228
|
info "Process #{status.pid} #{@build_data.termination_type == :killed ? 'was terminated' : "exited (#{@build_data.exit_code})"}"
|
185
229
|
Celluloid::Actor[:scheduler].async.on_build_completed(@build_data)
|
186
230
|
@watcher.terminate
|
187
231
|
@watcher = nil
|
188
232
|
|
189
|
-
# Delete older log
|
190
|
-
|
191
|
-
while
|
192
|
-
|
193
|
-
FileUtils.
|
194
|
-
info "Removing oldest log
|
233
|
+
# Delete older log directories
|
234
|
+
log_dir_names = Dir.entries(Config.build_output_dir).select { |f| !f.match(/\d{8}-\d{6}\.log/).nil? }.sort()
|
235
|
+
while log_dir_names.count > Config.num_saved_build_outputs
|
236
|
+
dir_name = log_dir_names.shift
|
237
|
+
FileUtils.rm_rf(File.join(Config.build_output_dir, dir_name))
|
238
|
+
info "Removing oldest log directory #{dir_name}"
|
195
239
|
end
|
196
240
|
end
|
197
241
|
|
data/lib/build_buddy/config.rb
CHANGED
@@ -9,9 +9,10 @@ module BuildBuddy
|
|
9
9
|
:github_api_token,
|
10
10
|
:slack_api_token,
|
11
11
|
:slack_build_channel,
|
12
|
+
:slack_pr_channel,
|
12
13
|
:slack_builders,
|
13
|
-
:
|
14
|
-
:
|
14
|
+
:build_output_dir,
|
15
|
+
:num_saved_build_outputs,
|
15
16
|
:pull_request_build_script,
|
16
17
|
:branch_build_script,
|
17
18
|
:pull_request_root_dir,
|
@@ -20,8 +21,6 @@ module BuildBuddy
|
|
20
21
|
:kill_build_after_mins,
|
21
22
|
:server_base_uri,
|
22
23
|
:mongo_uri,
|
23
|
-
:hud_secret_token,
|
24
|
-
:hud_image_dir,
|
25
24
|
]
|
26
25
|
attr_accessor(*ATTRS)
|
27
26
|
end
|
@@ -31,10 +30,9 @@ module BuildBuddy
|
|
31
30
|
config.github_webhook_port = 4567
|
32
31
|
config.kill_build_after_mins = 30
|
33
32
|
config.mongo_uri = 'mongodb://localhost:27017/build-buddy'
|
34
|
-
config.
|
33
|
+
config.num_saved_build_outputs = 30
|
35
34
|
block_given? ? yield(Config) : Config
|
36
|
-
config.
|
37
|
-
config.hud_image_dir = File.expand_path(Config.hud_image_dir.gsub(/\$(\w+)/) { ENV[$1] })
|
35
|
+
config.build_output_dir = File.expand_path(Config.build_output_dir.gsub(/\$(\w+)/) { ENV[$1] })
|
38
36
|
Config::ATTRS.map {|attr| ('@' + attr.to_s).to_sym }.each {|var|
|
39
37
|
if config.instance_variable_get(var).nil?
|
40
38
|
raise "Config value '#{var.to_s.delete('@')}' not set"
|
data/lib/build_buddy/recorder.rb
CHANGED
@@ -2,7 +2,9 @@ require 'rubygems'
|
|
2
2
|
require 'bundler'
|
3
3
|
require 'celluloid'
|
4
4
|
require 'mongo'
|
5
|
+
require 'bson'
|
5
6
|
require 'gruff'
|
7
|
+
require 'securerandom'
|
6
8
|
require_relative './config.rb'
|
7
9
|
|
8
10
|
module BuildBuddy
|
@@ -17,14 +19,18 @@ module BuildBuddy
|
|
17
19
|
mongo_uri = BuildBuddy::Config.mongo_uri
|
18
20
|
@mongo ||= Mongo::Client.new(mongo_uri)
|
19
21
|
info "Connected to MongoDB at '#{mongo_uri}'"
|
20
|
-
@mongo[:builds].indexes.create_one({:
|
22
|
+
@mongo[:builds].indexes.create_one({:create_time => -1}, :name => "reverse_order")
|
21
23
|
end
|
22
24
|
|
23
25
|
def record_build_data(build_data)
|
24
26
|
builds = @mongo[:builds]
|
25
|
-
|
26
|
-
|
27
|
-
|
27
|
+
begin
|
28
|
+
# Do this to prevent build _id's from being sequential and so reduce risk
|
29
|
+
# of someone guessing a valid build URL.
|
30
|
+
build_data._id = BSON::ObjectId.from_string(SecureRandom.hex(12).to_s)
|
31
|
+
builds.insert_one(build_data.to_h)
|
32
|
+
rescue Mongo::Error::OperationFailure => e
|
33
|
+
retry if e.to_s.start_with?('E11000') # Duplicate key error
|
28
34
|
end
|
29
35
|
end
|
30
36
|
|
@@ -35,8 +41,6 @@ module BuildBuddy
|
|
35
41
|
|
36
42
|
builds = @mongo[:builds]
|
37
43
|
builds.replace_one({ :_id => build_data._id }, build_data.to_h)
|
38
|
-
|
39
|
-
gen_charts
|
40
44
|
end
|
41
45
|
|
42
46
|
def get_build_data(id)
|
@@ -48,236 +52,21 @@ module BuildBuddy
|
|
48
52
|
end
|
49
53
|
|
50
54
|
def get_build_data_history(limit)
|
51
|
-
@mongo[:builds].find().sort(:
|
55
|
+
@mongo[:builds].find().sort(:create_time => -1).limit(limit).map do |doc|
|
52
56
|
BuildData.new(doc)
|
53
57
|
end
|
54
58
|
end
|
55
|
-
|
56
|
-
def gen_charts
|
57
|
-
gen_daily_builds_chart
|
58
|
-
gen_coverage_chart
|
59
|
-
gen_lines_chart
|
60
|
-
gen_localized_chart
|
61
|
-
gen_times_chart
|
62
|
-
gen_warnings_chart
|
63
|
-
end
|
64
|
-
|
65
|
-
def gen_daily_builds_chart
|
66
|
-
# Generates a chart showing builds per day
|
67
|
-
data = []
|
68
|
-
keys = []
|
69
|
-
@mongo[:builds].find().sort(:start_time => -1).limit(LIMIT).each do |doc|
|
70
|
-
next if doc['start_time'].nil?
|
71
|
-
start_data_components = doc['start_time'].to_s.split(' ')[0].split('-')
|
72
|
-
date_string = start_data_components[1] + "/" + start_data_components[2]
|
73
|
-
|
74
|
-
if keys.include?(date_string)
|
75
|
-
data.each { |arr|
|
76
|
-
if arr[0] == date_string
|
77
|
-
if doc['pull_request'] != nil
|
78
|
-
arr[1] = arr[1] + 1
|
79
|
-
else
|
80
|
-
arr[2] = arr[2] + 1
|
81
|
-
end
|
82
|
-
end
|
83
|
-
}
|
84
|
-
else
|
85
|
-
keys.push(date_string)
|
86
|
-
if doc['pull_request'] != nil
|
87
|
-
data.insert(0, [date_string, 1, 0])
|
88
|
-
else
|
89
|
-
data.insert(0, [date_string, 0, 1])
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
g = Gruff::StackedBar.new
|
95
|
-
g.title = "Builds per Day"
|
96
|
-
g.labels = data.each_with_index.map {|d, i| [ i, d[0] ]}.to_h
|
97
|
-
g.theme = {
|
98
|
-
:colors => ['#12a702', '#aedaa9'],
|
99
|
-
:font_color => 'black',
|
100
|
-
:background_colors => 'white'
|
101
|
-
}
|
102
|
-
g.font = '/Library/Fonts/Verdana.ttf'
|
103
|
-
g.data("Pull Request (#{data.size > 0 ? data[data.size - 1][1] : 0})", data.map {|d| d[1]})
|
104
|
-
g.data("Other (#{data.size > 0 ? data[data.size - 1][2] : 0})", data.map {|d| d[2]})
|
105
|
-
g.x_axis_label = 'Date'
|
106
|
-
g.y_axis_label = 'Number of Builds'
|
107
|
-
g.y_axis_increment = 2
|
108
|
-
g.write File.join(Config.hud_image_dir, "daily_builds.png")
|
109
|
-
end
|
110
|
-
|
111
|
-
def gen_coverage_chart
|
112
|
-
# Generate code coverage numbers
|
113
|
-
data = []
|
114
|
-
keys = []
|
115
|
-
@mongo[:builds].find().sort(:start_time => -1).limit(LIMIT).map do |doc|
|
116
|
-
next if doc['start_time'].nil?
|
117
|
-
start_date_components = doc['start_time'].to_s.split(' ')[0].split('-')
|
118
|
-
date_string = start_date_components[1] + "/" + start_date_components[2]
|
119
|
-
pull_number = doc['pull_request'].to_i
|
120
|
-
if pull_number == 0 && doc['metrics']['coverage'] != nil && !keys.include?(date_string)
|
121
|
-
keys.push(date_string)
|
122
|
-
|
123
|
-
missed_lines = doc['metrics']['coverage']['swift_files']['missed_lines'] + doc['metrics']['coverage']['m_files']['missed_lines']
|
124
|
-
total_lines = doc['metrics']['coverage']['swift_files']['total_lines'] + doc['metrics']['coverage']['m_files']['total_lines']
|
125
|
-
coverage_percent = ((1.0 * (total_lines - missed_lines) / total_lines) * 100).to_i
|
126
|
-
data.insert(0, [date_string, coverage_percent])
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
g = Gruff::Bar.new
|
131
|
-
g.minimum_value = 0
|
132
|
-
g.maximum_value = 100
|
133
|
-
g.title = "Coverage"
|
134
|
-
g.labels = data.each_with_index.map {|d, i| [ i, d[0] ]}.to_h
|
135
|
-
g.theme = {
|
136
|
-
:colors => ['#822F92'],
|
137
|
-
:font_color => 'black',
|
138
|
-
:background_colors => 'white'
|
139
|
-
}
|
140
|
-
g.font = '/Library/Fonts/Verdana.ttf'
|
141
|
-
g.data("covered % (#{data.size > 0 ? data[data.size - 1][1] : 0})", data.map {|d| d[1]})
|
142
|
-
g.x_axis_label = 'Date'
|
143
|
-
g.y_axis_label = 'Coverage %'
|
144
|
-
g.write File.join(Config.hud_image_dir, "code_coverage.png")
|
145
|
-
end
|
146
|
-
|
147
|
-
def gen_lines_chart
|
148
|
-
# Generates a chart displaying number of lines of code by Swift/Objective-C.
|
149
|
-
data = []
|
150
|
-
keys = []
|
151
|
-
@mongo[:builds].find().sort(:start_time => -1).limit(LIMIT).map do |doc|
|
152
|
-
next if doc['start_time'].nil?
|
153
|
-
start_date_components = doc['start_time'].to_s.split(' ')[0].split('-')
|
154
|
-
date_string = start_date_components[1] + "/" + start_date_components[2]
|
155
|
-
pull_number = doc['pull_request'].to_i
|
156
|
-
if pull_number == 0 && doc['metrics']['coverage'] != nil && !keys.include?(date_string)
|
157
|
-
keys.push(date_string)
|
158
|
-
data.insert(0, [date_string, doc['metrics']['coverage']['swift_files']['total_lines'], doc['metrics']['coverage']['m_files']['total_lines']])
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
g = Gruff::StackedBar.new
|
163
|
-
g.title = "Lines of Code"
|
164
|
-
g.labels = data.each_with_index.map {|d, i| [ i, d[0] ]}.to_h
|
165
|
-
g.theme = {
|
166
|
-
:colors => ['#C17B3A', '#C1BA3A'],
|
167
|
-
:font_color => 'black',
|
168
|
-
:background_colors => 'white'
|
169
|
-
}
|
170
|
-
g.font = '/Library/Fonts/Verdana.ttf'
|
171
|
-
g.data("Swift (#{data.size > 0 ? data[data.size - 1][1] : 0})", data.map {|d| d[1]})
|
172
|
-
g.data("Objective-C (#{data.size > 0 ? data[data.size - 1][2] : 0})", data.map {|d| d[2]})
|
173
|
-
g.x_axis_label = 'Day'
|
174
|
-
g.y_axis_label = 'Lines of Code'
|
175
|
-
g.write File.join(Config.hud_image_dir, "lines_of_code.png")
|
176
|
-
end
|
177
|
-
|
178
|
-
def gen_localized_chart
|
179
|
-
# Generates a chart displaying localization information for past pull requests.
|
180
|
-
# Displays # strings, # descriptions, and # words.
|
181
|
-
data = []
|
182
|
-
keys = []
|
183
|
-
@mongo[:builds].find().sort(:start_time => -1).limit(LIMIT).map do |doc|
|
184
|
-
pull_number = doc['pull_request'].to_i
|
185
|
-
if pull_number != 0 && !keys.include?(pull_number)
|
186
|
-
keys.push(pull_number)
|
187
|
-
data.insert(0, [pull_number, doc['metrics']['strings'], doc['metrics']['descriptions'], doc['metrics']['words']])
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
g = Gruff::Line.new
|
192
|
-
g.title = 'Localization'
|
193
|
-
g.labels = data.each_with_index.map {|d, i| [ i, d[0] ]}.to_h
|
194
|
-
g.dot_style = :square
|
195
|
-
g.font = '/Library/Fonts/Verdana.ttf'
|
196
|
-
g.data("# strings (#{data.size > 0 ? data[data.size - 1][1] : 0})", data.map {|d| d[1]})
|
197
|
-
g.data("# descriptions (#{data.size > 0 ? data[data.size - 1][2] : 0})", data.map {|d| d[2]})
|
198
|
-
g.data("# words (#{data.size > 0 ? data[data.size - 1][3] : 0})", data.map {|d| d[3]})
|
199
|
-
g.x_axis_label = 'Pull Request'
|
200
|
-
|
201
|
-
g.theme = {
|
202
|
-
:colors => ['#e65954', '#0a2154', '#2a7b20'],
|
203
|
-
:font_color => 'black',
|
204
|
-
:background_colors => 'white'
|
205
|
-
}
|
206
|
-
|
207
|
-
g.write(File.join(Config.hud_image_dir, 'localization_data.png'))
|
208
|
-
end
|
209
|
-
|
210
|
-
def gen_times_chart
|
211
|
-
# Generates a chart displaying build time information for past pull requests.
|
212
|
-
data = []
|
213
|
-
keys = []
|
214
|
-
@mongo[:builds].find().sort(:start_time => -1).limit(LIMIT).map do |doc|
|
215
|
-
next if doc['metrics'].nil?
|
216
|
-
pull_number = doc['pull_request'].to_i
|
217
|
-
if pull_number != 0 && !keys.include?(pull_number) && doc['metrics']['test_build_end'] != nil && doc['metrics']['test_build_start'] != nil
|
218
|
-
end_time_components = doc['metrics']['test_build_end'].to_s.split(' ')[1].split(':')
|
219
|
-
end_date_components = doc['metrics']['test_build_end'].to_s.split(' ')[0].split('-')
|
220
|
-
end_time = Time.new(end_date_components[0], end_date_components[1], end_date_components[2], end_time_components[0], end_time_components[1], end_time_components[2])
|
221
|
-
|
222
|
-
start_time_components = doc['metrics']['test_build_start'].to_s.split(' ')[1].split(':')
|
223
|
-
start_date_components = doc['metrics']['test_build_start'].to_s.split(' ')[0].split('-')
|
224
|
-
start_time = Time.new(start_date_components[0], start_date_components[1], start_date_components[2], start_time_components[0], start_time_components[1], start_time_components[2])
|
225
59
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
g = Gruff::Line.new
|
234
|
-
g.minimum_value = 0
|
235
|
-
g.maximum_value = 10
|
236
|
-
g.title = 'Build Times'
|
237
|
-
g.labels = data.each_with_index.map {|d, i| [ i, d[0] ]}.to_h
|
238
|
-
g.dot_style = :square
|
239
|
-
g.font = '/Library/Fonts/Verdana.ttf'
|
240
|
-
g.data("minutes (#{data.size > 0 ? data[data.size - 1][1] : 0})", data.map {|d| d[1]})
|
241
|
-
g.x_axis_label = 'Pull Request'
|
242
|
-
g.y_axis_label = 'Time (mins)'
|
243
|
-
g.theme = {
|
244
|
-
:colors => ['#38c0df'],
|
245
|
-
:font_color => 'black',
|
246
|
-
:background_colors => 'white'
|
247
|
-
}
|
248
|
-
|
249
|
-
g.write(File.join(Config.hud_image_dir, 'build_times.png'))
|
250
|
-
end
|
251
|
-
|
252
|
-
def gen_warnings_chart
|
253
|
-
# Script that generates a chart displaying warning count information for past pull requests.
|
254
|
-
data = []
|
255
|
-
keys = []
|
256
|
-
@mongo[:builds].find().sort(:start_time => -1).limit(LIMIT).map do |doc|
|
257
|
-
next if doc['start_time'].nil?
|
258
|
-
start_date_components = doc['start_time'].to_s.split(' ')[0].split('-')
|
259
|
-
date_string = start_date_components[1] + "/" + start_date_components[2]
|
260
|
-
pull_number = doc['pull_request'].to_i
|
261
|
-
if pull_number == 0 && doc['metrics']['test_build_start'] != nil && !keys.include?(date_string)
|
262
|
-
keys.push(date_string)
|
263
|
-
data.insert(0, [date_string, doc['metrics']['warning_count']])
|
60
|
+
def find_report_uri
|
61
|
+
uri = nil
|
62
|
+
@mongo[:builds].find().sort(:create_time => -1).each do |doc|
|
63
|
+
file_name = File.join(Config.build_output_dir, doc[:_id].to_s, "report.html")
|
64
|
+
if File.exist?(file_name)
|
65
|
+
uri = BuildData.server_report_uri(doc[:_id])
|
66
|
+
break
|
264
67
|
end
|
265
68
|
end
|
266
|
-
|
267
|
-
g = Gruff::Line.new
|
268
|
-
g.title = 'Warnings'
|
269
|
-
g.labels = data.each_with_index.map {|d, i| [ i, d[0] ]}.to_h
|
270
|
-
g.dot_style = :square
|
271
|
-
g.font = '/Library/Fonts/Verdana.ttf'
|
272
|
-
g.data("# warnings (#{data.size > 0 ? data[data.size - 1][1] : 0})", data.map {|d| d[1]})
|
273
|
-
g.x_axis_label = 'Date'
|
274
|
-
g.y_axis_label = 'Number of Warnings'
|
275
|
-
g.theme = {
|
276
|
-
:colors => ['#c3a00b'],
|
277
|
-
:font_color => 'black',
|
278
|
-
:background_colors => 'white'
|
279
|
-
}
|
280
|
-
g.write(File.join(Config.hud_image_dir, 'warning_count.png'))
|
69
|
+
uri
|
281
70
|
end
|
282
71
|
end
|
283
72
|
end
|
@@ -17,24 +17,45 @@ module BuildBuddy
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def queue_a_build(build_data)
|
20
|
-
@build_queue.unshift(build_data)
|
21
|
-
|
22
20
|
case build_data.type
|
23
21
|
when :pull_request
|
22
|
+
existing_bb_id = find_bb_id_for_pr(build_data.pull_request)
|
23
|
+
|
24
24
|
Celluloid::Actor[:gitter].async.set_status(
|
25
25
|
build_data.repo_full_name, build_data.repo_sha, :pending, "Build is queued",
|
26
26
|
build_data.server_log_uri)
|
27
27
|
info "Pull request build queued"
|
28
|
+
|
29
|
+
unless existing_bb_id.nil?
|
30
|
+
info "Stopping existing build #{existing_bb_id} for this PR"
|
31
|
+
stop_build(existing_bb_id, 'github')
|
32
|
+
end
|
28
33
|
when :branch
|
29
34
|
info "'#{build_data.branch}' branch build queued"
|
30
35
|
end
|
31
36
|
|
37
|
+
@build_queue.unshift(build_data)
|
38
|
+
|
32
39
|
if @build_timer.nil?
|
33
40
|
@build_timer = every(5) { on_build_interval }
|
34
41
|
info "Build timer started"
|
35
42
|
end
|
36
43
|
end
|
37
44
|
|
45
|
+
def find_bb_id_for_pr(pull_request)
|
46
|
+
if @active_build and @active_build.pull_request == pull_request
|
47
|
+
return @active_build.bb_id
|
48
|
+
end
|
49
|
+
|
50
|
+
build_data = @build_queue.find { |build_data| build_data.pull_request == pull_request}
|
51
|
+
|
52
|
+
if build_data.nil?
|
53
|
+
nil
|
54
|
+
else
|
55
|
+
build_data.bb_id
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
38
59
|
def queue_length
|
39
60
|
@build_queue.length
|
40
61
|
end
|
@@ -43,17 +64,17 @@ module BuildBuddy
|
|
43
64
|
@active_build
|
44
65
|
end
|
45
66
|
|
46
|
-
def stop_build(
|
67
|
+
def stop_build(bb_id, slack_user_name)
|
47
68
|
# Centralize stopping builds here
|
48
|
-
if @active_build != nil and @active_build.
|
69
|
+
if @active_build != nil and @active_build.bb_id == bb_id
|
49
70
|
@active_build.stopped_by = slack_user_name
|
50
|
-
Celluloid::Actor[:builder].stop_build
|
71
|
+
Celluloid::Actor[:builder].async.stop_build
|
51
72
|
# Build data will be recorded when the build stops
|
52
73
|
return :active
|
53
74
|
end
|
54
75
|
|
55
76
|
# Look for the build in the queue
|
56
|
-
i = @build_queue.find_index { |build_data| build_data.
|
77
|
+
i = @build_queue.find_index { |build_data| build_data.bb_id == bb_id}
|
57
78
|
if i != nil
|
58
79
|
build_data = @build_queue[i]
|
59
80
|
@build_queue.delete_at(i)
|
@@ -87,7 +108,7 @@ module BuildBuddy
|
|
87
108
|
# Make sure that the build has not run too long and kill if necessary
|
88
109
|
start_time = @active_build.start_time
|
89
110
|
if !start_time.nil? and Time.now.utc - start_time > Config.kill_build_after_mins * 60
|
90
|
-
Celluloid::Actor[:builder].async.stop_build
|
111
|
+
Celluloid::Actor[:builder].async.stop_build
|
91
112
|
end
|
92
113
|
end
|
93
114
|
end
|
data/lib/build_buddy/server.rb
CHANGED
@@ -38,148 +38,50 @@ module BuildBuddy
|
|
38
38
|
:pull_request => pull_request['number'],
|
39
39
|
:flags => {},
|
40
40
|
:repo_sha => pull_request['head']['sha'],
|
41
|
-
:repo_full_name => pull_request['base']['repo']['full_name']
|
41
|
+
:repo_full_name => pull_request['base']['repo']['full_name'],
|
42
|
+
:started_by => 'github')
|
42
43
|
info "Got #{action} pull request #{build_data.pull_request} from GitHub"
|
43
44
|
Celluloid::Actor[:scheduler].queue_a_build(build_data)
|
44
45
|
request.respond 200, "Building"
|
45
|
-
return
|
46
46
|
else
|
47
47
|
request.respond 200, "Ignoring"
|
48
|
-
return
|
49
48
|
end
|
50
49
|
end
|
51
50
|
when 'ping'
|
52
51
|
info "Got pinged from #{forwarded_for(request)}"
|
53
52
|
request.respond 200, "Running"
|
54
|
-
return
|
55
53
|
else
|
56
54
|
request.respond 404, "Event not supported"
|
57
|
-
return
|
58
55
|
end
|
59
56
|
else
|
60
57
|
request.respond 404, "Method not supported"
|
61
|
-
return
|
62
58
|
end
|
63
|
-
when /^\/
|
64
|
-
|
65
|
-
when 'GET'
|
66
|
-
build_data = Celluloid::Actor[:recorder].get_build_data($1)
|
67
|
-
if build_data.nil?
|
68
|
-
request.respond 404, "Not found"
|
69
|
-
return
|
70
|
-
end
|
71
|
-
log_file_name = build_data.log_file_name
|
72
|
-
if log_file_name.nil? or !File.exist?(log_file_name)
|
73
|
-
log_contents = 'Log file has been deleted.'
|
74
|
-
else
|
75
|
-
File.open(log_file_name) do |io|
|
76
|
-
log_contents = io.read
|
77
|
-
end
|
78
|
-
end
|
79
|
-
html = %Q(
|
80
|
-
<!doctype html>
|
81
|
-
<html lang="en">
|
82
|
-
<head>
|
83
|
-
<meta charset="utf-8">
|
84
|
-
<title>Build Log</title>
|
85
|
-
<meta name="description" content="Build Log">
|
86
|
-
<style>
|
87
|
-
body {
|
88
|
-
background-color: black;
|
89
|
-
color: #f0f0f0;
|
90
|
-
}
|
91
|
-
pre {
|
92
|
-
font-family: "Menlo", "Courier New";
|
93
|
-
font-size: 10pt;
|
94
|
-
}
|
95
|
-
</style>
|
96
|
-
</head>
|
97
|
-
|
98
|
-
<body>
|
99
|
-
<pre>
|
100
|
-
#{log_contents}
|
101
|
-
</pre>
|
102
|
-
</body>
|
103
|
-
</html>
|
104
|
-
)
|
105
|
-
request.respond 200, html
|
106
|
-
return
|
107
|
-
else
|
59
|
+
when /^\/build\/([0-9abcdef]{24})\/(log\.html|report\.html|[a-z_]+\.png)$/
|
60
|
+
if request.method != 'GET'
|
108
61
|
request.respond 404, "Method not supported"
|
109
62
|
return
|
110
63
|
end
|
111
|
-
|
112
|
-
|
113
|
-
|
64
|
+
|
65
|
+
build_id = $1
|
66
|
+
resource_name = $2
|
67
|
+
if build_id.nil? or resource_name.nil?
|
68
|
+
request.respond 404, "Not found"
|
114
69
|
return
|
115
70
|
end
|
116
71
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
<html lang="en">
|
121
|
-
<head>
|
122
|
-
<title>Build Metrics</title>
|
123
|
-
<meta charset="utf-8">
|
124
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
125
|
-
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
|
126
|
-
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.3/css/bootstrap.min.css" integrity="sha384-MIwDKRSSImVFAZCVLtU0LMDdON6KVCrZHyVQQj6e8wIEJkW4tvwqXrbMIya1vriY" crossorigin="anonymous">
|
127
|
-
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.3/js/bootstrap.min.js" integrity="sha384-ux8v3A6CPtOTqOzMKiuo3d/DomGaaClxFYdCu2HPMBEkf6x2xiDyJ7gkXU0MWwaD" crossorigin="anonymous"></script>
|
128
|
-
</head>
|
129
|
-
<body>
|
130
|
-
|
131
|
-
<div class="jumbotron text-center">
|
132
|
-
<h1>Build Metrics</h1>
|
133
|
-
<p>Build server data</p>
|
134
|
-
</div>
|
135
|
-
|
136
|
-
<div class="container-fluid">
|
137
|
-
<div class="row">
|
138
|
-
<div class="col-sm-4">
|
139
|
-
<img class="img-fluid" src="code_coverage.png" alt="Code Coverage">
|
140
|
-
</div>
|
141
|
-
<div class="col-sm-4">
|
142
|
-
<img class="img-fluid" src="build_times.png" alt="Build Times">
|
143
|
-
</div>
|
144
|
-
<div class="col-sm-4">
|
145
|
-
<img class="img-fluid" src="daily_builds.png" alt="Daily Builds">
|
146
|
-
</div>
|
147
|
-
</div>
|
148
|
-
<div class="row">
|
149
|
-
<div class="col-sm-4">
|
150
|
-
<img class="img-fluid" src="lines_of_code.png" alt="Lines of Code">
|
151
|
-
</div>
|
152
|
-
<div class="col-sm-4">
|
153
|
-
<img class="img-fluid" src="localization_data.png" alt="Localization Data">
|
154
|
-
</div>
|
155
|
-
<div class="col-sm-4">
|
156
|
-
<img class="img-fluid" src="warning_count.png" alt="Warning Count">
|
157
|
-
</div>
|
158
|
-
</div>
|
159
|
-
</div>
|
160
|
-
|
161
|
-
</body>
|
162
|
-
</html>
|
163
|
-
)
|
164
|
-
request.respond 200, html
|
72
|
+
resource_file_name = File.join(Config.build_output_dir, build_id, resource_name)
|
73
|
+
if !File.exist?(resource_file_name)
|
74
|
+
request.respond 404, "Not found"
|
165
75
|
return
|
166
|
-
|
167
|
-
png_file_name = File.join(Config.hud_image_dir, $1)
|
76
|
+
end
|
168
77
|
|
169
|
-
|
170
|
-
|
171
|
-
return
|
172
|
-
else
|
173
|
-
request.response 404, "Image not found"
|
174
|
-
return
|
175
|
-
end
|
78
|
+
if resource_name.end_with?('.html')
|
79
|
+
request.respond Reel::Response.new(200, { 'content-type' => 'text/html'}, File.open(resource_file_name, 'r'))
|
176
80
|
else
|
177
|
-
request.
|
178
|
-
return
|
81
|
+
request.respond Reel::Response.new(200, { 'content-type' => 'image/png'}, File.open(resource_file_name, 'rb'))
|
179
82
|
end
|
180
83
|
else
|
181
|
-
request.respond 404, "
|
182
|
-
return
|
84
|
+
request.respond 404, "Not found"
|
183
85
|
end
|
184
86
|
end
|
185
87
|
end
|
data/lib/build_buddy/slacker.rb
CHANGED
@@ -34,6 +34,7 @@ module BuildBuddy
|
|
34
34
|
end
|
35
35
|
|
36
36
|
@build_channel_id = nil
|
37
|
+
@pr_channel_id = nil
|
37
38
|
end
|
38
39
|
|
39
40
|
def self.extract_build_flags(message)
|
@@ -57,9 +58,10 @@ module BuildBuddy
|
|
57
58
|
end
|
58
59
|
else
|
59
60
|
scheduler = Celluloid::Actor[:scheduler]
|
61
|
+
message = message.strip
|
60
62
|
|
61
63
|
case message
|
62
|
-
when /^master(?: with
|
64
|
+
when /^master(?: +with +(?<flags>[a-z ]+))?/i
|
63
65
|
flags = Slacker.extract_build_flags($~[:flags])
|
64
66
|
response = "OK, I've queued a build of the `master` branch."
|
65
67
|
scheduler.queue_a_build(BuildData.new(
|
@@ -90,14 +92,16 @@ module BuildBuddy
|
|
90
92
|
end
|
91
93
|
|
92
94
|
def do_stop(message, is_from_slack_channel, slack_user_name)
|
95
|
+
message = message.strip
|
93
96
|
response = ''
|
94
|
-
m = message.match(
|
97
|
+
m = message.match(/^(?:build +)?(bb-\d+)$/i)
|
95
98
|
|
96
99
|
unless m.nil?
|
97
|
-
|
100
|
+
bb_id = m[1].upcase
|
101
|
+
result = Celluloid::Actor[:scheduler].stop_build(bb_id, slack_user_name)
|
98
102
|
response = case result
|
99
103
|
when :active, :in_queue
|
100
|
-
"OK#{is_from_slack_channel ? ' @' + slack_user_name : ''}, I #{result == :active ? "stopped" : "dequeued"} the build with identifier #{
|
104
|
+
"OK#{is_from_slack_channel ? ' @' + slack_user_name : ''}, I #{result == :active ? "stopped" : "dequeued"} the build with identifier #{bb_id}."
|
101
105
|
when :not_found
|
102
106
|
"I could not find a queued or active build with that identifier"
|
103
107
|
end
|
@@ -115,7 +119,9 @@ I understand types of build - pull requests and branch. A pull request build hap
|
|
115
119
|
|
116
120
|
For branch builds, I can run builds of the master branch if you say `build master`. I can do builds of release branches, e.g. `build v2.3` but only for those branches that I am allowed to build in by configuration file.
|
117
121
|
|
118
|
-
I can stop any running build if you ask me to `stop build X`, even pull request builds if you give the id X from the `show status` or `show queue` command.
|
122
|
+
I can stop any running build if you ask me to `stop build X`, even pull request builds if you give the id X from the `show status` or `show queue` command.
|
123
|
+
|
124
|
+
I will let the *#{Config.slack_build_channel}* channel know about branch build activity and the *#{Config.slack_pr_channel} channel know about PR activity.
|
119
125
|
|
120
126
|
I have lots of `show` commands:
|
121
127
|
|
@@ -123,11 +129,26 @@ I have lots of `show` commands:
|
|
123
129
|
- `show queue` and I will show you what is in the queue
|
124
130
|
- `show options` to a see a list of build options
|
125
131
|
- `show builds` to see the last 5 builds or `show last N builds` to see a list of the last N builds
|
126
|
-
|
127
|
-
Build metrics and charts are available at #{Config.server_base_uri}/hud/#{Config.hud_secret_token}/index.html
|
132
|
+
- `show report` to get a link to the latest build report
|
128
133
|
)
|
129
134
|
end
|
130
135
|
|
136
|
+
def do_relay(message, slack_user_name)
|
137
|
+
sender_is_a_builder = (Config.slack_builders.nil? ? true : Config.slack_builders.include?('@' + slack_user_name))
|
138
|
+
unless sender_is_a_builder
|
139
|
+
if is_from_slack_channel
|
140
|
+
response = "I'm sorry @#{slack_user_name} you are not on my list of allowed builders so I can't relay a message for you."
|
141
|
+
else
|
142
|
+
response = "I'm sorry but you are not on my list of allowed builders so I cannot relay a message for you."
|
143
|
+
end
|
144
|
+
else
|
145
|
+
message = message.strip.gsub('"', '')
|
146
|
+
@rt_client.message(channel: @build_channel_id, text: message) unless @build_channel_id.nil?
|
147
|
+
end
|
148
|
+
"Message relayed to #{Config.slack_build_channel}"
|
149
|
+
info "I relayed a message for #{slack_user_name} to #{Config.slack_build_channel}, \"#{message}\""
|
150
|
+
end
|
151
|
+
|
131
152
|
def do_show(request)
|
132
153
|
request = request.lstrip.rstrip
|
133
154
|
|
@@ -155,14 +176,14 @@ Build metrics and charts are available at #{Config.server_base_uri}/hud/#{Config
|
|
155
176
|
when :pull_request
|
156
177
|
"pull request build #{build_data.pull_request_uri}"
|
157
178
|
end
|
158
|
-
response += " at #{build_data.start_time.to_s}. #{
|
179
|
+
response += " at #{build_data.start_time.to_s}. #{BuildData.server_log_uri(build_data._id)}"
|
159
180
|
unless build_data.started_by.nil?
|
160
181
|
response += " started by #{build_data.started_by}"
|
161
182
|
end
|
183
|
+
response += " #{build_data.status_verb}"
|
162
184
|
unless build_data.stopped_by.nil?
|
163
|
-
response += "
|
185
|
+
response += " by #{build_data.stopped_by}"
|
164
186
|
end
|
165
|
-
response += " #{build_data.status_verb}"
|
166
187
|
response += ".\n"
|
167
188
|
end
|
168
189
|
end
|
@@ -181,9 +202,9 @@ Build metrics and charts are available at #{Config.server_base_uri}/hud/#{Config
|
|
181
202
|
else
|
182
203
|
case build_data.type
|
183
204
|
when :pull_request
|
184
|
-
response = "There is a pull request build in progress for https://github.com/#{build_data.repo_full_name}/pull/#{build_data.pull_request} (
|
205
|
+
response = "There is a pull request build in progress for https://github.com/#{build_data.repo_full_name}/pull/#{build_data.pull_request} (#{build_data.bb_id})"
|
185
206
|
when :branch
|
186
|
-
response = "There is a build of the `#{build_data.branch}` branch of https://github.com/#{build_data.repo_full_name} in progress (
|
207
|
+
response = "There is a build of the `#{build_data.branch}` branch of https://github.com/#{build_data.repo_full_name} in progress (#{build_data.bb_id})"
|
187
208
|
end
|
188
209
|
unless build_data.started_by.nil?
|
189
210
|
response += " started by " + build_data.started_by
|
@@ -217,7 +238,7 @@ Build metrics and charts are available at #{Config.server_base_uri}/hud/#{Config
|
|
217
238
|
when :pull_request
|
218
239
|
"pull request build #{build_data.pull_request_uri}"
|
219
240
|
end
|
220
|
-
response += " (
|
241
|
+
response += " (#{build_data.bb_id})"
|
221
242
|
unless build_data.started_by.nil?
|
222
243
|
response += " started by #{build_data.started_by}"
|
223
244
|
end
|
@@ -227,6 +248,14 @@ Build metrics and charts are available at #{Config.server_base_uri}/hud/#{Config
|
|
227
248
|
response += ".\n"
|
228
249
|
}
|
229
250
|
end
|
251
|
+
when /report/
|
252
|
+
response = ''
|
253
|
+
report_uri = Celluloid::Actor[:recorder].find_report_uri
|
254
|
+
if report_uri.nil?
|
255
|
+
response = "There do not appear to be any reports generated yet"
|
256
|
+
else
|
257
|
+
response = "The last build report is at #{report_uri}"
|
258
|
+
end
|
230
259
|
else
|
231
260
|
response = "I'm not sure what to say..."
|
232
261
|
end
|
@@ -248,10 +277,18 @@ Build metrics and charts are available at #{Config.server_base_uri}/hud/#{Config
|
|
248
277
|
@build_channel_id = Slacker.get_channel_id(Config.slack_build_channel, map_channel_name_to_id, map_group_name_to_id)
|
249
278
|
|
250
279
|
if @build_channel_id.nil?
|
251
|
-
error "Unable to identify the build
|
280
|
+
error "Unable to identify the slack build channel #{Config.slack_build_channel}"
|
252
281
|
else
|
253
282
|
info "Slack build notification channel is #{@build_channel_id} (#{Config.slack_build_channel})"
|
254
283
|
end
|
284
|
+
|
285
|
+
@pr_channel_id = Slacker.get_channel_id(Config.slack_pr_channel, map_channel_name_to_id, map_group_name_to_id)
|
286
|
+
|
287
|
+
if @pr_channel_id.nil?
|
288
|
+
error "Unable to identify the PR slack channel #{channel}"
|
289
|
+
else
|
290
|
+
info "Slack PR notification channel is #{@pr_channel_id} (#{Config.slack_pr_channel})"
|
291
|
+
end
|
255
292
|
end
|
256
293
|
|
257
294
|
def on_slack_data(data)
|
@@ -298,18 +335,20 @@ Build metrics and charts are available at #{Config.server_base_uri}/hud/#{Config
|
|
298
335
|
return
|
299
336
|
end
|
300
337
|
|
301
|
-
|
338
|
+
message = message.strip
|
302
339
|
|
303
340
|
response = case message
|
304
|
-
when
|
341
|
+
when /^build(.*)/i
|
305
342
|
do_build $1, is_from_slack_channel, slack_user_name
|
306
|
-
when
|
343
|
+
when /^status/ # Legacy support
|
307
344
|
do_show 'status'
|
308
|
-
when
|
345
|
+
when /^show(.*)/
|
309
346
|
do_show $1
|
310
|
-
when
|
347
|
+
when /^help/i
|
311
348
|
do_help is_from_slack_channel
|
312
|
-
when
|
349
|
+
when /^relay(.*)/i
|
350
|
+
do_relay $1, slack_user_name
|
351
|
+
when /^stop(.*)/i
|
313
352
|
do_stop $1, is_from_slack_channel, slack_user_name
|
314
353
|
else
|
315
354
|
"Sorry#{is_from_slack_channel ? ' ' + slack_user_name : ''}, I'm not sure how to respond."
|
@@ -323,9 +362,11 @@ Build metrics and charts are available at #{Config.server_base_uri}/hud/#{Config
|
|
323
362
|
|
324
363
|
if build_data.type == :branch
|
325
364
|
message = "A `#{build_data.branch}` branch build #{status_verb}"
|
365
|
+
short_message = message
|
326
366
|
info "Branch build #{status_verb}"
|
327
367
|
else
|
328
|
-
message = "
|
368
|
+
message = "A pull request https://github.com/#{build_data.repo_full_name}/pull/#{build_data.pull_request} build #{status_verb}"
|
369
|
+
short_message = "Pull request #{build_data.pull_request} #{status_verb}"
|
329
370
|
info "Pull request build #{status_verb}"
|
330
371
|
end
|
331
372
|
|
@@ -336,13 +377,17 @@ Build metrics and charts are available at #{Config.server_base_uri}/hud/#{Config
|
|
336
377
|
message += ". Log file at #{build_data.server_log_uri}"
|
337
378
|
|
338
379
|
# See https://api.slack.com/docs/attachments for more information about formatting Slack attachments
|
339
|
-
|
380
|
+
attachments = [
|
340
381
|
:title => build_data.type == :pull_request ? "Pull Request" : "Branch Build",
|
341
382
|
:text => message,
|
342
383
|
:color => build_data.termination_type == :killed ? :warning : build_data.exit_code != 0 ? :danger : :good,
|
343
384
|
]
|
344
385
|
|
345
|
-
|
386
|
+
if build_data.type == :branch
|
387
|
+
@rt_client.web_client.chat_postMessage(channel: @build_channel_id, text: short_message, attachments: attachments, as_user: true) unless @build_channel_id.nil?
|
388
|
+
else
|
389
|
+
@rt_client.web_client.chat_postMessage(channel: @pr_channel_id, text: short_message, attachments: attachments, as_user: true) unless @pr_channel_id.nil?
|
390
|
+
end
|
346
391
|
end
|
347
392
|
end
|
348
393
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: build-buddy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.14.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Lyon-smith
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-09-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: timers
|