build-buddy 1.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/build-buddy +47 -0
- data/lib/build_buddy.rb +9 -0
- data/lib/build_buddy/builder.rb +80 -0
- data/lib/build_buddy/config.rb +28 -0
- data/lib/build_buddy/server.rb +288 -0
- data/lib/build_buddy/watcher.rb +17 -0
- metadata +204 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0b2d3eab19b59adf57beb5489d827f289c871f8e
|
4
|
+
data.tar.gz: b6ea074f9c8e13b36247c839715e549bc25428bc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 66842f29b1f61fd65b290d88e346fa364dcdce66585100f77370c5216ed673e3b2c4a8831c800084eab887f7ffc122549eded0d780b6eacdfe72b640c02a37fb
|
7
|
+
data.tar.gz: 20063e74483fc136eab52fbbb6a772491a1a6b6fd7f600a0d02b4efa22ecd3d9d4f1e255d627d897f9cf1ef1148c75388c2117449328fba45b2a3bd6b62aecbd
|
data/bin/build-buddy
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'celluloid/current'
|
6
|
+
require 'celluloid/supervision'
|
7
|
+
require 'celluloid/supervision/container'
|
8
|
+
require 'methadone'
|
9
|
+
require 'build_buddy'
|
10
|
+
|
11
|
+
class Tool
|
12
|
+
include Methadone::Main
|
13
|
+
include Methadone::CLILogging
|
14
|
+
|
15
|
+
main do |config_name|
|
16
|
+
config_file_name = config_name
|
17
|
+
|
18
|
+
if File.extname(config_file_name) != '.bbconfig'
|
19
|
+
config_file_name += '.bbconfig'
|
20
|
+
end
|
21
|
+
|
22
|
+
load config_file_name
|
23
|
+
|
24
|
+
build_log_dir = BuildBuddy::Config.build_log_dir
|
25
|
+
|
26
|
+
unless Dir.exist?(build_log_dir)
|
27
|
+
Dir.mkdir(build_log_dir)
|
28
|
+
end
|
29
|
+
|
30
|
+
Slack.configure do |config|
|
31
|
+
config.token = BuildBuddy::Config.slack_api_token
|
32
|
+
end
|
33
|
+
|
34
|
+
Celluloid.logger = Reel::Logger.logger
|
35
|
+
|
36
|
+
BuildBuddy::Builder.supervise as: :builder
|
37
|
+
BuildBuddy::Server.supervise as: :server
|
38
|
+
|
39
|
+
sleep
|
40
|
+
end
|
41
|
+
|
42
|
+
version BuildBuddy::VERSION
|
43
|
+
description 'Build Buddy'
|
44
|
+
arg :config_name, :required
|
45
|
+
|
46
|
+
go!
|
47
|
+
end
|
data/lib/build_buddy.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
require 'celluloid'
|
4
|
+
require 'ostruct'
|
5
|
+
require_relative './watcher.rb'
|
6
|
+
require_relative './config.rb'
|
7
|
+
|
8
|
+
module BuildBuddy
|
9
|
+
class Builder
|
10
|
+
include Celluloid
|
11
|
+
include Celluloid::Internals::Logger
|
12
|
+
|
13
|
+
# TODO: Respond to request to kill the build.
|
14
|
+
# TODO: Kill the build pid after a certain amount of time has elapsed and report.
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@pid = nil
|
18
|
+
@watcher = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def start_build(build_data)
|
22
|
+
@build_data = build_data
|
23
|
+
repo_parts = build_data.repo_full_name.split('/')
|
24
|
+
command = "bash "
|
25
|
+
env = {
|
26
|
+
"GIT_REPO_OWNER" => repo_parts[0],
|
27
|
+
"GIT_REPO_NAME" => repo_parts[1],
|
28
|
+
"RBENV_DIR" => nil,
|
29
|
+
"RBENV_VERSION" => nil,
|
30
|
+
"PATH" => ENV['PATH'].split(':').select { |v| !v.match(/\.rbenv\/versions/) }.join(':')
|
31
|
+
}
|
32
|
+
|
33
|
+
case build_data.build_type
|
34
|
+
when :pull_request
|
35
|
+
env["GIT_PULL_REQUEST"] = build_data.pull_request.to_s
|
36
|
+
command += Config.pull_request_build_script
|
37
|
+
when :master
|
38
|
+
command += Config.master_build_script
|
39
|
+
when :release
|
40
|
+
env["GIT_BRANCH"] = build_data.build_version
|
41
|
+
command += Config.release_build_script
|
42
|
+
else
|
43
|
+
raise "Unknown build type"
|
44
|
+
end
|
45
|
+
|
46
|
+
build_log_filename = File.join(Config.build_log_dir, "build_#{build_data.build_type.to_s}_#{Time.now.utc.strftime('%Y%m%d%H%M%S')}.log")
|
47
|
+
build_data.build_log_filename = build_log_filename
|
48
|
+
|
49
|
+
command += " >#{build_log_filename} 2>&1"
|
50
|
+
|
51
|
+
Bundler.with_clean_env do
|
52
|
+
@pid = Process.spawn(env, command)
|
53
|
+
end
|
54
|
+
info "Running '#{command}' (process #{@pid})"
|
55
|
+
|
56
|
+
if @watcher
|
57
|
+
@watcher.terminate
|
58
|
+
end
|
59
|
+
|
60
|
+
@watcher = Watcher.new(@pid)
|
61
|
+
@watcher.async.watch_pid
|
62
|
+
end
|
63
|
+
|
64
|
+
def process_done(status)
|
65
|
+
@build_data.termination_type = (status.signaled? ? :killed : :exited)
|
66
|
+
@build_data.exit_code = (status.exited? ? status.exitstatus : -1)
|
67
|
+
info "Process #{status.pid} #{@build_data.termination_type == :killed ? 'was terminated' : "exited (#{@build_data.exit_code})"}"
|
68
|
+
Celluloid::Actor[:server].async.on_build_completed(@build_data)
|
69
|
+
@watcher.terminate
|
70
|
+
@watcher = nil
|
71
|
+
end
|
72
|
+
|
73
|
+
def stop_build
|
74
|
+
if @pid
|
75
|
+
info "Killing pid #{@pid}"
|
76
|
+
Process.kill(:SIGABRT, @pid)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module BuildBuddy
|
2
|
+
module Config
|
3
|
+
extend self
|
4
|
+
|
5
|
+
attr_accessor :github_webhook_secret_token
|
6
|
+
attr_accessor :github_webhook_repo_full_name
|
7
|
+
attr_accessor :github_api_token
|
8
|
+
attr_accessor :slack_api_token
|
9
|
+
attr_accessor :slack_build_channel
|
10
|
+
attr_accessor :slack_builders
|
11
|
+
attr_accessor :xcode_workspace
|
12
|
+
attr_accessor :xcode_test_scheme
|
13
|
+
attr_accessor :build_log_dir
|
14
|
+
attr_accessor :pull_request_build_script
|
15
|
+
attr_accessor :master_build_script
|
16
|
+
attr_accessor :release_build_script
|
17
|
+
end
|
18
|
+
|
19
|
+
class << self
|
20
|
+
def configure
|
21
|
+
block_given? ? yield(Config) : Config
|
22
|
+
end
|
23
|
+
|
24
|
+
def config
|
25
|
+
Config
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,288 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'celluloid/current'
|
3
|
+
require 'reel'
|
4
|
+
require 'slack-ruby-client'
|
5
|
+
require 'json'
|
6
|
+
require 'ostruct'
|
7
|
+
require 'octokit'
|
8
|
+
require 'thread'
|
9
|
+
require 'timers'
|
10
|
+
require 'rack'
|
11
|
+
require_relative './builder.rb'
|
12
|
+
|
13
|
+
module BuildBuddy
|
14
|
+
class Server < Reel::Server::HTTP
|
15
|
+
include Celluloid::Internals::Logger
|
16
|
+
|
17
|
+
def initialize(host = "127.0.0.1", port = 4567)
|
18
|
+
super(host, port, &method(:on_connection))
|
19
|
+
@gh_client ||= Octokit::Client.new(:access_token => Config.github_api_token)
|
20
|
+
@rt_client = Slack::RealTime::Client.new
|
21
|
+
@rt_client.on :hello do
|
22
|
+
self.on_slack_hello()
|
23
|
+
end
|
24
|
+
@rt_client.on :message do |data|
|
25
|
+
self.on_slack_data(data)
|
26
|
+
end
|
27
|
+
@rt_client.on :error do |error|
|
28
|
+
self.on_slack_error(error)
|
29
|
+
end
|
30
|
+
@rt_client.start_async
|
31
|
+
@active_build = nil
|
32
|
+
@build_queue = Queue.new
|
33
|
+
@done_queue = Queue.new
|
34
|
+
@notify_slack_channel = nil
|
35
|
+
@reverse_user_map = nil
|
36
|
+
end
|
37
|
+
|
38
|
+
def on_slack_error(error)
|
39
|
+
sub_error = error['error']
|
40
|
+
error "Whoops! Slack error #{sub_error['code']} - #{sub_error['msg']}}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def on_slack_hello
|
44
|
+
user_id = @rt_client.self['id']
|
45
|
+
user_map = @rt_client.users.map {|user| [user['name'], user['id']]}.to_h
|
46
|
+
@reverse_user_map = user_map.invert
|
47
|
+
info "Connected to Slack as user id #{user_id} (@#{@reverse_user_map[user_id]})"
|
48
|
+
|
49
|
+
channel_map = @rt_client.channels.map {|channel| [channel['name'], channel['id']]}.to_h
|
50
|
+
group_map = @rt_client.groups.map {|group| [group['name'], group['id']]}.to_h
|
51
|
+
channel = Config.slack_build_channel
|
52
|
+
is_channel = (channel[0] == '#')
|
53
|
+
|
54
|
+
@notify_slack_channel = (is_channel ? channel_map[channel[1..-1]] : group_map[channel])
|
55
|
+
if @notify_slack_channel.nil?
|
56
|
+
error "Unable to identify the slack channel #{channel}"
|
57
|
+
else
|
58
|
+
info "Slack notification channel is #{@notify_slack_channel} (#{channel})"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def on_slack_data(data)
|
63
|
+
message = data['text']
|
64
|
+
|
65
|
+
# If no message, then there's nothing to do
|
66
|
+
if message.nil?
|
67
|
+
return
|
68
|
+
end
|
69
|
+
|
70
|
+
sending_user_id = data['user']
|
71
|
+
sending_user_name = @reverse_user_map[sending_user_id]
|
72
|
+
|
73
|
+
# Don't respond if _we_ sent the message!
|
74
|
+
if sending_user_id == @rt_client.self['id']
|
75
|
+
return
|
76
|
+
end
|
77
|
+
|
78
|
+
sender_is_a_builder = (Config.slack_builders.nil? ? true : Config.slack_builders.include?('@' + sending_user_name))
|
79
|
+
|
80
|
+
c = data['channel'][0]
|
81
|
+
in_channel = (c == 'C' || c == 'G')
|
82
|
+
|
83
|
+
# Don't respond if the message is to a channel and our name is not in the message
|
84
|
+
if in_channel and !message.match(@rt_client.self['id'])
|
85
|
+
return
|
86
|
+
end
|
87
|
+
|
88
|
+
case message
|
89
|
+
when /build/i
|
90
|
+
unless sender_is_a_builder
|
91
|
+
if in_channel
|
92
|
+
response = "I'm sorry @#{sending_user_name} you are not on my list of allowed builders."
|
93
|
+
else
|
94
|
+
response = "I'm sorry but you are not on my list of allowed builders."
|
95
|
+
end
|
96
|
+
else
|
97
|
+
case message
|
98
|
+
when /master/i
|
99
|
+
response = "OK, I've queued a build of the `master` branch."
|
100
|
+
queue_a_build(OpenStruct.new(
|
101
|
+
:build_type => :master,
|
102
|
+
:repo_full_name => Config.github_webhook_repo_full_name))
|
103
|
+
when /(?<version>v\d+\.\d+)/
|
104
|
+
version = $~[:version]
|
105
|
+
response = "OK, I've queued a build of `#{version}` branch."
|
106
|
+
queue_a_build(OpenStruct.new(
|
107
|
+
:build_type => :release,
|
108
|
+
:build_version => version,
|
109
|
+
:repo_full_name => Config.github_webhook_repo_full_name))
|
110
|
+
when /stop/i
|
111
|
+
build_data = @active_build
|
112
|
+
if build_data.nil?
|
113
|
+
response = "There is no build running to stop"
|
114
|
+
else
|
115
|
+
# TODO: We need some more checks here to avoid accidental stoppage
|
116
|
+
response = "OK, I'm trying to *stop* the currently running build..."
|
117
|
+
Celluloid::Actor[:builder].async.stop_build
|
118
|
+
end
|
119
|
+
else
|
120
|
+
response = "Sorry#{in_channel ? " <@#{data['user']}>" : ""}, I'm not sure if you want do an internal *master*, external *M.m* build, or maybe *stop* any running build?"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
when /status/i
|
124
|
+
build_data = @active_build
|
125
|
+
queue_length = @build_queue.length
|
126
|
+
if build_data == nil
|
127
|
+
response = "There is currently no build running"
|
128
|
+
if queue_length == 0
|
129
|
+
response += " and no builds in the queue."
|
130
|
+
else
|
131
|
+
response += " and #{queue_length} in the queue."
|
132
|
+
end
|
133
|
+
else
|
134
|
+
case build_data.build_type
|
135
|
+
when :pull_request
|
136
|
+
response = "There is a pull request build in progress for https://github.com/#{build_data.repo_full_name}/pull/#{build_data.pull_request}."
|
137
|
+
when :master
|
138
|
+
response = "There is a build of the `master` branch of https://github.com/#{build_data.repo_full_name} in progress."
|
139
|
+
when :release
|
140
|
+
response = "There is a build of the `#{build_data.build_version}` branch of https://github.com/#{build_data.repo_full_name} in progress."
|
141
|
+
end
|
142
|
+
if queue_length == 1
|
143
|
+
response += " There is one build in the queue."
|
144
|
+
elsif queue_length > 1
|
145
|
+
response += " There are #{queue_length} builds in the queue."
|
146
|
+
end
|
147
|
+
end
|
148
|
+
when /help/i, /what can/i
|
149
|
+
# TODO: The repository should be a link to GitHub
|
150
|
+
response = %Q(Hello#{in_channel ? " <@#{data['user']}>" : ""}, I'm the *@#{@rt_client.self['name']}* build bot! I look after 3 types of build: pull request, master and release.
|
151
|
+
|
152
|
+
A pull request *build* happens when you make a pull request to the *#{Config.github_webhook_repo_full_name}* GitHub repository. I can stop those builds if you ask me too through Slack, but you have to start them with a pull request.
|
153
|
+
|
154
|
+
I can run builds of the *master* branch when you ask me, as well as doing builds of a release branch, e.g. *v1.0*, *v2.3*, etc..
|
155
|
+
|
156
|
+
You can also ask me about the *status* of builds and I'll tell you if anything is currently happening.
|
157
|
+
|
158
|
+
I am configured to let the *\##{Config.slack_build_channel}* channel know if master or release builds fail. Note the words I have highlighted in bold. These are the keywords that I'll look for to understand what you are asking me.
|
159
|
+
)
|
160
|
+
else
|
161
|
+
response = "Sorry#{in_channel ? " <@#{data['user']}>" : ""}, I'm not sure how to respond."
|
162
|
+
end
|
163
|
+
@rt_client.message channel: data['channel'], text: response
|
164
|
+
info "Slack message '#{message}' from #{data['channel']} handled"
|
165
|
+
end
|
166
|
+
|
167
|
+
def on_connection(connection)
|
168
|
+
connection.each_request do |request|
|
169
|
+
case request.method
|
170
|
+
when 'POST'
|
171
|
+
case request.path
|
172
|
+
when '/webhook'
|
173
|
+
case request.headers["X-GitHub-Event"]
|
174
|
+
when 'pull_request'
|
175
|
+
payload_text = request.body.to_s
|
176
|
+
# TODO: Also need to validate that it's the github_webhook_repo_full_name
|
177
|
+
if !verify_signature(payload_text, request.headers["X-Hub-Signature"])
|
178
|
+
request.respond 500, "Signatures didn't match!"
|
179
|
+
else
|
180
|
+
payload = JSON.parse(payload_text)
|
181
|
+
pull_request = payload['pull_request']
|
182
|
+
build_data = OpenStruct.new(
|
183
|
+
:build_type => :pull_request,
|
184
|
+
:pull_request => pull_request['number'],
|
185
|
+
:repo_sha => pull_request['head']['sha'],
|
186
|
+
:repo_full_name => pull_request['base']['repo']['full_name'])
|
187
|
+
info "Got pull request #{build_data[:pull_request]} from GitHub"
|
188
|
+
queue_a_build(build_data)
|
189
|
+
request.respond 200
|
190
|
+
end
|
191
|
+
when 'ping'
|
192
|
+
request.respond 200, "Running"
|
193
|
+
else
|
194
|
+
request.respond 404, "Path not found"
|
195
|
+
end
|
196
|
+
end
|
197
|
+
else
|
198
|
+
request.respond 404, "Method not supported"
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def queue_a_build(build_data)
|
204
|
+
@build_queue.push(build_data)
|
205
|
+
|
206
|
+
case build_data.build_type
|
207
|
+
when :pull_request
|
208
|
+
@gh_client.create_status(
|
209
|
+
build_data.repo_full_name, build_data.repo_sha, 'pending',
|
210
|
+
{ :description => "This build is in the queue" })
|
211
|
+
info "Pull request build queued"
|
212
|
+
when :master
|
213
|
+
info "Internal build queued"
|
214
|
+
when :release
|
215
|
+
info "External build queued"
|
216
|
+
end
|
217
|
+
|
218
|
+
if @build_timer.nil?
|
219
|
+
@build_timer = every(5) { on_build_interval }
|
220
|
+
info "Build timer started"
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def on_build_interval
|
225
|
+
if @active_build.nil?
|
226
|
+
if @build_queue.length > 0
|
227
|
+
build_data = @build_queue.pop()
|
228
|
+
@active_build = build_data
|
229
|
+
# TODO: Add timing information into the build_data
|
230
|
+
if build_data.build_type == :pull_request
|
231
|
+
@gh_client.create_status(
|
232
|
+
build_data.repo_full_name, build_data.repo_sha, 'pending',
|
233
|
+
{ :description => "This build has started" })
|
234
|
+
end
|
235
|
+
Celluloid::Actor[:builder].async.start_build(build_data)
|
236
|
+
elsif @done_queue.length > 0
|
237
|
+
# TODO: Should pop everything in the done queue
|
238
|
+
build_data = @done_queue.pop
|
239
|
+
term_msg = (build_data.termination_type == :killed ? "was stopped" : "completed")
|
240
|
+
if build_data.termination_type == :exited
|
241
|
+
if build_data.exit_code != 0
|
242
|
+
term_msg += " with errors (exit code #{build_data.exit_code}). See log file `#{build_data.build_log_filename}` for more details."
|
243
|
+
else
|
244
|
+
term_msg += " successfully"
|
245
|
+
end
|
246
|
+
end
|
247
|
+
if build_data.build_type == :pull_request
|
248
|
+
description = "The buddy build #{term_msg}"
|
249
|
+
@gh_client.create_status(
|
250
|
+
build_data.repo_full_name, build_data.repo_sha,
|
251
|
+
build_data.termination_type == :killed ? 'failure' : build_data.exit_code != 0 ? 'error' : 'success',
|
252
|
+
{ :description => description })
|
253
|
+
info "Pull request build #{term_msg}"
|
254
|
+
else
|
255
|
+
case build_data.build_type
|
256
|
+
when :master
|
257
|
+
message = "A build of the `master` branch #{term_msg}."
|
258
|
+
info "Internal build #{term_msg}"
|
259
|
+
when :release
|
260
|
+
message = "A build of the `#{build_data.build_version}` branch #{term_msg}."
|
261
|
+
info "External build #{term_msg}"
|
262
|
+
end
|
263
|
+
unless @notify_slack_channel.nil?
|
264
|
+
@rt_client.message(channel: @notify_slack_channel, text: message)
|
265
|
+
end
|
266
|
+
end
|
267
|
+
else
|
268
|
+
@build_timer.cancel
|
269
|
+
@build_timer = nil
|
270
|
+
info "Build timer stopped"
|
271
|
+
end
|
272
|
+
else
|
273
|
+
# TODO: Make sure that the build has not run too long and kill if necessary
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
def on_build_completed(build_data)
|
278
|
+
@active_build = nil
|
279
|
+
@done_queue.push(build_data)
|
280
|
+
end
|
281
|
+
|
282
|
+
def verify_signature(payload_body, gh_signature)
|
283
|
+
signature = 'sha1=' + OpenSSL::HMAC.hexdigest(
|
284
|
+
OpenSSL::Digest.new('sha1'), Config.github_webhook_secret_token, payload_body)
|
285
|
+
Rack::Utils.secure_compare(signature, gh_signature)
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'celluloid'
|
3
|
+
|
4
|
+
module BuildBuddy
|
5
|
+
class Watcher
|
6
|
+
include Celluloid
|
7
|
+
|
8
|
+
def initialize(pid)
|
9
|
+
@pid = pid
|
10
|
+
end
|
11
|
+
|
12
|
+
def watch_pid
|
13
|
+
Process.waitpid2(@pid)
|
14
|
+
Celluloid::Actor[:builder].async.process_done($?)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,204 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: build-buddy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.6
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- John Lyon-smith
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-01-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: timers
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: celluloid
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.17.2
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.17.2
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: celluloid-supervision
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.20.5
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.20.5
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: methadone
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.9'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.9'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: slack-ruby-client
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.5.3
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.5.3
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: json
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.8'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.8'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: http
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '1.0'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '1.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: reel
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - '='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 0.6.0.pre3
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - '='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 0.6.0.pre3
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: octokit
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '4.2'
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '4.2'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: rack
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '1.6'
|
146
|
+
type: :runtime
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '1.6'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: code-tools
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - "~>"
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '5.0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - "~>"
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '5.0'
|
167
|
+
description: A build buddy bot with GitHub and Slack integration.
|
168
|
+
email: john@jamoki.com
|
169
|
+
executables:
|
170
|
+
- build-buddy
|
171
|
+
extensions: []
|
172
|
+
extra_rdoc_files: []
|
173
|
+
files:
|
174
|
+
- bin/build-buddy
|
175
|
+
- lib/build_buddy.rb
|
176
|
+
- lib/build_buddy/builder.rb
|
177
|
+
- lib/build_buddy/config.rb
|
178
|
+
- lib/build_buddy/server.rb
|
179
|
+
- lib/build_buddy/watcher.rb
|
180
|
+
homepage: http://rubygems.org/gems/build-buddy
|
181
|
+
licenses:
|
182
|
+
- MIT
|
183
|
+
metadata: {}
|
184
|
+
post_install_message:
|
185
|
+
rdoc_options: []
|
186
|
+
require_paths:
|
187
|
+
- lib
|
188
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
189
|
+
requirements:
|
190
|
+
- - "~>"
|
191
|
+
- !ruby/object:Gem::Version
|
192
|
+
version: '2.2'
|
193
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
194
|
+
requirements:
|
195
|
+
- - ">="
|
196
|
+
- !ruby/object:Gem::Version
|
197
|
+
version: '0'
|
198
|
+
requirements: []
|
199
|
+
rubyforge_project:
|
200
|
+
rubygems_version: 2.4.5
|
201
|
+
signing_key:
|
202
|
+
specification_version: 4
|
203
|
+
summary: An automated build buddy
|
204
|
+
test_files: []
|