atlantispro 0.1.2 → 0.2.0

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