spaceship 0.3.4 → 0.4.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: e89341fcf932310a4b78c176c5b086e95ffab9da
4
- data.tar.gz: 4920d972e2c514365ccd3fa9db4269a7ec3fb212
3
+ metadata.gz: bee636c5de7df79ede26deda935649fb5a738e3f
4
+ data.tar.gz: 0e846d25300e1c0658e036f208991f841d55b5d7
5
5
  SHA512:
6
- metadata.gz: 5cb359bd10efbd58190b9542e35bf1de007af2441c2a39346a77f6877f562a7119cfbcc211609e677ce7ff53655b62ae09f154154f10219a6cb64a26f2d52859
7
- data.tar.gz: 2b1727cdacbcc278cfdda3c82b39f54ebf5f0c259e7ce6c0e9af8a3aa2acf19295a83dc541878a45cb29fb14f8e8df09ca16614c6d3ae1d1b84a3a538e33cc04
6
+ metadata.gz: 5a81882cdb7b1169c78d456e4a9c531da3888632ad6d9ecd9cb36d3b546a1c95db5e98a88369d3996847a5a61b5f6b80663fb2db4b4950d612c417e84989d80b
7
+ data.tar.gz: c894e87a4e5819716c5959cfeba5b8de19b2b82c91ef46c4ab506225e1fabfedd51058a46b1ca545eaabd7ae48bf8bbad3499a276f453ca930759d0fe7cd9197
data/README.md CHANGED
@@ -66,17 +66,17 @@ Enough words, here is some code:
66
66
 
67
67
  ```ruby
68
68
  Spaceship.login
69
-
69
+
70
70
  # Create a new app
71
71
  app = Spaceship.app.create!(bundle_id: "com.krausefx.app", name: "Spaceship App")
72
-
72
+
73
73
  # Use an existing certificate
74
74
  cert = Spaceship.certificate.production.all.first
75
-
75
+
76
76
  # Create a new provisioning profile
77
77
  profile = Spaceship.provisioning_profile.app_store.create!(bundle_id: app.bundle_id,
78
78
  certificate: cert)
79
-
79
+
80
80
  # Print the name and download the new profile
81
81
  puts "Created Profile " + profile.name
82
82
  profile.download
@@ -84,7 +84,7 @@ profile.download
84
84
 
85
85
  ## Speed
86
86
 
87
- How fast are tools using `spaceship` compared to web scraping?
87
+ How fast are tools using `spaceship` compared to web scraping?
88
88
 
89
89
  ![assets/SpaceshipRecording.gif](assets/SpaceshipRecording.gif)
90
90
 
@@ -116,9 +116,9 @@ You can find the log file here `/tmp/spaceship[time].log`.
116
116
 
117
117
  ## HTTP Client
118
118
 
119
- Up until now all [fastlane tools](https://fastlane.tools) used web scraping to interact with Apple's web services. `spaceship` uses a simple HTTP client only, resulting in much less overhead and extremely improved speed.
119
+ Up until now all [fastlane tools](https://fastlane.tools) used web scraping to interact with Apple's web services. `spaceship` uses a simple HTTP client only, resulting in much less overhead and extremely improved speed.
120
120
 
121
- Advantages of `spaceship` (HTTP client) over web scraping:
121
+ Advantages of `spaceship` (HTTP client) over web scraping:
122
122
 
123
123
  - Blazing fast :rocket: 90% faster than previous methods
124
124
  - No more overhead by loading images, HTML, JS and CSS files on each page load
@@ -131,18 +131,19 @@ Advantages of `spaceship` (HTTP client) over web scraping:
131
131
  I won't go into too much technical details about the various API endpoints, but just to give you an idea:
132
132
 
133
133
  - `https://idmsa.apple.com`: Used to authenticate to get a valid session
134
- - `https://developerservices2.apple.com`:
134
+ - `https://developerservices2.apple.com`:
135
135
  - Get a detailed list of all available provisioning profiles
136
136
  - This API returns the devices, certificates and app for each of the profiles
137
137
  - Register new devices
138
- - `https://developer.apple.com`:
139
- - List all devices, certificates and apps
138
+ - `https://developer.apple.com`:
139
+ - List all devices, certificates, apps and app groups
140
140
  - Create new certificates, provisioning profiles and apps
141
+ - Disable/enable services on apps and assign them to app groups
141
142
  - Delete certificates and apps
142
143
  - Repair provisioning profiles
143
144
  - Download provisioning profiles
144
145
  - Team selection
145
- - `https://itunesconnect.apple.com`:
146
+ - `https://itunesconnect.apple.com`:
146
147
  - Managing apps
147
148
  - Managing beta testers
148
149
  - Submitting updates to review
@@ -152,11 +153,11 @@ I won't go into too much technical details about the various API endpoints, but
152
153
 
153
154
  ## Magic involved
154
155
 
155
- `spaceship` does a lot of magic to get everything working so neatly:
156
+ `spaceship` does a lot of magic to get everything working so neatly:
156
157
 
157
158
  - **Sensible Defaults**: You only have to provide the mandatory information (e.g. new provisioning profiles contain all devices by default)
158
159
  - **Local Validation**: When pushing changes back to the Apple Dev Portal `spaceship` will make sure only valid data is sent to Apple (e.g. automatic repairing of provisioning profiles)
159
- - **Various request/response types**: When working with the different API endpoints, `spaceship` has to deal with `JSON`, `XML`, `txt`, `plist` and sometimes even `HTML` responses and requests.
160
+ - **Various request/response types**: When working with the different API endpoints, `spaceship` has to deal with `JSON`, `XML`, `txt`, `plist` and sometimes even `HTML` responses and requests.
160
161
  - **Automatic Pagination**: Even if you have thousands of apps, profiles or certificates, `spaceship` **can** handle your scale. It was heavily tested by first using `spaceship` to create hundreds of profiles and then accessing them using `spaceship`.
161
162
  - **Session, Cookie and CSRF token**: All the security aspects are handled by `spaceship`.
162
163
  - **Profile Magic**: Create and upload code signing requests, all managed by `spaceship`
@@ -164,9 +165,9 @@ I won't go into too much technical details about the various API endpoints, but
164
165
 
165
166
  # Credits
166
167
 
167
- The initial release was sponsored by [ZeroPush](https://zeropush.com).
168
+ The initial release was sponsored by [ZeroPush](https://zeropush.com).
168
169
 
169
- `spaceship` was developed by
170
+ `spaceship` was developed by
170
171
  - [@KrauseFx](https://twitter.com/KrauseFx).
171
172
  - [@snatchev](https://twitter.com/snatchev/)
172
173
  - [@mathcarignani](https://twitter.com/mathcarignani/)
data/lib/spaceship.rb CHANGED
@@ -19,10 +19,11 @@ module Spaceship
19
19
  ProvisioningProfile = Spaceship::Portal::ProvisioningProfile
20
20
  Device = Spaceship::Portal::Device
21
21
  App = Spaceship::Portal::App
22
+ AppGroup = Spaceship::Portal::AppGroup
23
+ AppService = Spaceship::Portal::AppService
22
24
 
23
25
  # iTunes Connect
24
26
  AppVersion = Spaceship::Tunes::AppVersion
25
27
  AppSubmission = Spaceship::Tunes::AppSubmission
26
28
  Application = Spaceship::Tunes::Application
27
29
  end
28
-
@@ -175,7 +175,6 @@ module Spaceship
175
175
  # This method can be used by subclasses to do additional initialisation
176
176
  # using the `raw_data`
177
177
  def setup
178
-
179
178
  end
180
179
 
181
180
  ##
@@ -5,7 +5,6 @@ require 'spaceship/ui'
5
5
  require 'spaceship/helper/plist_middleware'
6
6
  require 'spaceship/helper/net_http_generic_request'
7
7
 
8
-
9
8
  if ENV["DEBUG"]
10
9
  require 'openssl'
11
10
  # this has to be on top of this file, since the value can't be changed later
@@ -29,7 +28,6 @@ module Spaceship
29
28
  # Raised when no user credentials were passed at all
30
29
  class NoUserCredentialsError < StandardError; end
31
30
 
32
-
33
31
  class UnexpectedResponse < StandardError; end
34
32
 
35
33
  # Authenticates with Apple's web services. This method has to be called once
@@ -157,94 +155,98 @@ module Spaceship
157
155
  !!@cookie
158
156
  end
159
157
 
158
+ def with_retry(tries = 5, &block)
159
+ return block.call
160
+ rescue Faraday::Error::TimeoutError => ex # New Faraday version: Faraday::TimeoutError => ex
161
+ unless (tries -= 1).zero?
162
+ sleep 3
163
+ retry
164
+ end
165
+
166
+ raise ex # re-raise the exception
167
+ end
168
+
160
169
  private
161
- # Is called from `parse_response` to store the latest csrf_token (if available)
162
- def store_csrf_tokens(response)
163
- if response and response.headers
164
- tokens = response.headers.select { |k, v| %w[csrf csrf_ts].include?(k) }
165
- if tokens and not tokens.empty?
166
- @csrf_tokens = tokens
167
- end
170
+
171
+ # Is called from `parse_response` to store the latest csrf_token (if available)
172
+ def store_csrf_tokens(response)
173
+ if response and response.headers
174
+ tokens = response.headers.select { |k, v| %w[csrf csrf_ts].include?(k) }
175
+ if tokens and not tokens.empty?
176
+ @csrf_tokens = tokens
168
177
  end
169
178
  end
179
+ end
170
180
 
171
181
  # memoize the last csrf tokens from responses
172
- def csrf_tokens
173
- @csrf_tokens || {}
174
- end
182
+ def csrf_tokens
183
+ @csrf_tokens || {}
184
+ end
175
185
 
176
- def request(method, url_or_path = nil, params = nil, headers = {}, &block)
177
- if session?
178
- headers.merge!({'Cookie' => cookie})
179
- headers.merge!(csrf_tokens)
180
- end
181
- headers.merge!({'User-Agent' => 'spaceship'})
186
+ def request(method, url_or_path = nil, params = nil, headers = {}, &block)
187
+ if session?
188
+ headers.merge!({'Cookie' => cookie})
189
+ headers.merge!(csrf_tokens)
190
+ end
191
+ headers.merge!({'User-Agent' => 'spaceship'})
182
192
 
183
- # Before encoding the parameters, log them
184
- log_request(method, url_or_path, params)
193
+ # Before encoding the parameters, log them
194
+ log_request(method, url_or_path, params)
185
195
 
186
- # form-encode the params only if there are params, and the block is not supplied.
187
- # this is so that certain requests can be made using the block for more control
188
- if method == :post && params && !block_given?
189
- params, headers = encode_params(params, headers)
190
- end
196
+ # form-encode the params only if there are params, and the block is not supplied.
197
+ # this is so that certain requests can be made using the block for more control
198
+ if method == :post && params && !block_given?
199
+ params, headers = encode_params(params, headers)
200
+ end
191
201
 
192
- response = send_request(method, url_or_path, params, headers, &block)
202
+ response = send_request(method, url_or_path, params, headers, &block)
193
203
 
194
- log_response(method, url_or_path, response)
204
+ log_response(method, url_or_path, response)
195
205
 
196
- return response
197
- end
206
+ return response
207
+ end
198
208
 
199
- def log_request(method, url, params)
200
- params_to_log = Hash(params).dup # to also work with nil
201
- params_to_log.delete(:accountPassword) # Dev Portal
202
- params_to_log.delete(:theAccountPW) # iTC
203
- params_to_log = params_to_log.collect do |key, value|
204
- "{#{key}: #{value}}"
205
- end
206
- logger.info("#{method.upcase}: #{url} #{params_to_log.join(', ')}")
209
+ def log_request(method, url, params)
210
+ params_to_log = Hash(params).dup # to also work with nil
211
+ params_to_log.delete(:accountPassword) # Dev Portal
212
+ params_to_log.delete(:theAccountPW) # iTC
213
+ params_to_log = params_to_log.collect do |key, value|
214
+ "{#{key}: #{value}}"
207
215
  end
216
+ logger.info("#{method.upcase}: #{url} #{params_to_log.join(', ')}")
217
+ end
208
218
 
209
- def log_response(method, url, response)
210
- logger.debug("#{method.upcase}: #{url}: #{response.body}")
211
- end
219
+ def log_response(method, url, response)
220
+ logger.debug("#{method.upcase}: #{url}: #{response.body}")
221
+ end
212
222
 
213
223
  # Actually sends the request to the remote server
214
224
  # Automatically retries the request up to 3 times if something goes wrong
215
- def send_request(method, url_or_path, params, headers, &block)
216
- tries ||= 5
217
-
218
- return @client.send(method, url_or_path, params, headers, &block)
219
-
220
- rescue Faraday::Error::TimeoutError => ex # New Faraday version: Faraday::TimeoutError => ex
221
- unless (tries -= 1).zero?
222
- sleep 3
223
- retry
224
- end
225
-
226
- raise ex # re-raise the exception
225
+ def send_request(method, url_or_path, params, headers, &block)
226
+ with_retry do
227
+ @client.send(method, url_or_path, params, headers, &block)
227
228
  end
229
+ end
228
230
 
229
- def parse_response(response, expected_key = nil)
230
- if expected_key
231
- content = response.body[expected_key]
232
- else
233
- content = response.body
234
- end
235
-
236
- if content == nil
237
- raise UnexpectedResponse.new(response.body)
238
- else
239
- store_csrf_tokens(response)
240
- content
241
- end
231
+ def parse_response(response, expected_key = nil)
232
+ if expected_key
233
+ content = response.body[expected_key]
234
+ else
235
+ content = response.body
242
236
  end
243
237
 
244
- def encode_params(params, headers)
245
- params = Faraday::Utils::ParamsHash[params].to_query
246
- headers = {'Content-Type' => 'application/x-www-form-urlencoded'}.merge(headers)
247
- return params, headers
238
+ if content == nil
239
+ raise UnexpectedResponse.new(response.body)
240
+ else
241
+ store_csrf_tokens(response)
242
+ content
248
243
  end
244
+ end
245
+
246
+ def encode_params(params, headers)
247
+ params = Faraday::Utils::ParamsHash[params].to_query
248
+ headers = {'Content-Type' => 'application/x-www-form-urlencoded'}.merge(headers)
249
+ return params, headers
250
+ end
249
251
  end
250
252
  end
@@ -9,4 +9,4 @@ class Net::HTTPGenericRequest
9
9
  def supply_default_content_type
10
10
  return if content_type
11
11
  end
12
- end
12
+ end
@@ -6,17 +6,17 @@ module Spaceship
6
6
  # spaceship. You can call `.new` without any parameters, but you'll have to call
7
7
  # `.login` at a later point. If you prefer, you can pass the login credentials
8
8
  # here already.
9
- #
9
+ #
10
10
  # Authenticates with Apple's web services. This method has to be called once
11
11
  # to generate a valid session. The session will automatically be used from then
12
12
  # on.
13
- #
13
+ #
14
14
  # This method will automatically use the username from the Appfile (if available)
15
15
  # and fetch the password from the Keychain (if available)
16
- #
16
+ #
17
17
  # @param user (String) (optional): The username (usually the email address)
18
18
  # @param password (String) (optional): The password
19
- #
19
+ #
20
20
  # @raise InvalidUserCredentialsError: raised if authentication failed
21
21
  def initialize(user = nil, password = nil)
22
22
  @client = PortalClient.new
@@ -33,30 +33,30 @@ module Spaceship
33
33
  # Authenticates with Apple's web services. This method has to be called once
34
34
  # to generate a valid session. The session will automatically be used from then
35
35
  # on.
36
- #
36
+ #
37
37
  # This method will automatically use the username from the Appfile (if available)
38
38
  # and fetch the password from the Keychain (if available)
39
- #
39
+ #
40
40
  # @param user (String) (optional): The username (usually the email address)
41
41
  # @param password (String) (optional): The password
42
- #
42
+ #
43
43
  # @raise InvalidUserCredentialsError: raised if authentication failed
44
- #
44
+ #
45
45
  # @return (Spaceship::Client) The client the login method was called for
46
- def login(user, password)
46
+ def login(user, password)
47
47
  @client.login(user, password)
48
48
  end
49
49
 
50
50
  # Open up the team selection for the user (if necessary).
51
- #
51
+ #
52
52
  # If the user is in multiple teams, a team selection is shown.
53
53
  # The user can then select a team by entering the number
54
- #
54
+ #
55
55
  # Additionally, the team ID is shown next to each team name
56
56
  # so that the user can use the environment variable `FASTLANE_TEAM_ID`
57
57
  # for future user.
58
- #
59
- # @return (String) The ID of the select team. You also get the value if
58
+ #
59
+ # @return (String) The ID of the select team. You also get the value if
60
60
  # the user is only in one team.
61
61
  def select_team
62
62
  @client.select_team
@@ -71,6 +71,11 @@ module Spaceship
71
71
  Spaceship::App.set_client(@client)
72
72
  end
73
73
 
74
+ # @return (Class) Access the app groups for this spaceship
75
+ def app_group
76
+ Spaceship::AppGroup.set_client(@client)
77
+ end
78
+
74
79
  # @return (Class) Access the devices for this spaceship
75
80
  def device
76
81
  Spaceship::Device.set_client(@client)
@@ -86,4 +91,4 @@ module Spaceship
86
91
  Spaceship::ProvisioningProfile.set_client(@client)
87
92
  end
88
93
  end
89
- end
94
+ end
@@ -4,7 +4,7 @@ module Spaceship
4
4
  class App < PortalBase
5
5
 
6
6
  # @return (String) The identifier of this app, provided by the Dev Portal
7
- # @example
7
+ # @example
8
8
  # "RGAWZGXSAA"
9
9
  attr_accessor :app_id
10
10
 
@@ -14,17 +14,17 @@ module Spaceship
14
14
  attr_accessor :name
15
15
 
16
16
  # @return (String) the supported platform of this app
17
- # @example
17
+ # @example
18
18
  # "ios"
19
19
  attr_accessor :platform
20
20
 
21
21
  # Prefix provided by the Dev Portal
22
- # @example
22
+ # @example
23
23
  # "5A997XSHK2"
24
24
  attr_accessor :prefix
25
25
 
26
26
  # @return (String) The bundle_id (app identifier) of your app
27
- # @example
27
+ # @example
28
28
  # "com.krausefx.app"
29
29
  attr_accessor :bundle_id
30
30
 
@@ -33,7 +33,7 @@ module Spaceship
33
33
 
34
34
  # @return (Hash) Feature details
35
35
  attr_accessor :features
36
-
36
+
37
37
  # @return (Array) List of enabled features
38
38
  attr_accessor :enabled_features
39
39
 
@@ -45,10 +45,10 @@ module Spaceship
45
45
 
46
46
  # @return (Fixnum) Number of associated app groups
47
47
  attr_accessor :app_groups_count
48
-
48
+
49
49
  # @return (Fixnum) Number of associated cloud containers
50
50
  attr_accessor :cloud_containers_count
51
-
51
+
52
52
  # @return (Fixnum) Number of associated identifiers
53
53
  attr_accessor :identifiers_count
54
54
 
@@ -81,7 +81,7 @@ module Spaceship
81
81
  end
82
82
 
83
83
  # Creates a new App ID on the Apple Dev Portal
84
- #
84
+ #
85
85
  # if bundle_id ends with '*' then it is a wildcard id otherwise, it is an explicit id
86
86
  # @param bundle_id [String] the bundle id (app_identifier) of the app associated with this provisioning profile
87
87
  # @param name [String] the name of the App
@@ -113,13 +113,27 @@ module Spaceship
113
113
  client.delete_app!(app_id)
114
114
  self
115
115
  end
116
-
116
+
117
117
  # Fetch a specific App ID details based on the bundle_id
118
118
  # @return (App) The app you're looking for. This is nil if the app can't be found.
119
119
  def details
120
120
  app = client.details_for_app(self)
121
121
  self.class.factory(app)
122
122
  end
123
+
124
+ # Associate specific groups with this app
125
+ # @return (App) The updated detailed app. This is nil if the app couldn't be found
126
+ def associate_groups(groups)
127
+ app = client.associate_groups_with_app(self, groups)
128
+ self.class.factory(app)
129
+ end
130
+
131
+ # Update a service for the app with given AppService object
132
+ # @return (App) The updated detailed app. This is nil if the app couldn't be found
133
+ def update_service(service)
134
+ app = client.update_service_for_app(self, service)
135
+ self.class.factory(app)
136
+ end
123
137
  end
124
138
  end
125
139
  end