slowlane 1.0.1 → 1.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ccbe4b06a1d413c6aab728a2ad8b27db74edd98e
4
- data.tar.gz: d9b7702e713547e036b2b980b74836f7de9629f6
3
+ metadata.gz: 83a8d79b2005d87f2b9c35e79bc9942304d0b09b
4
+ data.tar.gz: e3effd5f51ce6eab958a99b21d2fdb7012597ab5
5
5
  SHA512:
6
- metadata.gz: bf67a421c0e1aabbfb703decc1e0c2ba9caa6fe3c09102c678c511fcedb35824a9293bc364dc267dc813c9d2a19e9f892be2917011f2915cc87d3bf469b946c4
7
- data.tar.gz: 73e2586bb0b59828781d86c223538fd7244fe0478f44a9affd4343a503a1253f0ae293cd422397afe90c66b9c29569335d8dd2bdf48c214a345566f387d37ead
6
+ metadata.gz: 09a2c085299bced49a2e435ac1c400c8ac062d67c7499734c06da1bccc3732359a257ba13d3e88016ae3b4e99e40e748f33fbd7db52e0ec0fdb11e2d374f1690
7
+ data.tar.gz: 25b3805ba96923d11d22f28b6da47091c0dbbd706c2ef35097dc6744cfbb456d5f5715175948566081d3908080f46c9a60dc6c6c7be603a23fd3e794849ab1b1
data/README.md CHANGED
@@ -23,6 +23,14 @@ Let us know if you like this! [We're only one tweet away!](http://twitter.com/sl
23
23
  ## Kudos
24
24
  - [Fastlane](https://github.com/fastlane/fastlane)
25
25
  - [Shenzhen](https://github.com/nomad/shenzhen)
26
+ - [Atlantispro](https://github.com/Legoless/Atlantis)
27
+
28
+ ## Changes
29
+ 1.1.0
30
+ - added slowlane-fabric (organization, tester, apps)
31
+
32
+ 1.0.0
33
+ - first release
26
34
 
27
35
  ## Status
28
36
  Current able to list most items, moving on to create & delete
@@ -32,6 +40,7 @@ Current able to list most items, moving on to create & delete
32
40
  results in binaries:
33
41
  - `slowlane-itunes`
34
42
  - `slowlane-portal`
43
+ - `slowlane-fabric`
35
44
  - `slowlane-ipa` (NOTE: this might change to a more generic `slowlane-ios` command)
36
45
 
37
46
  ### Working
@@ -42,6 +51,9 @@ results in binaries:
42
51
  - `SLOWLANE_PORTAL_USER`
43
52
  - `SLOWLANE_PORTAL_PASSWORD`
44
53
  - `SLOWLANE_PORTAL_TEAM`
54
+ - `SLOWLANE_FABRIC_USER`
55
+ - `SLOWLANE_FABRIC_PASSWORD`
56
+ - `SLOWLANE_FABRIC_TEAM`
45
57
 
46
58
  #### Portal
47
59
  - `slowlane-portal app list`
@@ -63,6 +75,10 @@ results in binaries:
63
75
  - `slowlane-itunes build list`
64
76
  - `slowlane-itunes build upload <bundle_id> <ipa_file>`
65
77
 
78
+ #### Fabric
79
+ - `slowlane-fabric app list`
80
+ - `slowlane-fabric tester list <bundle_id>`
81
+
66
82
  #### Ipa
67
83
  - `slowlane-ipa info <ipa_file>`
68
84
 
@@ -90,5 +106,11 @@ A lot is still focusing on the happy path , we need to catch better the errors a
90
106
  - create | submit | delete app
91
107
  - all other commands
92
108
 
109
+ #### Fabric
110
+ - info organization
111
+ - create|delete|list groups
112
+ - create|delete|list testers
113
+ - list, add device
114
+
93
115
  #### Playstore
94
116
  - all commands
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require "thor"
5
+
6
+ require_relative '../lib/slowlane/fabric/app.rb'
7
+ require_relative '../lib/slowlane/fabric/tester.rb'
8
+ require_relative '../lib/slowlane/fabric/organization.rb'
9
+ require_relative '../lib/slowlane/fabric/command.rb'
10
+
11
+ SlowlaneFabric.start(ARGV)
@@ -0,0 +1,37 @@
1
+ require_relative './util.rb'
2
+ require_relative './client.rb'
3
+ require 'terminal-table'
4
+
5
+ module Slowlane
6
+ module Fabric
7
+ class App <Thor
8
+
9
+ desc "apps", "get list of apps"
10
+ def list
11
+
12
+ c=Utils.credentials(options)
13
+
14
+ fabric = Slowlane::Fabric::Client.new
15
+ fabric.username = c.username
16
+ fabric.password = c.password
17
+ fabric.team = Utils.team(options)
18
+ apps = fabric.list_apps()
19
+
20
+ headings = ['id', 'name', 'bundle_id']
21
+ rows = []
22
+
23
+ apps.each do |app|
24
+ row = []
25
+ row << app['id']
26
+ row << app['name']
27
+ row << app['bundle_identifier']
28
+ rows << row
29
+ end
30
+
31
+ table = Terminal::Table.new :headings => headings, :rows => rows
32
+ puts table
33
+
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,333 @@
1
+ require 'mechanize'
2
+ require 'uri'
3
+ require 'json'
4
+ require 'logger'
5
+
6
+ module Slowlane
7
+ module Fabric
8
+ class Client
9
+ attr_accessor :host, :agent
10
+ attr_accessor :username,:password,:team
11
+ attr_accessor :developer_token, :access_token, :csrf_token, :login_data, :team_id
12
+
13
+ def initialize
14
+ self.agent = Mechanize.new
15
+
16
+ self.host = "fabric.io"
17
+ end
18
+
19
+ def get(uri, parameters = [], referer = nil, headers = {})
20
+ uri = ::File.join("https://#{self.host}", uri) unless /^https?/ === uri
21
+
22
+ #puts "Requesting: #{uri}"
23
+
24
+ unless (self.developer_token.nil?)
25
+ headers['X-CRASHLYTICS-DEVELOPER-TOKEN'] = self.developer_token
26
+ end
27
+
28
+ unless (self.access_token.nil?)
29
+ headers['X-CRASHLYTICS-ACCESS-TOKEN'] = self.access_token
30
+ end
31
+
32
+ unless (self.csrf_token.nil?)
33
+ headers['X-CSRF-Token'] = self.csrf_token
34
+ end
35
+
36
+ headers['X-Requested-With'] = 'XMLHttpRequest'
37
+
38
+ 3.times do
39
+
40
+ self.agent.get(uri, parameters, referer, headers)
41
+
42
+ return self.agent.page
43
+ end
44
+
45
+ #raise NetworkError
46
+ raise Error
47
+ end
48
+
49
+ #
50
+ # Handles login and CSRF tokens
51
+ #
52
+
53
+ def bootstrap
54
+
55
+ if (self.csrf_token.nil?)
56
+ csrf!
57
+ end
58
+
59
+ #
60
+ # First need developer token
61
+ #
62
+ if (self.developer_token.nil?)
63
+ config!
64
+ end
65
+
66
+ #
67
+ # Need to login too
68
+ #
69
+ if (self.access_token.nil?)
70
+ login!
71
+ end
72
+ end
73
+
74
+ def list_devices(distribution_list)
75
+ people = list_people(distribution_list)
76
+
77
+ people_list = []
78
+
79
+ people.each do |person|
80
+ people_list << person.id
81
+ end
82
+
83
+ self.agent.post('/dashboard/team/export/devices/', { "members" => people_list.join('|'), "csrfmiddlewaretoken" => self.agent.page.parser.css("[name='csrfmiddlewaretoken']")[0]['value'] } )
84
+
85
+ device_list = self.agent.page.body.split( /\r?\n/ )
86
+
87
+ # Remove first one
88
+ device_list.shift
89
+
90
+ devices = []
91
+
92
+ device_list.each do |dev|
93
+ #puts dev
94
+
95
+ device = Device.new
96
+ device.udid = dev.split(/\t/)[0]
97
+ device.name = dev.split(/\t/)[1]
98
+
99
+ devices << device
100
+ end
101
+
102
+ devices
103
+ end
104
+
105
+ def list_apps
106
+ bootstrap
107
+
108
+ page = get("/api/v2/organizations/#{self.team_id}/apps")
109
+
110
+ apps = JSON.parse(page.body)
111
+
112
+ return apps
113
+ end
114
+
115
+ def list_organizations
116
+ bootstrap
117
+
118
+ page = get("/api/v2/organizations")
119
+
120
+ organizations = JSON.parse(page.body)
121
+
122
+ return organizations
123
+ end
124
+
125
+ def find_app_by_bundle_id(bundle_id)
126
+ apps = list_apps()
127
+ apps.find { |app| app['bundle_identifier'] == bundle_id }
128
+ end
129
+
130
+ def list_testers(app_id)
131
+ bootstrap
132
+ page = get("/api/v2/organizations/#{self.team_id}/apps/#{app_id}/beta_distribution/testers_in_organization?include_developers=true")
133
+
134
+ testers = JSON.parse(page.body)
135
+
136
+ return testers['testers']
137
+ end
138
+
139
+ def list_people(group)
140
+ bootstrap
141
+
142
+ apps = list_apps
143
+
144
+ all_testers = {}
145
+
146
+ apps.each do |app|
147
+ testers = list_testers (app['id'])
148
+
149
+ #
150
+ # For each tester go through it's devices and add them
151
+ #
152
+
153
+ testers.each do |tester|
154
+
155
+ #
156
+ # If tester is not yet in, create it
157
+ #
158
+
159
+ if all_testers[tester['id']].nil?
160
+
161
+ person = Person.new
162
+ person.id = tester['id']
163
+ person.name = tester['name']
164
+ person.email = tester['email']
165
+ person.groups = []
166
+ person.devices = {}
167
+
168
+ add_group = false
169
+
170
+ if tester['groups']
171
+ groups = tester['groups']
172
+
173
+ groups.each do |current_group|
174
+
175
+ person_group = Group.new
176
+ person_group.id = current_group['id']
177
+ person_group.name = current_group['name']
178
+ person_group.alias = current_group['alias']
179
+
180
+ person.groups << person_group
181
+
182
+ if person_group.name == group or person_group.alias == group
183
+ add_group = true
184
+ end
185
+ end
186
+ end
187
+
188
+ if (add_group == true or group.nil?) and !person.name.empty?
189
+ all_testers[person.id] = person
190
+ end
191
+ else
192
+ person = all_testers[tester['id']]
193
+ end
194
+
195
+ append_devices(person, tester['devices'])
196
+ end
197
+ end
198
+
199
+ return all_testers
200
+ end
201
+
202
+ def list_devices(group)
203
+ bootstrap
204
+
205
+ testers = list_people(group)
206
+
207
+ devices = {}
208
+
209
+ testers.each do |id, tester|
210
+ devices = devices.merge (tester.devices)
211
+ end
212
+
213
+ return devices.values
214
+ end
215
+
216
+ def list_groups
217
+ testers = list_people(nil)
218
+
219
+ groups = {}
220
+
221
+ testers.each do |id, tester|
222
+ tester.groups.each do |group|
223
+ groups[group.id] = group
224
+ end
225
+ end
226
+
227
+ return groups.values
228
+ end
229
+
230
+ private
231
+
232
+ def append_devices (person, devices)
233
+
234
+ if devices.nil?
235
+ return nil
236
+ end
237
+
238
+ devices.each do |device|
239
+
240
+ if person.devices[device['identifier']].nil?
241
+ current_device = Device.new
242
+ current_device.manufacturer = device['manufacturer']
243
+ current_device.model = device['model']
244
+ current_device.os_version = device['os_version']
245
+ current_device.identifier = device['identifier']
246
+ current_device.name = device['name']
247
+ current_device.platform = device['platform']
248
+ current_device.model_name = device['model_name']
249
+
250
+ person.devices[current_device.identifier] = current_device
251
+ end
252
+
253
+ end
254
+ end
255
+
256
+ def csrf!
257
+ page = get('/login')
258
+
259
+ token = page.at('meta[name="csrf-token"]')[:content]
260
+
261
+ unless token.nil?
262
+ self.csrf_token = token
263
+ end
264
+ end
265
+
266
+ def config!
267
+ page = get ('/api/v2/client_boot/config_data')
268
+
269
+ config_object = JSON.parse(page.body)
270
+
271
+ unless config_object['developer_token'].nil?
272
+ self.developer_token = config_object['developer_token']
273
+ else
274
+ raise Error
275
+ #raise UnsuccessfulAuthenticationError
276
+ end
277
+ end
278
+
279
+ def login!
280
+ begin
281
+
282
+ session = self.agent.post('https://fabric.io/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' } )
283
+
284
+ rescue Mechanize::ResponseCodeError => ex
285
+ message = JSON.parse(ex.page.body)
286
+
287
+ unless message['message'].nil?
288
+ puts message['message']
289
+ end
290
+ end
291
+
292
+ self.login_data = JSON.parse(self.agent.page.body)
293
+
294
+ unless self.login_data['token'].nil?
295
+ self.access_token = self.login_data['token']
296
+
297
+ select_team!
298
+ else
299
+ raise Error
300
+ #raise UnsuccessfulAuthenticationError
301
+ end
302
+ end
303
+
304
+ def select_team!
305
+
306
+ if self.login_data['organizations'].nil?
307
+ raise Error
308
+ # raise UnknownTeamError
309
+ end
310
+
311
+ teams = self.login_data['organizations']
312
+
313
+ teams.each do |team|
314
+ #puts team['name']
315
+
316
+ if team['alias'] == self.team or team['name'] == self.team
317
+ self.team_id = team['id']
318
+
319
+ break
320
+ end
321
+ end
322
+
323
+ if self.team_id.nil?
324
+ #raise UnknownTeamError
325
+ raise Error
326
+ end
327
+
328
+ #puts "SELECTED TEAM: #{self.team_id}"
329
+ end
330
+
331
+ end
332
+ end
333
+ end
@@ -0,0 +1,17 @@
1
+ require 'thor'
2
+ class SlowlaneFabric < Thor
3
+
4
+ class_option :username , :default => '<username>' , :required => true, :desc => 'username [SLOWLANE_FABRIC_USERNAME]'
5
+ class_option :password , :default => '<password>' , :required => true, :desc => 'password [SLOWLANE_FABRIC_PASSWORD]'
6
+ class_option :team , :default => '<team>' , :required => false, :desc => 'team [SLOWLANE_FABRIC_TEAM]'
7
+
8
+ desc "app SUBCOMMAND ...ARGS", "manage apps"
9
+ subcommand "app", Slowlane::Fabric::App
10
+
11
+ desc "organization SUBCOMMAND ...ARGS", "manage organizations"
12
+ subcommand "organization", Slowlane::Fabric::Organization
13
+
14
+ desc "tester SUBCOMMAND ...ARGS", "manage testers"
15
+ subcommand "tester", Slowlane::Fabric::Tester
16
+
17
+ end
@@ -0,0 +1,41 @@
1
+ require_relative './util.rb'
2
+ require_relative './client.rb'
3
+ require 'terminal-table'
4
+
5
+ module Slowlane
6
+ module Fabric
7
+ class Organization <Thor
8
+
9
+ desc "list", "get list of organizations"
10
+ def list
11
+
12
+ c=Utils.credentials(options)
13
+
14
+ fabric = Slowlane::Fabric::Client.new
15
+ fabric.username = c.username
16
+ fabric.password = c.password
17
+ fabric.team = Utils.team(options)
18
+ orgs = fabric.list_organizations
19
+
20
+ puts orgs
21
+ headings = ['id', 'name', 'alias','accounts_count', 'build_secret', 'api_key']
22
+ rows = []
23
+
24
+ orgs.each do |org|
25
+ row = []
26
+ row << org['id']
27
+ row << org['name']
28
+ row << org['alias']
29
+ row << org['accounts_count']
30
+ row << org['build_secret']
31
+ row << org['api_key']
32
+ rows << row
33
+ end
34
+
35
+ table = Terminal::Table.new :headings => headings, :rows => rows
36
+ puts table
37
+
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,45 @@
1
+ require_relative './util.rb'
2
+ require_relative './client.rb'
3
+ require 'terminal-table'
4
+
5
+ module Slowlane
6
+ module Fabric
7
+ class Tester <Thor
8
+
9
+ desc "list", "get list of tester"
10
+ def list(bundle_id)
11
+
12
+ c=Utils.credentials(options)
13
+
14
+ fabric = Slowlane::Fabric::Client.new
15
+ fabric.username = c.username
16
+ fabric.password = c.password
17
+ fabric.team = Utils.team(options)
18
+ app = fabric.find_app_by_bundle_id(bundle_id)
19
+ app_id = app['id']
20
+ testers = fabric.list_testers(app_id)
21
+
22
+ headings = ['id', 'name', 'email','groups' ]
23
+ rows = []
24
+
25
+ testers.each do |tester|
26
+ row = []
27
+ row << tester['id']
28
+ row << tester['name']
29
+ row << tester['email']
30
+ groups = tester['groups']
31
+ if groups.nil?
32
+ row << ""
33
+ else
34
+ row << groups.map { |g| g['name'] }.join(",")
35
+ end
36
+ rows << row
37
+ end
38
+
39
+ table = Terminal::Table.new :headings => headings, :rows => rows
40
+ puts table
41
+
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,47 @@
1
+ require 'ostruct'
2
+
3
+ class Utils
4
+ def self.credentials(options)
5
+
6
+ credentials = OpenStruct.new
7
+
8
+ if ENV['SLOWLANE_FABRIC_USERNAME']
9
+ credentials.username=ENV['SLOWLANE_FABRIC_USERNAME']
10
+ else
11
+ if options[:username] == '<username>'
12
+ puts "username is required"
13
+ exit(-1)
14
+ else
15
+ credentials.username=options[:username]
16
+ end
17
+ end
18
+
19
+ if ENV['SLOWLANE_FABRIC_PASSWORD']
20
+ credentials.password=ENV['SLOWLANE_FABRIC_PASSWORD']
21
+ else
22
+ if options[:password] == '<password>'
23
+ puts "password is required"
24
+ exit(-1)
25
+ else
26
+ credentials.password=options[:username]
27
+ end
28
+ end
29
+ return credentials
30
+ end
31
+
32
+ def self.team(options)
33
+
34
+ if ENV['SLOWLANE_FABRIC_TEAM']
35
+ team=ENV['SLOWLANE_FABRIC_TEAM']
36
+ else
37
+ if options[:team] == '<team>'
38
+ puts "team is required"
39
+ exit(-1)
40
+ else
41
+ team=options[:team]
42
+ end
43
+ end
44
+ return team
45
+ end
46
+
47
+ end
data/slowlane.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "slowlane"
5
- s.version = "1.0.1"
5
+ s.version = "1.1.0"
6
6
  s.platform = Gem::Platform::RUBY
7
7
  s.license = 'MIT'
8
8
  s.authors = ["Patrick Debois"]
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: slowlane
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrick Debois
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-05-12 00:00:00.000000000 Z
11
+ date: 2016-05-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -127,6 +127,7 @@ email:
127
127
  - patrick.debois@jedi.be
128
128
  executables:
129
129
  - complete.sh
130
+ - slowlane-fabric
130
131
  - slowlane-ipa
131
132
  - slowlane-itunes
132
133
  - slowlane-portal
@@ -138,9 +139,16 @@ files:
138
139
  - README.md
139
140
  - Rakefile
140
141
  - bin/complete.sh
142
+ - bin/slowlane-fabric
141
143
  - bin/slowlane-ipa
142
144
  - bin/slowlane-itunes
143
145
  - bin/slowlane-portal
146
+ - lib/slowlane/fabric/app.rb
147
+ - lib/slowlane/fabric/client.rb
148
+ - lib/slowlane/fabric/command.rb
149
+ - lib/slowlane/fabric/organization.rb
150
+ - lib/slowlane/fabric/tester.rb
151
+ - lib/slowlane/fabric/util.rb
144
152
  - lib/slowlane/itunes/app.rb
145
153
  - lib/slowlane/itunes/build.rb
146
154
  - lib/slowlane/itunes/command.rb