blend 0.1.4

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.
@@ -0,0 +1,63 @@
1
+ require 'blend/client/hipchat_client'
2
+ require 'blend/client/github_client'
3
+ require 'blend/client/juice_client'
4
+ require 'blend/client/heroku_client'
5
+
6
+ module Blend
7
+ module Client
8
+ class << self
9
+ def hipchat_client
10
+ @hipchat_client ||= Blend::Client::HipchatClient.new( HipChat::API.new( get_token( :hipchat ) ) )
11
+ end
12
+
13
+ def github_client
14
+ @github_client ||= Blend::Client::GithubClient.new( Github.new( oauth_token: get_token( :github ) ) )
15
+ end
16
+
17
+ def juice_client( options = {} )
18
+ if( options[:auth_token] )
19
+ if( @juice_client && @juice_client.auth_token != options[:auth_token] )
20
+ @juice_client = nil
21
+ @hipchat_client = nil
22
+ @github_client = nil
23
+ end
24
+
25
+ @juice_client ||= Blend::Client::JuiceClient.new options
26
+ end
27
+
28
+ @juice_client ||= Blend::Client::JuiceClient.new
29
+ end
30
+
31
+ def heroku_client
32
+ @heroku_client ||= Blend::Client::HerokuClient.new( Heroku::API.new( api_key: get_token( :heroku ) ) )
33
+ end
34
+
35
+ def get_token( type )
36
+ token = case type
37
+
38
+ #when :juice
39
+ # Don't use this. This is already handled together with the login
40
+ # stuff in JuiceClient. It could be refactored to fit in with this
41
+ # method a little better, but what'd be the point? It works and
42
+ # it's straightforward.
43
+ when :hipchat
44
+ ENV['HIPCHAT_API_TOKEN'] || juice_client.hipchat_api
45
+ when :github
46
+ ENV['GITHUB_API_TOKEN'] || juice_client.auth( "github" )
47
+ when :heroku
48
+ # Opt for the organization-wide heroku api token:
49
+ ENV['HEROKU_API_TOKEN'] || juice_client.heroku_api
50
+ # Instead of per-user heroku auth:
51
+ #ENV['HEROKU_API_TOKEN'] || juice_client.auth( "heroku" )
52
+ else
53
+ throw "Unknown token type #{type}".red
54
+ end
55
+
56
+
57
+ throw "Token not found for #{type}" if token.nil? || token == ""
58
+
59
+ token
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,106 @@
1
+ module Blend
2
+ module Client
3
+ class GithubClient
4
+ attr_accessor :client
5
+
6
+ def initialize( client )
7
+ @client = client
8
+ end
9
+
10
+ def list_repos( filter = nil )
11
+ return @repos if @repos && filter.nil?
12
+
13
+ repos = []
14
+ @client.repos.list( org: "sublimeguile" ).each_page do |page|
15
+ page.each do |r|
16
+ repos << r
17
+ end
18
+ end
19
+
20
+ if filter
21
+ repos = repos.select { |x| x['name'] =~ /#{filter}/ }
22
+ end
23
+
24
+ repos = repos.sort do |a,b|
25
+ a['name'] <=> b['name']
26
+ end
27
+
28
+ @repos = repos if filter.nil?
29
+ repos
30
+ end
31
+
32
+ def repo_create( team, name )
33
+ require 'pp'
34
+ pp @client.repos.create org: "sublimeguile", name: name, public: false, private: true
35
+ add_team_repo( team, "sublimeguile/#{name}" )
36
+ end
37
+
38
+
39
+ def list_collaborators( repo )
40
+ @client.repos.collaborators.list *repo.split( /\// )
41
+ end
42
+
43
+ def list_hooks( repo )
44
+ @client.repos.hooks.list( *repo.split( /\// ) )
45
+ end
46
+
47
+ def add_hook( repo, name, config )
48
+ @client.repos.hooks.create *repo.split( /\// ), name: name, config: config
49
+ end
50
+
51
+ def list_teams
52
+ @client.orgs.teams.list "sublimeguile"
53
+ end
54
+
55
+ def find_team_from_name( name )
56
+ list_teams.select { |x| x['name'] == name }
57
+ end
58
+
59
+ def list_team( team )
60
+ find_team_from_name( team ).first
61
+ end
62
+
63
+ def list_team_repos( team )
64
+ find_team_from_name(team).each do |x|
65
+ return @client.orgs.teams.repos x.id
66
+ end
67
+ end
68
+
69
+ def list_team_members( team )
70
+ find_team_from_name(team).each do |x|
71
+ return @client.orgs.teams.list_members x.id
72
+ end
73
+ end
74
+
75
+ def create_team( team )
76
+ @client.orgs.teams.create "sublimeguile", { name: team, permission: "push" }
77
+ end
78
+
79
+ def add_team_member( team, user )
80
+ find_team_from_name( team ).each do |x|
81
+ return @client.orgs.teams.add_member x.id, user
82
+ end
83
+ end
84
+
85
+ def remove_team_member( team, user)
86
+ find_team_from_name( team ).each do |x|
87
+ return @client.orgs.teams.remove_member x.id, user
88
+ end
89
+ end
90
+
91
+ def add_team_repo( team, repo )
92
+ user,name = repo.split( /\// )
93
+ find_team_from_name( team ).each do |x|
94
+ return @client.orgs.teams.add_repo x.id, user, name
95
+ end
96
+ end
97
+
98
+ def remove_team_repo( team, repo )
99
+ user,name = repo.split( /\// )
100
+ find_team_from_name( team ).each do |x|
101
+ return @client.orgs.teams.remove_repo x.id, user, name
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,87 @@
1
+ require 'highline/import'
2
+
3
+ module Blend
4
+ module Client
5
+ class HerokuClient
6
+
7
+ attr_accessor :client
8
+
9
+ def initialize( client )
10
+ @client = client
11
+ @addons = {}
12
+ end
13
+
14
+ def info( app )
15
+ client.get_app(app).body
16
+ end
17
+
18
+ def domains( app )
19
+ client.get_domains(app).body
20
+ end
21
+
22
+ def nonheroku_domains( app )
23
+ domains(app).reject{|x| x['base_domain']=="herokuapp.com" || x['domain'] =~ /^www/}.collect{|x| x['domain']}
24
+ end
25
+
26
+ def addons( app, filter=nil )
27
+ @addons[app] ||= client.get_addons(app).body
28
+ if filter.nil?
29
+ @addons[app]
30
+ else
31
+ @addons[app].select{|x| x['name'] =~ filter}
32
+ end
33
+ end
34
+
35
+ def addon_names( app, filter )
36
+ addons(app, filter).collect do |addon|
37
+ name,plan = addon['name'].split(/:/)
38
+ {name: name, plan: plan}
39
+ end
40
+ end
41
+
42
+ def addon_matches( app, addon_name )
43
+ addons(app).select{|x| x[:name] =~ addon_name}
44
+ end
45
+
46
+ def stack(app)
47
+ info(app)['stack']
48
+ end
49
+
50
+ def dynos( app )
51
+ info(app)['dynos']
52
+ end
53
+
54
+ def backups( app )
55
+ addon_names(app, /pgbackups/)
56
+ end
57
+
58
+ def databases( app )
59
+ addon_names(app, /postgresq/)
60
+ end
61
+
62
+ def run_domain_checker( app )
63
+ results = {}
64
+ nonheroku_domains(app).map{|x| x['domain']}.each do |d|
65
+
66
+
67
+ DomainChecker.check(d)
68
+
69
+ result = {}
70
+ results[d] = result
71
+
72
+ # Check registered
73
+ result[:registered] = dc.registered?
74
+
75
+ # Check registrar
76
+ result[:registrar] = dc.registrar
77
+
78
+ result[:ssl] = dc.ssl
79
+ end
80
+
81
+ results
82
+
83
+ end
84
+
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,78 @@
1
+ module Blend
2
+ module Client
3
+ class HipchatClient
4
+ attr_accessor :api
5
+
6
+ def initialize( api )
7
+ @api = api
8
+ end
9
+
10
+ def create_room( name, topic )
11
+ creator = user_by_name 'Will Schenk'
12
+
13
+ ret = api.rooms_create name, creator['user_id'], 'public', topic
14
+
15
+ if ret["error"]
16
+ pp ret["error"]
17
+ end
18
+
19
+ @rooms = nil
20
+ end
21
+
22
+ def rooms
23
+ @rooms ||= api.rooms_list.parsed_response['rooms']
24
+ raise Exceptions::HipchatAuthenticationFailure if @rooms.nil?
25
+ @rooms
26
+ end
27
+
28
+ def room_name_from_xmpp_jid( name )
29
+ rooms.select{|x| x['xmpp_jid'] == name}.first['name'] rescue nil
30
+ end
31
+
32
+ def room_by_name( name )
33
+ rooms.each do |r|
34
+ return r if r['name'] == name
35
+ end
36
+
37
+ puts "Room #{name} not found".red
38
+ nil
39
+ end
40
+
41
+ def room_info( name )
42
+ room = room_by_name name
43
+
44
+ if( room )
45
+ api.rooms_show( room['room_id'] ).parsed_response['room']
46
+ end
47
+ end
48
+
49
+ def users
50
+ @users ||= api.users_list.parsed_response['users']
51
+ end
52
+
53
+ def user_by_name( name )
54
+ users.each do |u|
55
+ return u if u['name'] == name
56
+ end
57
+
58
+ puts "User #{name} not found".red
59
+ end
60
+
61
+ def set_topic( room, topic )
62
+ room = room_by_name room
63
+
64
+ if room
65
+ api.rooms_topic( room['room_id'], topic)
66
+ end
67
+ end
68
+
69
+ def post_message( room, message, color = 'yellow', notify = false, type = 'text' )
70
+ room = room_by_name room
71
+
72
+ if room
73
+ api.rooms_message room['room_id'], 'hfc', message, notify ? "1" : "0", color, type
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,400 @@
1
+ require 'highline/import'
2
+
3
+ module Blend
4
+ module Client
5
+ class JuiceClient
6
+ include HTTParty
7
+
8
+ class << self
9
+
10
+ # Wrap HHTParty's get method so we can actually catch errors
11
+ def get( url, *args )
12
+ response = super( url, *args )
13
+ case response.code
14
+ when 401
15
+ raise Exceptions::AuthenticationFailure
16
+ end
17
+ return response
18
+ end
19
+
20
+ def post( url, *args )
21
+ response = super( url, *args )
22
+ case response.code
23
+ when 401
24
+ raise Exceptions::AuthenticationFailure
25
+ end
26
+ return response
27
+ end
28
+
29
+ end
30
+
31
+ def initialize(opts={})
32
+ @options = {}
33
+ @options['auth_token'] = opts[:auth_token] if opts.include? :auth_token
34
+ end
35
+
36
+ base_uri ENV['JUICE_API_ENDPOINT'] || 'http://happyfunjuice.com/api'
37
+ # debug_output $stderr
38
+
39
+ def login
40
+ query_login
41
+ end
42
+
43
+ def logout
44
+ destroy_auth_token
45
+ end
46
+
47
+ def create_project( name )
48
+ auth_token
49
+ pp self.class.post "/organizations/1/projects", { query: { "project[name]" => name } }
50
+ @projects = nil
51
+ end
52
+
53
+ def profile
54
+ auth_token
55
+ @projects ||= self.class.get( "/user.json" )
56
+ end
57
+
58
+ def projects
59
+ auth_token
60
+ @projects ||= self.class.get( "/projects.json" ).each do |x|
61
+ x['blend_config'] ||= {}
62
+ x['blend_config']['teams'] ||= []
63
+ end
64
+ end
65
+
66
+ def summary
67
+ auth_token
68
+ @projects ||= self.class.get( "/projects/summary.json" ).each do |x|
69
+ x['blend_config'] ||= {}
70
+ x['blend_config']['teams'] ||= []
71
+ end
72
+ end
73
+
74
+ def organizations
75
+ auth_token
76
+ @organizations ||= self.class.get( "/organizations.json" ).each do |o|
77
+ o['blend_config'] ||= {}
78
+ end
79
+ end
80
+
81
+ def project_info_from_room( room )
82
+ auth_token
83
+ if room =~ /[0-9]+_[^@]+@.*/
84
+ room_name = Blend::Client.hipchat_client.room_name_from_xmpp_jid( room )
85
+ else
86
+ room_name = room
87
+ end
88
+ return nil if room_name.nil? or room_name.length==0
89
+ projects.select{|x| (x['blend_config']['hipchat_room'].downcase rescue nil) == room_name.downcase}.first
90
+ end
91
+
92
+ def project_id_from_room( room )
93
+ @project_ids_from_rooms ||= {}
94
+ p = project_info_from_room( room )
95
+ if p.nil?
96
+ nil
97
+ else
98
+ @project_ids_from_rooms[room] ||= p['id']
99
+ end
100
+ end
101
+
102
+ def project_id_from_name( name )
103
+ return name if name.to_s =~ /^[0-9]+$/
104
+
105
+ projects.each do |project|
106
+ return project['id'] if project['name'].projectize == name.projectize
107
+ end
108
+
109
+ puts "Couldn't find project #{name.projectize}"
110
+ nil
111
+ end
112
+
113
+ def project( id )
114
+ return nil if id.nil?
115
+
116
+ auth_token
117
+ data = self.class.get "/projects/#{id}.json"
118
+
119
+ data['blend_config'] ||= {}
120
+ data['blend_config']['teams'] ||= []
121
+
122
+ data
123
+ end
124
+
125
+ def lookup_user( query )
126
+ auth_token
127
+ self.class.get "/users/lookup.json", { query: { query: query } }
128
+ end
129
+
130
+ def search_users( query )
131
+ auth_token
132
+ self.class.get "/users/search.json", { query: { query: query } }
133
+ end
134
+
135
+ def user_from_github_user( github )
136
+ @github_users ||= {}
137
+ @github_users[github] ||= search_users(github).select { |x| x['github_handle'] == github }.first
138
+ end
139
+
140
+ def organization_users( id )
141
+ auth_token
142
+ self.class.get "/organizations/#{id}/users.json"
143
+ end
144
+
145
+ def project_users( id )
146
+ auth_token
147
+ self.class.get "/projects/#{id}/users.json"
148
+ end
149
+
150
+ def project_add_user( project_id, user_id )
151
+ auth_token
152
+ self.class.post "/projects/#{project_id}/users/#{user_id}"
153
+ end
154
+
155
+ def project_config( id, config = nil )
156
+ auth_token
157
+ if( config.nil? )
158
+ puts "Loading config"
159
+ project = self.class.get "/projects/#{id}.json"
160
+ project['blend_config'] || {}
161
+ else
162
+ puts "Setting config #{config}"
163
+ self.class.put "/projects/#{id}.json", { query: { project: { blend_config: config } } }
164
+ @projects = nil
165
+ end
166
+ end
167
+
168
+ def project_add_team( id, github_team )
169
+ config = project_config( id )
170
+ config['teams'] ||= []
171
+ config['teams'] << github_team
172
+ project_config( id, config )
173
+ end
174
+
175
+ def project_add_hipchat( id, hipchat )
176
+ config = project_config( id )
177
+ config['hipchat_room'] = hipchat
178
+ project_config( id, config )
179
+ end
180
+
181
+ def feeds( id )
182
+ auth_token
183
+ self.class.get "/projects/#{id}/feeds.json"
184
+ end
185
+
186
+ def add_feed( project_id, feed_type, key, environment = nil )
187
+ auth_token
188
+ self.class.post "/projects/#{project_id}/feeds", {query: { feed_type: feed_type, key: key } }
189
+ end
190
+
191
+ def environments( id )
192
+ auth_token
193
+ self.class.get "/projects/#{id}/environments.json"
194
+ end
195
+
196
+ def heroku_apps( id )
197
+ auth_token
198
+ f = feeds( id ).group_by{|x| x['feed_name']}
199
+ (f['heroku'] || []).group_by { |x| x['environment']['name'].downcase}
200
+ end
201
+
202
+ def auths
203
+ auth_token
204
+ @auths ||= self.class.get "/profile/authentications.json"
205
+ end
206
+
207
+ def auth( provider )
208
+ auths.select do |x|
209
+ return x['token'] if x['provider'] == provider
210
+ end
211
+
212
+ nil
213
+ end
214
+
215
+ def activities( project_id, after, before = Time.now )
216
+ auth_token
217
+ activities = self.class.get "/projects/#{project_id}/activities.json", {query: { after: after.to_i, before: before.to_i } }
218
+ activities_stats activities, after, before # <-- so activities actually returns activities_stats? That's... weird.
219
+ end
220
+
221
+ def activities_stats( activities, after, before )
222
+ stats = { type: {},
223
+ actors_activites: {},
224
+ activities: activities,
225
+ after: after,
226
+ before: before,
227
+ active_tickets: {},
228
+ closed_tickets: {}}
229
+
230
+ activities.each do |x|
231
+ x['happened_at'] = Time.parse x['happened_at']
232
+ end.sort do |a,b|
233
+ a['happened_at'] <=> b['happened_at']
234
+ end.each do |a|
235
+ actor = a['actor_identifier']
236
+ type = a['activity_type']
237
+ stats[:type][type] ||= []
238
+ stats[:type][type] << a
239
+
240
+ if( !(type =~ /^juice/) && !(type =~ /tweet/) )
241
+ stats[:actors_activites][type] ||= {}
242
+ stats[:actors_activites][type][actor] ||= []
243
+ stats[:actors_activites][type][actor] << a
244
+ end
245
+ end
246
+
247
+ stats
248
+ end
249
+
250
+ def heroku_api( token = nil )
251
+ auth_token
252
+ ret = {}
253
+ if( token )
254
+ ret = self.class.post "/organizations/1/heroku/api_token.json", {query: {heroku_api_token: token}}
255
+ else
256
+ ret = self.class.get "/organizations/1/heroku/api_token.json"
257
+ end
258
+ ret['heroku_api_token']
259
+ end
260
+
261
+
262
+ def hipchat_api( token = nil )
263
+ auth_token
264
+ ret = {}
265
+ if( token )
266
+ ret = self.class.post "/organizations/1/hipchat/auth_token.json", {query: {hipchat_auth_token: token}}
267
+ else
268
+ ret = self.class.get "/organizations/1/hipchat/auth_token.json"
269
+ end
270
+ ret['hipchat_auth_token']
271
+ end
272
+
273
+ def hipchat_check
274
+ hipchat_client = Blend::Client.hipchat_client
275
+
276
+ rooms = {}
277
+ hipchat_client.rooms.each { |x| rooms[x['name']] = [] }
278
+
279
+ projects.each do |project|
280
+ room = project['blend_config']['hipchat_room']
281
+ if room
282
+ rooms[room] ||= []
283
+ rooms[room] << project['name']
284
+ end
285
+ end
286
+
287
+ rooms.keys.sort { |a,b| a <=> b }.collect do |room|
288
+ if rooms[room].size == 0
289
+ rooms[room] << "_unassigned_"
290
+ end
291
+ { room: room, projects: rooms[room] }
292
+ end
293
+ end
294
+
295
+ def github_team_check
296
+ github_client = Blend::Client.github_client
297
+
298
+ teams = {}
299
+ github_client.list_teams.each do |team|
300
+ teams[team.name] = []
301
+ end
302
+
303
+ projects.each do |project|
304
+ project['blend_config']['teams'].each do |x|
305
+ teams[x] << project
306
+ end
307
+ end
308
+
309
+ teams
310
+ end
311
+
312
+
313
+ def config_file
314
+ "#{ENV['HOME']}/.juice.yml"
315
+ end
316
+
317
+ def destroy_auth_token
318
+ _prev_options = @options
319
+ begin
320
+ return File.delete config_file
321
+ rescue Errno::ENOENT
322
+ raise Exceptions::AlreadyLoggedOut
323
+ ensure
324
+ @options = {}
325
+ end
326
+ _prev_options
327
+ end
328
+
329
+
330
+ def check_credentials( username, password )
331
+ response = self.class.post '/auth', {body: {username: username, password: password}}
332
+ raise Exceptions::LoginAuthenticationFailure if response['auth_token'].nil?
333
+ @options = response.parsed_response if @options['auth_token'].nil?
334
+ end
335
+
336
+
337
+ def query_login
338
+
339
+ begin
340
+ response = read_auth_token
341
+ @options = response
342
+
343
+ # If it doesn't contain an auth_token, it's as good as not existing:
344
+
345
+ raise Exceptions::AlreadyLoggedIn
346
+
347
+ rescue Errno::ENOENT
348
+ username = ask( "Username : " ) { |q| q.echo = true }
349
+ password = ask( "Password : " ) { |q| q.echo = '.' }
350
+
351
+ @options = check_credentials( username, password )
352
+
353
+ write_auth_token( @options )
354
+ end
355
+ end
356
+
357
+
358
+ def read_auth_token
359
+ begin
360
+ response = YAML.load( File.read( config_file ) )
361
+ raise StandardError.new unless response['auth_token']
362
+ response
363
+ rescue StandardError
364
+ raise Errno::ENOENT
365
+ end
366
+ end
367
+
368
+
369
+ def write_auth_token( options )
370
+ File.open( config_file, "w" ) do |o|
371
+ o.puts YAML.dump( options || {} )
372
+ end
373
+ return options
374
+ end
375
+
376
+
377
+ def auth_token
378
+
379
+ # Just return it if it's been set manually. This is accomplished when client is
380
+ # instantiated with JuiceClient.new(auth_token: ---)
381
+ if !@options['auth_token'].nil?
382
+ self.class.default_params auth_token: @options['auth_token']
383
+ return @options['auth_token']
384
+ end
385
+
386
+ if ENV['JUICE_AUTH_TOKEN'].nil?
387
+ begin
388
+ query_login
389
+ rescue Exceptions::AlreadyLoggedIn
390
+ end
391
+ else
392
+ @options = { 'auth_token' => ENV['JUICE_AUTH_TOKEN'] }
393
+ end
394
+ self.class.default_params auth_token: @options['auth_token']
395
+ @options['auth_token']
396
+ end
397
+
398
+ end
399
+ end
400
+ end