atlantispro 0.1.2 → 0.2.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.
@@ -1,12 +1,12 @@
1
1
  command :logout do |c|
2
- c.syntax = 'testflight logout'
3
- c.summary = 'Remove account credentials'
2
+ c.syntax = 'distribution logout'
3
+ c.summary = 'Remove stored account credentials'
4
4
  c.description = ''
5
5
 
6
6
  c.action do |args, options|
7
- say_error "You are not authenticated" and abort unless Security::InternetPassword.find(:server => Atlantis::Portal::HOST)
7
+ say_error "You are not authenticated" and abort unless Security::InternetPassword.find(:server => service.host)
8
8
 
9
- Security::InternetPassword.delete(:server => Atlantis::Portal::HOST)
9
+ Security::InternetPassword.delete(:server => service.host)
10
10
 
11
11
  say_ok "Account credentials removed"
12
12
  end
@@ -1,43 +1,13 @@
1
1
  command :'people' do |c|
2
- c.syntax = 'testflight people [DISTRIBUTION_LIST]'
3
- c.summary = 'Lists people currently signed up to TestFlight'
2
+ c.syntax = 'distribution people'
3
+ c.summary = 'Lists testers currently signed up to the service'
4
4
  c.description = ''
5
5
 
6
6
  c.action do |args, options|
7
7
 
8
- distribution_list = args.join(" ").strip
8
+ people = try{service.list_people(arguments.group)}
9
+ people = people.values
9
10
 
10
- if (distribution_list.empty?)
11
- distribution_list = 'all'
12
- end
13
-
14
- people = try{agent.list_people(distribution_list)}
15
-
16
- if (agent.format == "csv")
17
- csv_string = CSV.generate do |csv|
18
- csv << ["ID", "User", "Email", "Devices"]
19
-
20
- people.each do |person|
21
- csv << [person.id, person.name, person.email, person.devices]
22
- end
23
- end
24
-
25
- puts csv_string
26
-
27
- else
28
- title = "Listing people"
29
-
30
- table = Terminal::Table.new :title => title do |t|
31
- t << ["ID", "User", "Email", "Devices"]
32
- t.add_separator
33
- people.each do |person|
34
- t << [person.id, person.name, person.email, person.devices]
35
- end
36
- end
37
-
38
- #table.align_column 2, :center
39
-
40
- puts table
41
- end
11
+ output('Testers', ["ID", "Name", "Email"], people)
42
12
  end
43
13
  end
@@ -0,0 +1,315 @@
1
+ require 'mechanize'
2
+ require 'security'
3
+ require 'uri'
4
+ require 'json'
5
+ require 'logger'
6
+ require 'nokogiri'
7
+
8
+ include Atlantis::Portal
9
+
10
+ module Atlantis
11
+ module Portal
12
+ class CrashlyticsService < ::Service
13
+ attr_accessor :developer_token, :access_token, :csrf_token, :login_data, :team_id
14
+
15
+ def initialize
16
+ super
17
+
18
+ self.host = "crashlytics.com"
19
+ end
20
+
21
+ def get(uri, parameters = [], referer = nil, headers = {})
22
+ uri = ::File.join("https://#{self.host}", uri) unless /^https?/ === uri
23
+
24
+ #puts "Requesting: #{uri}"
25
+
26
+ unless (self.developer_token.nil?)
27
+ headers['X-CRASHLYTICS-DEVELOPER-TOKEN'] = self.developer_token
28
+ end
29
+
30
+ unless (self.access_token.nil?)
31
+ headers['X-CRASHLYTICS-ACCESS-TOKEN'] = self.access_token
32
+ end
33
+
34
+ unless (self.csrf_token.nil?)
35
+ headers['X-CSRF-Token'] = self.csrf_token
36
+ end
37
+
38
+ headers['X-Requested-With'] = 'XMLHttpRequest'
39
+
40
+ 3.times do
41
+
42
+ agent.get(uri, parameters, referer, headers)
43
+
44
+ return agent.page
45
+ end
46
+
47
+ raise NetworkError
48
+ end
49
+
50
+ #
51
+ # Handles login and CSRF tokens
52
+ #
53
+
54
+ def bootstrap
55
+
56
+ if (self.csrf_token.nil?)
57
+ csrf!
58
+ end
59
+
60
+ #
61
+ # First need developer token
62
+ #
63
+ if (self.developer_token.nil?)
64
+ config!
65
+ end
66
+
67
+ #
68
+ # Need to login too
69
+ #
70
+ if (self.access_token.nil?)
71
+ login!
72
+ end
73
+ end
74
+
75
+ def list_devices(distribution_list)
76
+ people = list_people(distribution_list)
77
+
78
+ people_list = []
79
+
80
+ people.each do |person|
81
+ people_list << person.id
82
+ end
83
+
84
+ agent.post('/dashboard/team/export/devices/', { "members" => people_list.join('|'), "csrfmiddlewaretoken" => agent.page.parser.css("[name='csrfmiddlewaretoken']")[0]['value'] } )
85
+
86
+ device_list = agent.page.body.split( /\r?\n/ )
87
+
88
+ # Remove first one
89
+ device_list.shift
90
+
91
+ devices = []
92
+
93
+ device_list.each do |dev|
94
+ #puts dev
95
+
96
+ device = Device.new
97
+ device.udid = dev.split(/\t/)[0]
98
+ device.name = dev.split(/\t/)[1]
99
+
100
+ devices << device
101
+ end
102
+
103
+ devices
104
+ end
105
+
106
+ def list_apps
107
+ bootstrap
108
+
109
+ page = get("/api/v2/organizations/#{self.team_id}/apps")
110
+
111
+ apps = JSON.parse(page.body)
112
+
113
+ return apps
114
+ end
115
+
116
+ def list_testers(app_id)
117
+ bootstrap
118
+ page = get("/api/v2/organizations/#{self.team_id}/apps/#{app_id}/beta_distribution/testers_in_organization?include_developers=true")
119
+
120
+ testers = JSON.parse(page.body)
121
+
122
+ return testers['testers']
123
+ end
124
+
125
+ def list_people(group)
126
+ bootstrap
127
+
128
+ apps = list_apps
129
+
130
+ all_testers = {}
131
+
132
+ apps.each do |app|
133
+ testers = list_testers (app['id'])
134
+
135
+ #
136
+ # For each tester go through it's devices and add them
137
+ #
138
+
139
+ testers.each do |tester|
140
+
141
+ #
142
+ # If tester is not yet in, create it
143
+ #
144
+
145
+ if all_testers[tester['id']].nil?
146
+
147
+ person = Person.new
148
+ person.id = tester['id']
149
+ person.name = tester['name']
150
+ person.email = tester['email']
151
+ person.groups = []
152
+ person.devices = {}
153
+
154
+ add_group = false
155
+
156
+ if tester['groups']
157
+ groups = tester['groups']
158
+
159
+ groups.each do |current_group|
160
+
161
+ person_group = Group.new
162
+ person_group.id = current_group['id']
163
+ person_group.name = current_group['name']
164
+ person_group.alias = current_group['alias']
165
+
166
+ person.groups << person_group
167
+
168
+ if person_group.name == group or person_group.alias == group
169
+ add_group = true
170
+ end
171
+ end
172
+ end
173
+
174
+ if (add_group == true or group.nil?) and !person.name.empty?
175
+ all_testers[person.id] = person
176
+ end
177
+ else
178
+ person = all_testers[tester['id']]
179
+ end
180
+
181
+ append_devices(person, tester['devices'])
182
+ end
183
+ end
184
+
185
+ return all_testers
186
+ end
187
+
188
+ def list_devices(group)
189
+ bootstrap
190
+
191
+ testers = list_people(group)
192
+
193
+ devices = {}
194
+
195
+ testers.each do |id, tester|
196
+ devices = devices.merge (tester.devices)
197
+ end
198
+
199
+ return devices.values
200
+ end
201
+
202
+ def list_groups
203
+ testers = list_people(nil)
204
+
205
+ groups = {}
206
+
207
+ testers.each do |id, tester|
208
+ tester.groups.each do |group|
209
+ groups[group.id] = group
210
+ end
211
+ end
212
+
213
+ return groups.values
214
+ end
215
+
216
+ private
217
+
218
+ def append_devices (person, devices)
219
+
220
+ if devices.nil?
221
+ return nil
222
+ end
223
+
224
+ devices.each do |device|
225
+
226
+ if person.devices[device['identifier']].nil?
227
+ current_device = Device.new
228
+ current_device.manufacturer = device['manufacturer']
229
+ current_device.model = device['model']
230
+ current_device.os_version = device['os_version']
231
+ current_device.identifier = device['identifier']
232
+ current_device.name = device['name']
233
+ current_device.platform = device['platform']
234
+ current_device.model_name = device['model_name']
235
+
236
+ person.devices[current_device.identifier] = current_device
237
+ end
238
+
239
+ end
240
+ end
241
+
242
+ def csrf!
243
+ page = get('/login')
244
+
245
+ token = page.at('meta[name="csrf-token"]')[:content]
246
+
247
+ unless token.nil?
248
+ self.csrf_token = token
249
+ end
250
+ end
251
+
252
+ def config!
253
+ page = get ('/api/v2/client_boot/config_data')
254
+
255
+ config_object = JSON.parse(page.body)
256
+
257
+ unless config_object['developer_token'].nil?
258
+ self.developer_token = config_object['developer_token']
259
+ else
260
+ raise UnsuccessfulAuthenticationError
261
+ end
262
+ end
263
+
264
+ def login!
265
+ begin
266
+
267
+ session = agent.post('https://crashlytics.com/api/v2/session', { "email" => self.username, "password" => self.password }, { 'X-CRASHLYTICS-DEVELOPER-TOKEN' => self.developer_token, 'X-CSRF-Token' => self.csrf_token, 'X-Requested-With' => 'XMLHttpRequest' } )
268
+
269
+ rescue Mechanize::ResponseCodeError => ex
270
+ message = JSON.parse(ex.page.body)
271
+
272
+ unless message['message'].nil?
273
+ puts message['message']
274
+ end
275
+ end
276
+
277
+ self.login_data = JSON.parse(agent.page.body)
278
+
279
+ unless self.login_data['token'].nil?
280
+ self.access_token = self.login_data['token']
281
+
282
+ select_team!
283
+ else
284
+ raise UnsuccessfulAuthenticationError
285
+ end
286
+ end
287
+
288
+ def select_team!
289
+
290
+ if self.login_data['organizations'].nil?
291
+ raise UnknownTeamError
292
+ end
293
+
294
+ teams = self.login_data['organizations']
295
+
296
+ teams.each do |team|
297
+ #puts team['name']
298
+
299
+ if team['alias'] == self.team or team['name'] == self.team
300
+ self.team_id = team['id']
301
+
302
+ break
303
+ end
304
+ end
305
+
306
+ if self.team_id.nil?
307
+ raise UnknownTeamError
308
+ end
309
+
310
+ #puts "SELECTED TEAM: #{self.team_id}"
311
+ end
312
+
313
+ end
314
+ end
315
+ end
@@ -16,8 +16,22 @@ end
16
16
  module Atlantis
17
17
  module Portal
18
18
  module Helpers
19
+
20
+ def arguments
21
+ unless @arguments
22
+ @arguments = Atlantis::Portal::CommandArguments.new
23
+ end
24
+
25
+ @arguments
26
+ end
27
+
19
28
  def agent
20
29
  unless @agent
30
+
31
+ #
32
+ # Web connecting agent
33
+ #
34
+
21
35
  @agent = Atlantis::Portal::Agent.new
22
36
 
23
37
  @agent.instance_eval do
@@ -34,6 +48,16 @@ module Atlantis
34
48
  @agent
35
49
  end
36
50
 
51
+ def service
52
+
53
+ unless @service
54
+ @service = arguments.service.casecmp('crashlytics') == 0 ? Atlantis::Portal::CrashlyticsService.new : Atlantis::Portal::TestFlightService.new
55
+ @service.arguments = arguments
56
+ end
57
+
58
+ @service
59
+ end
60
+
37
61
  def pluralize(n, singular, plural = nil)
38
62
  n.to_i == 1 ? "1 #{singular}" : "#{n} #{plural || singular + 's'}"
39
63
  end
@@ -44,10 +68,59 @@ module Atlantis
44
68
  begin
45
69
  yield
46
70
  rescue UnsuccessfulAuthenticationError
47
- say_error "Could not authenticate with TestFlight. Check that your username & password are correct, and that your membership is valid and all pending Terms of Service & agreements are accepted. If this problem continues, try logging into https://testflightapp.com/ from a browser to see what's going on." and abort
71
+ say_error "Could not authenticate with the service. Check that your username & password are correct, and that your membership is valid and all pending Terms of Service & agreements are accepted. If this problem continues, try logging into the service from a browser to see what's going on." and abort
72
+ rescue UnknownTeamError
73
+ say_error "Could not find the specified team. Check if your team parameter is correct."
48
74
  end
49
75
  end
50
76
 
77
+ def output (title, fields, data)
78
+
79
+ if arguments.format == "csv"
80
+ output_string = CSV.generate do |csv|
81
+ csv << fields
82
+
83
+ data.each do |row|
84
+ csv << array_for_object(fields, row)
85
+ end
86
+ end
87
+
88
+ elsif arguments.format == "json"
89
+ objects = []
90
+
91
+ data.each do |row|
92
+ object = {}
93
+
94
+ fields.each do |field|
95
+ object[field.downcase] = row[field.downcase]
96
+ end
97
+
98
+ objects << object
99
+ end
100
+
101
+ output_string = JSON.generate(objects)
102
+ else
103
+ output_string = Terminal::Table.new :title => title do |t|
104
+ t << fields
105
+ t.add_separator
106
+ data.each do |row|
107
+ t << array_for_object(fields, row)
108
+ end
109
+ end
110
+ end
111
+
112
+ puts output_string
113
+ end
114
+ end
115
+
116
+ def array_for_object(fields, row)
117
+ properties = []
118
+
119
+ fields.each do |field|
120
+ properties << row[field.downcase]
121
+ end
122
+
123
+ return properties
51
124
  end
52
125
  end
53
126
  end