blend 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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