spaceship 0.3.4 → 0.4.0

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