blend 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Rakefile +38 -0
- data/bin/blend +34 -0
- data/lib/blend.rb +28 -0
- data/lib/blend/chatbot/bot.rb +352 -0
- data/lib/blend/cli.rb +38 -0
- data/lib/blend/cli/github.rb +140 -0
- data/lib/blend/cli/heroku.rb +232 -0
- data/lib/blend/cli/hipchat.rb +61 -0
- data/lib/blend/cli/juice.rb +493 -0
- data/lib/blend/client.rb +63 -0
- data/lib/blend/client/github_client.rb +106 -0
- data/lib/blend/client/heroku_client.rb +87 -0
- data/lib/blend/client/hipchat_client.rb +78 -0
- data/lib/blend/client/juice_client.rb +400 -0
- data/lib/blend/core_ext/fixnum.rb +5 -0
- data/lib/blend/core_ext/object.rb +13 -0
- data/lib/blend/core_ext/string.rb +9 -0
- data/lib/blend/exceptions.rb +16 -0
- data/lib/blend/status/domain.rb +225 -0
- data/lib/blend/status/environment.rb +167 -0
- data/lib/blend/status/project.rb +227 -0
- data/lib/blend/status/project_resolver.rb +183 -0
- data/lib/blend/status/repo.rb +51 -0
- data/lib/blend/status/team.rb +29 -0
- data/lib/blend/version.rb +10 -0
- metadata +225 -0
@@ -0,0 +1,493 @@
|
|
1
|
+
module Blend
|
2
|
+
module CLI
|
3
|
+
class Juice < Thor
|
4
|
+
desc "login", "Login to juice"
|
5
|
+
def login
|
6
|
+
puts "Logged in." if client.login
|
7
|
+
end
|
8
|
+
|
9
|
+
desc "logout", "Clear juice credentials"
|
10
|
+
def logout
|
11
|
+
puts "Juice credentials cleared." if client.logout
|
12
|
+
end
|
13
|
+
|
14
|
+
desc "open PROJECT", "Open up juice"
|
15
|
+
def open( name )
|
16
|
+
project_id = project_id_from_name name
|
17
|
+
if( project_id.nil? )
|
18
|
+
puts "#{name} not found"
|
19
|
+
return
|
20
|
+
end
|
21
|
+
|
22
|
+
system "open http://happyfunjuice.com/projects/#{project_id}"
|
23
|
+
end
|
24
|
+
|
25
|
+
desc "settings PROJECT", "Open up juice settings"
|
26
|
+
def settings(name)
|
27
|
+
project_id = project_id_from_name name
|
28
|
+
if( project_id.nil? )
|
29
|
+
puts "#{name} not found"
|
30
|
+
return
|
31
|
+
end
|
32
|
+
|
33
|
+
system "open http://happyfunjuice.com/projects/#{project_id}/overview"
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "create PROJECT", "Create a juice project"
|
37
|
+
def create( name )
|
38
|
+
puts "TIP: you can also run check #{name} to set up everything"
|
39
|
+
client.create_project( name )
|
40
|
+
end
|
41
|
+
|
42
|
+
desc "projects", "List juice projects"
|
43
|
+
def projects
|
44
|
+
puts
|
45
|
+
printf "%-25s %-10s %-10s %-25s\n".blue, '', 'Commits', 'Deploys', '', ''
|
46
|
+
printf "%-25s %-10s %-10s %-25s\n".blue, '', 'this', 'this', '', ''
|
47
|
+
printf "%-25s %-10s %-10s %-25s %s\n".underline.blue, 'Name', 'wk', 'wk', 'Hipchat room', 'Teams'
|
48
|
+
client.summary.sort{|a,b| b['name'].to_i <=> a['name'].to_i}.each_with_index do |project,i|
|
49
|
+
printf "%-25s %-10s %-10s %-25s %s\n".try{|x| i%4==3 ? x.underline : x},
|
50
|
+
#project['id'],
|
51
|
+
project['name'].projectize,
|
52
|
+
(project['has_source_feeds'] ? project['commits_this_week'] : ''),
|
53
|
+
(project['has_server_feeds'] ? project['deploys_this_week'] : ''),
|
54
|
+
project['blend_config']['hipchat_room'],
|
55
|
+
project['blend_config']['teams'].join( ',' )
|
56
|
+
end
|
57
|
+
puts
|
58
|
+
end
|
59
|
+
|
60
|
+
desc "organizations", "Get a list of your organizations"
|
61
|
+
def organizations
|
62
|
+
puts
|
63
|
+
printf "%-5s %-25s %-25s\n".underline.blue, 'ID', 'Name', 'Domain name'
|
64
|
+
client.organizations.each do |o|
|
65
|
+
printf "%-5s %-25s %-25s\n", o['id'], o['name'], o['domain_name']
|
66
|
+
end
|
67
|
+
puts
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
desc "feeds PROJECT", "Show the configured juice feeds"
|
72
|
+
def feeds(name)
|
73
|
+
puts
|
74
|
+
printf "%-30s %-20s %-30s\n".blue.underline, 'Feed', 'Environment', 'Namespace'
|
75
|
+
client.feeds( project_id_from_name( name ) ).sort do
|
76
|
+
|a,b| a['feed_name'] <=> b['feed_name']
|
77
|
+
end.each do |feed|
|
78
|
+
printf "%-30s %-20s %-30s\n",
|
79
|
+
feed['feed_name'],
|
80
|
+
(feed['environment'] || {})['name'],
|
81
|
+
[feed['namespace'],feed['name']].select {|x| x}.join( '/' )
|
82
|
+
end
|
83
|
+
puts
|
84
|
+
end
|
85
|
+
|
86
|
+
desc "users [PROJECT]", "Get a list of users"
|
87
|
+
def users( name=nil )
|
88
|
+
|
89
|
+
if name.nil?
|
90
|
+
_users = client.organization_users( 1 ) # Hard-code hfc org here
|
91
|
+
else
|
92
|
+
_users = client.project_users( project_id_from_name( name ) )
|
93
|
+
end
|
94
|
+
|
95
|
+
puts
|
96
|
+
printf "%-25s %35s %35s %35s %35s\n".blue.underline, 'Name', 'Email', 'Personal email', 'Heroku', 'Github'
|
97
|
+
_users.each do |u|
|
98
|
+
printf "%-25s %35s %35s %35s %35s\n", u['name'], u['email'], u['personal_email'], u['heroku_handle'], u['github_handle']
|
99
|
+
end
|
100
|
+
puts
|
101
|
+
end
|
102
|
+
|
103
|
+
desc "add_team PROJECT TEAM", "Add a github team to a project"
|
104
|
+
def add_team( name, team )
|
105
|
+
client.project_add_team( project_id_from_name( name ), team )
|
106
|
+
info( name )
|
107
|
+
end
|
108
|
+
|
109
|
+
desc "add_hipchat PROJECT ROOM", "Add a hipchat room to a project"
|
110
|
+
def add_hipchat( name, room )
|
111
|
+
client.project_add_hipchat( project_id_from_name( name ), room )
|
112
|
+
info( name )
|
113
|
+
end
|
114
|
+
|
115
|
+
desc "lookup_user NAME", "Looks up a user by email address"
|
116
|
+
def lookup_user( query )
|
117
|
+
pp client.lookup_user( query )
|
118
|
+
end
|
119
|
+
|
120
|
+
desc "search_users QUERY", "Look up a user by name, email, github, heroku, etc."
|
121
|
+
def search_users( query )
|
122
|
+
puts
|
123
|
+
printf "%-25s %35s %35s %35s %35s\n".blue.underline, 'Name', 'Email', 'Personal email', 'Heroku', 'Github'
|
124
|
+
client.search_users(query).each do |u|
|
125
|
+
printf "%-25s %35s %35s %35s %35s\n", u['name'], u['email'], u['personal_email'], u['heroku_handle'], u['github_handle']
|
126
|
+
end
|
127
|
+
puts
|
128
|
+
end
|
129
|
+
|
130
|
+
# desc "user_set FIELD VALUE", "Set the value of a particular field for a user"
|
131
|
+
# def user_set( field, value )
|
132
|
+
# client
|
133
|
+
# end
|
134
|
+
|
135
|
+
desc "heroku_api TOKEN", "Sets the organization heroku token"
|
136
|
+
def heroku_api( token )
|
137
|
+
client.heroku_api token
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
desc "hipchat_api TOKEN", "Sets the organization hipchat token"
|
142
|
+
def hipchat_api( token )
|
143
|
+
client.hipchat_api token
|
144
|
+
end
|
145
|
+
|
146
|
+
desc "all_projects [--resolve]", "Show all project status"
|
147
|
+
option :resolve
|
148
|
+
def all_projects
|
149
|
+
client.projects.each do |p|
|
150
|
+
project( p['name'] )
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
desc "project NAME [--resolve]", "Get project status"
|
155
|
+
option :resolve
|
156
|
+
def project( name )
|
157
|
+
status = Blend::Status::Project.new( name, options[:resolve] )
|
158
|
+
|
159
|
+
status.header "#{name}: Juice Configuration"
|
160
|
+
status.check "Project Exists", :project_found
|
161
|
+
status.check "Hipchat Room", :hipchat
|
162
|
+
status.check "Github Teams", :github_teams
|
163
|
+
status.check "Repos Configured", :repos_setup
|
164
|
+
status.check "Source Control", :source_control
|
165
|
+
status.check "Bug Tracking", :bugtracking
|
166
|
+
|
167
|
+
status.header "#{name}: Environments"
|
168
|
+
status.check "Production", :production
|
169
|
+
status.check "Staging", :staging
|
170
|
+
|
171
|
+
status.header "#{name}: Team Configuration (#{status.github_teams.join(',')})" if status.github_teams.length > 0
|
172
|
+
|
173
|
+
status.check "Members", :github_members
|
174
|
+
|
175
|
+
status.check "User Matchup", :juice_users_synced
|
176
|
+
|
177
|
+
status.github_members.each do |m|
|
178
|
+
member = m[:name]
|
179
|
+
access = m[:access]
|
180
|
+
# puts member
|
181
|
+
juice_user = client.user_from_github_user member
|
182
|
+
if access == :read
|
183
|
+
printf "%-20s %15s".yellow, member, "readonly"
|
184
|
+
else
|
185
|
+
printf "%-20s %15s".green, member, "fullaccess"
|
186
|
+
end
|
187
|
+
|
188
|
+
if juice_user
|
189
|
+
printf " %-20s %s\n", juice_user['name'], juice_user['email']
|
190
|
+
else
|
191
|
+
puts " Unknown to juice".red
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
status.repo_status.each do |repo|
|
196
|
+
status.header "#{repo.name} Configuration"
|
197
|
+
|
198
|
+
repo.check "Private", :private?
|
199
|
+
repo.check "Hipchat Deployhook", :hipchat_hook
|
200
|
+
end
|
201
|
+
|
202
|
+
status.environment_status.each do |env|
|
203
|
+
status.header "Server: #{env.server} Configuration"
|
204
|
+
|
205
|
+
env.check "Dyno Redundancy", :dyno_redundancy
|
206
|
+
env.check "Database", :database
|
207
|
+
env.check "Backups", :backups
|
208
|
+
env.check "Stack", :stack
|
209
|
+
env.check "Exception Handling", :exception_handling
|
210
|
+
env.check "Deploy Hooks", :deployhooks
|
211
|
+
env.check "Log Monitoring", :log_monitoring
|
212
|
+
env.check "App Monitoring", :app_monitoring
|
213
|
+
env.check "SSL Addon", :ssl
|
214
|
+
end
|
215
|
+
|
216
|
+
status.domains_status.each do |domain|
|
217
|
+
status.header "DNS: #{domain.domain} configuration"
|
218
|
+
|
219
|
+
domain.check "Registered?", :registered?
|
220
|
+
domain.check "Expires", :expires
|
221
|
+
domain.check "Owner", :owner
|
222
|
+
domain.check "SSL Cert", :ssl_exists?
|
223
|
+
domain.check "SSL Expires", :ssl_valid_until
|
224
|
+
domain.check "SSL Common Name", :ssl_common_name
|
225
|
+
end
|
226
|
+
|
227
|
+
end
|
228
|
+
=begin
|
229
|
+
|
230
|
+
|
231
|
+
|
232
|
+
|
233
|
+
desc "check [PROJECT]", "Check a project config (all the checks)"
|
234
|
+
def check( name )
|
235
|
+
check_project( name )
|
236
|
+
puts
|
237
|
+
check_hipchat( name )
|
238
|
+
puts
|
239
|
+
check_team( name )
|
240
|
+
puts
|
241
|
+
check_hooks( name )
|
242
|
+
puts
|
243
|
+
info( name )
|
244
|
+
end
|
245
|
+
|
246
|
+
desc "check_hooks [PROJECT]", "Check to see if the hooks are configured"
|
247
|
+
def check_hooks( name )
|
248
|
+
puts "Looking for github hooks".bold
|
249
|
+
##
|
250
|
+
# Github Hooks
|
251
|
+
##
|
252
|
+
project_id = project_id_from_name name
|
253
|
+
return if project_id.nil?
|
254
|
+
|
255
|
+
data = client.project project_id
|
256
|
+
config = data['blend_config']
|
257
|
+
|
258
|
+
teams = config['teams']
|
259
|
+
|
260
|
+
if teams.count > 0
|
261
|
+
teams.each do |team|
|
262
|
+
puts "Looking at repos for #{team}".bold
|
263
|
+
github_client.list_team_repos( team ).each do |repo|
|
264
|
+
printf "%-40s %s\n", repo['full_name'], repo['description']
|
265
|
+
found = {}
|
266
|
+
github_client.list_hooks( repo['full_name'] ).each do |hook|
|
267
|
+
puts "Found #{hook["name"]}"
|
268
|
+
found[hook['name']] = true
|
269
|
+
end
|
270
|
+
|
271
|
+
unless found['hipchat']
|
272
|
+
puts "Missing hipchat hook"
|
273
|
+
if config['hipchat_room']
|
274
|
+
puts "Adding Hipchat Hook"
|
275
|
+
Blend::CLI::Github.new.add_hipchat( repo['full_name'], config['hipchat_room'])
|
276
|
+
else
|
277
|
+
puts "Missing hipchat room"
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
else
|
283
|
+
puts "Teams must be set up correctly in order to monitor hooks.".red
|
284
|
+
end
|
285
|
+
|
286
|
+
apps = client.heroku_apps( project_id )
|
287
|
+
|
288
|
+
|
289
|
+
if( apps['production'] )
|
290
|
+
puts "Production".blue
|
291
|
+
apps['production'].each do |app|
|
292
|
+
Blend::CLI::Heroku.new.check app['name']
|
293
|
+
a = heroku_client.addons(app['name'], /deployhooks/)
|
294
|
+
|
295
|
+
if( a.nil? || a.length == 0 )
|
296
|
+
puts "Adding deploy hook".yellow
|
297
|
+
system( "echo heroku addons:add deployhooks:hipchat --auth_token=#{client.hipchat_api} --room=\"#{config['hipchat_room']} --app #{app['name']}\"")
|
298
|
+
system( "heroku addons:add deployhooks:hipchat --auth_token=#{client.hipchat_api} --room=\"#{config['hipchat_room']}\" --app #{app['name']}")
|
299
|
+
Blend::Client::hipchat_client.post_message config['hipchat_room'], "Heroku app: #{app['name']} commit hook now added"
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
if( apps['staging'] )
|
304
|
+
puts "Staging".blue
|
305
|
+
apps['staging'].each do |app|
|
306
|
+
Blend::CLI::Heroku.new.check app['name']
|
307
|
+
a = heroku_client.addons(app['name'], /deployhooks/)
|
308
|
+
|
309
|
+
if( a.nil? || a.length == 0 )
|
310
|
+
puts "Adding deploy hook".yellow
|
311
|
+
system( "echo heroku addons:add deployhooks:hipchat --auth_token=#{client.hipchat_api} --room=\"#{config['hipchat_room']} --app #{app['name']}\"")
|
312
|
+
system( "heroku addons:add deployhooks:hipchat --auth_token=#{client.hipchat_api} --room=\"#{config['hipchat_room']} --app #{app['name']}\"")
|
313
|
+
Blend::Client::hipchat_client.post_message config['hipchat_room'], "Heroku app: #{app['name']} commit hook now added"
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
end
|
319
|
+
=end
|
320
|
+
|
321
|
+
desc "hipchat_check", "Prints out all the rooms not assigned to rooms"
|
322
|
+
def hipchat_check
|
323
|
+
begin
|
324
|
+
client.hipchat_check.each do |x|
|
325
|
+
printf "%-30s %s\n", x[:room], x[:projects].join( "," )
|
326
|
+
end
|
327
|
+
rescue Exceptions::HipchatAuthenticationFailure
|
328
|
+
puts "Unable to connect to Hipchat. Is your key valid?".red
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
desc "github_team_check", "Prints out all the teams not assigned to projects"
|
333
|
+
option :fix
|
334
|
+
def github_team_check
|
335
|
+
client.github_team_check.each do |team,projects|
|
336
|
+
next if team == 'Owners'
|
337
|
+
next if projects.size > 0
|
338
|
+
puts "#{team} has no juice project".red
|
339
|
+
# printf "%-30s %s\n", team, projects.collect {|x| x['name'] }.join( "," )
|
340
|
+
|
341
|
+
if( options[:fix] )
|
342
|
+
choices = client.projects.collect { |x| x['name'] }
|
343
|
+
|
344
|
+
project = choose do |menu|
|
345
|
+
menu.header = "Select a github team action"
|
346
|
+
menu.prompt = "Please choose a unassigned juice project to associate #{team} with:"
|
347
|
+
|
348
|
+
menu.choice "Ignore" do
|
349
|
+
"ignore"
|
350
|
+
end
|
351
|
+
|
352
|
+
menu.choice "Create a project called #{team}" do
|
353
|
+
"create"
|
354
|
+
end
|
355
|
+
|
356
|
+
menu.choices *choices
|
357
|
+
end
|
358
|
+
|
359
|
+
if( project == "ignore" )
|
360
|
+
puts "Skip it"
|
361
|
+
else
|
362
|
+
if( project == "create" )
|
363
|
+
puts "TODO: Create a new project called #{team}"
|
364
|
+
else
|
365
|
+
add_team project, team
|
366
|
+
# puts "Attaching to: #{project}"
|
367
|
+
end
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
desc "activity PROJECT", "Shows recent project activity in last week or (default) current week [--lastweek] [--thisweek]"
|
374
|
+
option :lastweek
|
375
|
+
option :thisweek
|
376
|
+
def activity( name )
|
377
|
+
puts
|
378
|
+
project_id = project_id_from_name name
|
379
|
+
return if project_id.nil?
|
380
|
+
|
381
|
+
now = DateTime.now.to_date + 1
|
382
|
+
after = now - now.wday
|
383
|
+
before = Time.now
|
384
|
+
|
385
|
+
if( options[:lastweek] )
|
386
|
+
after -= 7
|
387
|
+
before = after + 7
|
388
|
+
end
|
389
|
+
|
390
|
+
summary = client.activities( project_id, after.to_time, before.to_time )
|
391
|
+
|
392
|
+
project = client.project( project_id )
|
393
|
+
|
394
|
+
puts "#{project['name']} activity".bold + " for #{summary[:after].strftime( "%Y-%m-%d %H:%M" )} (#{((Time.now-summary[:after])/3600/24).round(1)} days ago) - #{summary[:before].strftime( "%Y-%m-%d %H:%M" )} (now)"
|
395
|
+
puts
|
396
|
+
puts "Activity Summary".underline.blue
|
397
|
+
summary[:type].keys.sort.each do |x|
|
398
|
+
printf "%-6s %s\n", summary[:type][x].count, x
|
399
|
+
end
|
400
|
+
|
401
|
+
puts
|
402
|
+
puts "Activity Breakdown".underline.blue
|
403
|
+
summary[:actors_activites].keys.sort.each do |x|
|
404
|
+
summary[:actors_activites][x].keys.select { |x| x}.sort.each do |type|
|
405
|
+
printf "%-6s %-25s %s\n", summary[:actors_activites][x][type].count, type.strip_email, x
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
puts
|
410
|
+
puts "New Tickets".underline.blue
|
411
|
+
(summary[:type]['bugtracking:openticket'] || []).each do |activity|
|
412
|
+
printf "%-15s %-100s\n", activity['actor_identifier'], activity['description'][0..100].gsub( /\n/, " " )
|
413
|
+
#puts activity['description'][0..100].gsub( /\n/, " " )
|
414
|
+
end
|
415
|
+
|
416
|
+
puts
|
417
|
+
puts "Closed Tickets".underline.blue
|
418
|
+
(summary[:type]['bugtracking:closedticket'] || []).each do |activity|
|
419
|
+
printf "%-15s %-100s\n", activity['actor_identifier'], activity['description'][0..100].gsub( /\n/, " " )
|
420
|
+
#puts activity['description'][0..100].gsub( /\n/, " " )
|
421
|
+
end
|
422
|
+
|
423
|
+
puts
|
424
|
+
puts "Active Tickets".underline.blue
|
425
|
+
summary[:type].keys.select do |x|
|
426
|
+
x =~ /bugtracking/
|
427
|
+
end.collect do |x|
|
428
|
+
summary[:type][x].collect do |a|
|
429
|
+
a['description']
|
430
|
+
end
|
431
|
+
end.flatten.sort.uniq.each do |x|
|
432
|
+
puts x[0..100].gsub( /\n/, " " ) unless x =~ /^\[Changeset\]/
|
433
|
+
end
|
434
|
+
|
435
|
+
puts
|
436
|
+
puts "Commits".underline.blue
|
437
|
+
(summary[:type]['sourcecontrol:commit'] || []).each do |x|
|
438
|
+
printf "%-30s %s\n", x['actor_identifier'].strip_email, x['description'][0..100].gsub( /\n/, " " )
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
desc "report NAME", "Report for an individual project"
|
443
|
+
option :lastweek
|
444
|
+
def report(name)
|
445
|
+
info name
|
446
|
+
activity name
|
447
|
+
end
|
448
|
+
|
449
|
+
desc "report_dump", "Write out weekly reports"
|
450
|
+
option :lastweek
|
451
|
+
def report_dump
|
452
|
+
puts "Loading projects"
|
453
|
+
|
454
|
+
system "mkdir -p /tmp/juice_reports"
|
455
|
+
client.projects.each do |project|
|
456
|
+
File.open( "/tmp/juice_reports/#{project['name']}.txt", "w" ) do |out|
|
457
|
+
puts "Running report for #{project['name']}"
|
458
|
+
$stdout = out
|
459
|
+
info project['name']
|
460
|
+
activities project['name']
|
461
|
+
$stdout = STDOUT
|
462
|
+
end
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
466
|
+
no_commands do
|
467
|
+
def client
|
468
|
+
@client ||= Blend::Client.juice_client
|
469
|
+
end
|
470
|
+
|
471
|
+
def hipchat_client
|
472
|
+
Blend::Client.hipchat_client
|
473
|
+
end
|
474
|
+
|
475
|
+
def github_client
|
476
|
+
Blend::Client.github_client
|
477
|
+
end
|
478
|
+
|
479
|
+
def heroku_client
|
480
|
+
Blend::Client.heroku_client
|
481
|
+
end
|
482
|
+
|
483
|
+
def project_id_from_name( name )
|
484
|
+
client.project_id_from_name( name )
|
485
|
+
end
|
486
|
+
|
487
|
+
def set( s )
|
488
|
+
!s.nil? && s != ""
|
489
|
+
end
|
490
|
+
end
|
491
|
+
end
|
492
|
+
end
|
493
|
+
end
|