build-buddy 1.12.2 → 1.14.0
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.
- 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
|