spaceship 0.3.2 → 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.
@@ -0,0 +1,222 @@
1
+ module Spaceship
2
+ module Portal
3
+ # Represents a single application service (its state to be more precise) on the Apple Dev Portal
4
+ class AppService
5
+ # @return (String) The identifier used by the Dev Portal to represent this service
6
+ # @example
7
+ # "homeKit"
8
+ attr_accessor :service_id
9
+
10
+ # @return (Object) The current value for this service
11
+ # @example
12
+ # false
13
+ attr_accessor :value
14
+
15
+ # @return (String) The service URI for this service
16
+ # @example
17
+ # "account/ios/identifiers/updateService.action"
18
+ attr_accessor :service_uri
19
+
20
+ def initialize(service_id, value)
21
+ @service_id = service_id
22
+ @value = value
23
+
24
+ if @service_id == "push"
25
+ # Push notifications have a special URI
26
+ @service_uri = "account/ios/identifiers/updatePushService.action"
27
+ else
28
+ # Default service URI
29
+ @service_uri = "account/ios/identifiers/updateService.action"
30
+ end
31
+ end
32
+
33
+ class << self
34
+ def app_group
35
+ self::AppGroup
36
+ end
37
+
38
+ def associated_domains
39
+ self::AssociatedDomains
40
+ end
41
+
42
+ def data_protection
43
+ self::DataProtection
44
+ end
45
+
46
+ def health_kit
47
+ self::HealthKit
48
+ end
49
+
50
+ def home_kit
51
+ self::HomeKit
52
+ end
53
+
54
+ def wireless_accessory
55
+ self::WirelessAccessory
56
+ end
57
+
58
+ def icloud
59
+ self::Cloud
60
+ end
61
+
62
+ def cloud_kit
63
+ self::CloudKit
64
+ end
65
+
66
+ def inter_app_audio
67
+ self::InterAppAudio
68
+ end
69
+
70
+ def passbook
71
+ self::Passbook
72
+ end
73
+
74
+ def push_notification
75
+ self::PushNotification
76
+ end
77
+
78
+ def vpn_configuration
79
+ self::VPNConfiguration
80
+ end
81
+ end
82
+
83
+ def ==(other)
84
+ self.class == other.class &&
85
+ self.service_id == other.service_id &&
86
+ self.value == other.value &&
87
+ self.service_uri == other.service_uri
88
+ end
89
+
90
+ #
91
+ # Modules for "constants"
92
+ #
93
+ module AppGroup
94
+ def self.off
95
+ AppService.new("APG3427HIY", false)
96
+ end
97
+
98
+ def self.on
99
+ AppService.new("APG3427HIY", true)
100
+ end
101
+ end
102
+
103
+ module AssociatedDomains
104
+ def self.off
105
+ AppService.new("SKC3T5S89Y", false)
106
+ end
107
+
108
+ def self.on
109
+ AppService.new("SKC3T5S89Y", true)
110
+ end
111
+ end
112
+
113
+ module DataProtection
114
+ def self.off
115
+ AppService.new("dataProtection", "")
116
+ end
117
+
118
+ def self.complete
119
+ AppService.new("dataProtection", "complete")
120
+ end
121
+
122
+ def self.unless_open
123
+ AppService.new("dataProtection", "unlessopen")
124
+ end
125
+
126
+ def self.until_first_auth
127
+ AppService.new("dataProtection", "untilfirstauth")
128
+ end
129
+ end
130
+
131
+ module HealthKit
132
+ def self.off
133
+ AppService.new("HK421J6T7P", false)
134
+ end
135
+
136
+ def self.on
137
+ AppService.new("HK421J6T7P", true)
138
+ end
139
+ end
140
+
141
+ module HomeKit
142
+ def self.off
143
+ AppService.new("homeKit", false)
144
+ end
145
+
146
+ def self.on
147
+ AppService.new("homeKit", true)
148
+ end
149
+ end
150
+
151
+ module WirelessAccessory
152
+ def self.off
153
+ AppService.new("WC421J6T7P", false)
154
+ end
155
+
156
+ def self.on
157
+ AppService.new("WC421J6T7P", true)
158
+ end
159
+ end
160
+
161
+ module Cloud
162
+ def self.off
163
+ AppService.new("iCloud", false)
164
+ end
165
+
166
+ def self.on
167
+ AppService.new("iCloud", true)
168
+ end
169
+ end
170
+
171
+ module CloudKit
172
+ def self.xcode5_compatible
173
+ AppService.new("cloudKitVersion", 1)
174
+ end
175
+
176
+ def self.cloud_kit
177
+ AppService.new("cloudKitVersion", 2)
178
+ end
179
+ end
180
+
181
+ module InterAppAudio
182
+ def self.off
183
+ AppService.new("IAD53UNK2F", false)
184
+ end
185
+
186
+ def self.on
187
+ AppService.new("IAD53UNK2F", true)
188
+ end
189
+ end
190
+
191
+ module Passbook
192
+ def self.off
193
+ AppService.new("pass", false)
194
+ end
195
+
196
+ def self.on
197
+ AppService.new("pass", true)
198
+ end
199
+ end
200
+
201
+ module PushNotification
202
+ def self.off
203
+ AppService.new("push", false)
204
+ end
205
+
206
+ def self.on
207
+ AppService.new("push", true)
208
+ end
209
+ end
210
+
211
+ module VPNConfiguration
212
+ def self.off
213
+ AppService.new("V66P55NK2I", false)
214
+ end
215
+
216
+ def self.on
217
+ AppService.new("V66P55NK2I", true)
218
+ end
219
+ end
220
+ end
221
+ end
222
+ end
@@ -237,7 +237,7 @@ module Spaceship
237
237
  end
238
238
 
239
239
  # ensure csr is a OpenSSL::X509::Request
240
- csr = OpenSSL::X509::Request.new(csr) if csr.is_a?(String)
240
+ csr = OpenSSL::X509::Request.new(csr) if csr.kind_of?(String)
241
241
 
242
242
  # if this succeeds, we need to save the .cer and the private key in keychain access or wherever they go in linux
243
243
  response = client.create_certificate!(type, csr.to_pem, app_id)
@@ -266,7 +266,7 @@ module Spaceship
266
266
 
267
267
  # @return (Bool): Is this certificate a push profile for apps?
268
268
  def is_push?
269
- self.kind_of?PushCertificate
269
+ self.kind_of? PushCertificate
270
270
  end
271
271
  end
272
272
  end
@@ -3,27 +3,27 @@ module Spaceship
3
3
  # Represents a device from the Apple Developer Portal
4
4
  class Device < PortalBase
5
5
  # @return (String) The ID given from the developer portal. You'll probably not need it.
6
- # @example
6
+ # @example
7
7
  # "XJXGVS46MW"
8
8
  attr_accessor :id
9
9
 
10
10
  # @return (String) The name of the device
11
- # @example
11
+ # @example
12
12
  # "Felix Krause's iPhone 6"
13
13
  attr_accessor :name
14
14
 
15
15
  # @return (String) The UDID of the device
16
- # @example
16
+ # @example
17
17
  # "4c24a7ee5caaa4847f49aaab2d87483053f53b65"
18
18
  attr_accessor :udid
19
19
 
20
20
  # @return (String) The platform of the device. This is probably always "ios"
21
- # @example
21
+ # @example
22
22
  # "ios"
23
23
  attr_accessor :platform
24
24
 
25
25
  # @return (String) Status of the device. Probably always "c"
26
- # @example
26
+ # @example
27
27
  # "c"
28
28
  attr_accessor :status
29
29
 
@@ -47,7 +47,7 @@ module Spaceship
47
47
  client.devices.map { |device| self.factory(device) }
48
48
  end
49
49
 
50
- # @return (Device) Find a device based on the ID of the device. *Attention*:
50
+ # @return (Device) Find a device based on the ID of the device. *Attention*:
51
51
  # This is *not* the UDID. nil if no device was found.
52
52
  def find(device_id)
53
53
  all.find do |device|
@@ -72,7 +72,7 @@ module Spaceship
72
72
  # Register a new device to this account
73
73
  # @param name (String) (required): The name of the new device
74
74
  # @param udid (String) (required): The UDID of the new device
75
- # @example
75
+ # @example
76
76
  # Spaceship.device.create!(name: "Felix Krause's iPhone 6", udid: "4c24a7ee5caaa4847f49aaab2d87483053f53b65")
77
77
  # @return (Device): The newly created device
78
78
  def create!(name: nil, udid: nil)
@@ -1,6 +1,8 @@
1
1
  require 'spaceship/portal/portal_base'
2
2
  require 'spaceship/portal/app'
3
+ require 'spaceship/portal/app_group'
4
+ require 'spaceship/portal/app_service'
3
5
  require 'spaceship/portal/certificate'
4
6
  require 'spaceship/portal/device'
5
7
  require 'spaceship/portal/provisioning_profile'
6
- require 'spaceship/portal/portal_client'
8
+ require 'spaceship/portal/portal_client'
@@ -10,4 +10,4 @@ module Spaceship
10
10
  end
11
11
  end
12
12
  end
13
- end
13
+ end
@@ -13,7 +13,7 @@ module Spaceship
13
13
  def api_key
14
14
  cache_path = "/tmp/spaceship_api_key.txt"
15
15
  begin
16
- cached = File.read(cache_path)
16
+ cached = File.read(cache_path)
17
17
  rescue Errno::ENOENT
18
18
  end
19
19
  return cached if cached
@@ -83,12 +83,10 @@ module Spaceship
83
83
 
84
84
  # Is the current session from an Enterprise In House account?
85
85
  def in_house?
86
- # TODO: move to portal
87
86
  return @in_house unless @in_house.nil?
88
87
  @in_house = (team_information['type'] == 'In-House')
89
88
  end
90
89
 
91
-
92
90
  #####################################################
93
91
  # @!group Apps
94
92
  #####################################################
@@ -113,6 +111,28 @@ module Spaceship
113
111
  parse_response(r, 'appId')
114
112
  end
115
113
 
114
+ def update_service_for_app(app, service)
115
+ request(:post, service.service_uri, {
116
+ teamId: team_id,
117
+ displayId: app.app_id,
118
+ featureType: service.service_id,
119
+ featureValue: service.value
120
+ })
121
+
122
+ details_for_app(app)
123
+ end
124
+
125
+ def associate_groups_with_app(app, groups)
126
+ r = request(:post, 'account/ios/identifiers/assignApplicationGroupToAppId.action', {
127
+ teamId: team_id,
128
+ appIdId: app.app_id,
129
+ displayId: app.app_id,
130
+ applicationGroups: groups.map { |g| g.app_group_id }
131
+ })
132
+
133
+ details_for_app(app)
134
+ end
135
+
116
136
  def create_app!(type, name, bundle_id)
117
137
  ident_params = case type.to_sym
118
138
  when :explicit
@@ -151,6 +171,39 @@ module Spaceship
151
171
  parse_response(r)
152
172
  end
153
173
 
174
+ #####################################################
175
+ # @!group App Groups
176
+ #####################################################
177
+
178
+ def app_groups
179
+ paging do |page_number|
180
+ r = request(:post, 'account/ios/identifiers/listApplicationGroups.action', {
181
+ teamId: team_id,
182
+ pageNumber: page_number,
183
+ pageSize: page_size,
184
+ sort: 'name=asc'
185
+ })
186
+ parse_response(r, 'applicationGroupList')
187
+ end
188
+ end
189
+
190
+ def create_app_group!(name, group_id)
191
+ r = request(:post, 'account/ios/identifiers/addApplicationGroup.action', {
192
+ name: name,
193
+ identifier: group_id,
194
+ teamId: team_id
195
+ })
196
+ parse_response(r, 'applicationGroup')
197
+ end
198
+
199
+ def delete_app_group!(app_group_id)
200
+ r = request(:post, 'account/ios/identifiers/deleteApplicationGroup.action', {
201
+ teamId: team_id,
202
+ applicationGroup: app_group_id
203
+ })
204
+ parse_response(r)
205
+ end
206
+
154
207
  #####################################################
155
208
  # @!group Devices
156
209
  #####################################################
@@ -168,7 +221,7 @@ module Spaceship
168
221
  end
169
222
 
170
223
  def create_device!(device_name, device_id)
171
- r = request(:post) do |r|
224
+ req = request(:post) do |r|
172
225
  r.url "https://developerservices2.apple.com/services/#{PROTOCOL_VERSION}/ios/addDevice.action"
173
226
  r.params = {
174
227
  teamId: team_id,
@@ -177,7 +230,7 @@ module Spaceship
177
230
  }
178
231
  end
179
232
 
180
- parse_response(r, 'device')
233
+ parse_response(req, 'device')
181
234
  end
182
235
 
183
236
  #####################################################
@@ -202,7 +255,7 @@ module Spaceship
202
255
  teamId: team_id,
203
256
  type: type,
204
257
  csrContent: csr,
205
- appIdId: app_id #optional
258
+ appIdId: app_id # optional
206
259
  })
207
260
  parse_response(r, 'certRequest')
208
261
  end
@@ -232,16 +285,16 @@ module Spaceship
232
285
  #####################################################
233
286
 
234
287
  def provisioning_profiles
235
- r = request(:post) do |r|
288
+ req = request(:post) do |r|
236
289
  r.url "https://developerservices2.apple.com/services/#{PROTOCOL_VERSION}/ios/listProvisioningProfiles.action"
237
290
  r.params = {
238
291
  teamId: team_id,
239
292
  includeInactiveProfiles: true,
240
- onlyCountLists: true,
293
+ onlyCountLists: true
241
294
  }
242
295
  end
243
296
 
244
- parse_response(r, 'provisioningProfiles')
297
+ parse_response(req, 'provisioningProfiles')
245
298
  end
246
299
 
247
300
  def create_provisioning_profile!(name, distribution_method, app_id, certificate_ids, device_ids)
@@ -279,11 +332,11 @@ module Spaceship
279
332
  provisioningProfileName: name,
280
333
  appIdId: app_id,
281
334
  distributionType: distribution_method,
282
- certificateIds: certificate_ids.first, # we are most of the times only allowed to pass one
335
+ certificateIds: certificate_ids.join(','),
283
336
  deviceIds: device_ids
284
337
  })
285
338
 
286
339
  parse_response(r, 'provisioningProfile')
287
340
  end
288
341
  end
289
- end
342
+ end
@@ -16,12 +16,12 @@ module Spaceship
16
16
  attr_accessor :uuid
17
17
 
18
18
  # @return (DateTime) The date and time of when the profile
19
- # expires.
19
+ # expires.
20
20
  # @example
21
21
  # #<DateTime: 2015-11-25T22:45:50+00:00 ((2457352j,81950s,0n),+0s,2299161j)>
22
22
  attr_accessor :expires
23
23
 
24
- # @return (String) The profile distribution type. You probably want to
24
+ # @return (String) The profile distribution type. You probably want to
25
25
  # use the class type to detect the profile type instead of this string.
26
26
  # @example AppStore Profile
27
27
  # "store"
@@ -45,7 +45,7 @@ module Spaceship
45
45
  # "Invalid"
46
46
  attr_accessor :status
47
47
 
48
- # @return (String) The type of the profile (development or distribution).
48
+ # @return (String) The type of the profile (development or distribution).
49
49
  # You'll probably not need this value
50
50
  # @example Distribution
51
51
  # "iOS Distribution"
@@ -69,7 +69,7 @@ module Spaceship
69
69
  # A reference to the app this profile is for.
70
70
  # You can then easily access the value directly
71
71
  # @return (App) The app this profile is for
72
- #
72
+ #
73
73
  # @example Example Value
74
74
  # <Spaceship::App
75
75
  # @app_id="2UMR2S6PAA"
@@ -80,7 +80,7 @@ module Spaceship
80
80
  # @is_wildcard=false
81
81
  # @dev_push_enabled=false
82
82
  # @prod_push_enabled=false>
83
- #
83
+ #
84
84
  # @example Usage
85
85
  # profile.app.name
86
86
  attr_accessor :app
@@ -99,14 +99,14 @@ module Spaceship
99
99
  # @owner_id=nil
100
100
  # @type_display_id="R58UK2EWAA">]
101
101
  # ]
102
- #
102
+ #
103
103
  # @example Usage
104
104
  # profile.certificates.first.id
105
105
  attr_accessor :certificates
106
106
 
107
107
  # @return (Array) A list of devices this profile is enabled for.
108
108
  # This will always be [] for AppStore profiles
109
- #
109
+ #
110
110
  # @example Example Value
111
111
  # <Spaceship::Device
112
112
  # @id="WXQ7V239BE"
@@ -114,7 +114,7 @@ module Spaceship
114
114
  # @udid="ba0ac7d70f7a14c6fa02ef0e02f4fe9c5178e2f7"
115
115
  # @platform="ios"
116
116
  # @status="c">]
117
- #
117
+ #
118
118
  # @example Usage
119
119
  # profile.devices.first.name
120
120
  attr_accessor :devices
@@ -204,7 +204,7 @@ module Spaceship
204
204
 
205
205
  devices = [] if (self == AppStore or self == InHouse) # App Store Profiles MUST NOT have devices
206
206
 
207
- certificate_parameter = certificate.collect { |c| c.id } if certificate.kind_of?Array
207
+ certificate_parameter = certificate.collect { |c| c.id } if certificate.kind_of? Array
208
208
  certificate_parameter ||= [certificate.id]
209
209
 
210
210
  # Fix https://github.com/KrauseFx/fastlane/issues/349
@@ -217,29 +217,19 @@ module Spaceship
217
217
  end
218
218
  end
219
219
 
220
- def send_create_request(name, type, app_id, certificate_parameter, devices)
221
- tries ||= 5
222
- client.create_provisioning_profile!(name, type, app_id, certificate_parameter, devices)
223
- rescue => ex
224
- unless (tries -= 1).zero?
225
- sleep 3
226
- retry
227
- end
228
-
229
- raise ex # re-raise the exception
220
+ profile = client.with_retry do
221
+ client.create_provisioning_profile!(name,
222
+ self.type,
223
+ app.app_id,
224
+ certificate_parameter,
225
+ devices.map {|d| d.id} )
230
226
  end
231
227
 
232
- profile = send_create_request(name,
233
- self.type,
234
- app.app_id,
235
- certificate_parameter,
236
- devices.map {|d| d.id} )
237
-
238
228
  self.new(profile)
239
229
  end
240
230
 
241
231
  # @return (Array) Returns all profiles registered for this account
242
- # If you're calling this from a subclass (like AdHoc), this will
232
+ # If you're calling this from a subclass (like AdHoc), this will
243
233
  # only return the profiles that are of this type
244
234
  def all
245
235
  profiles = client.provisioning_profiles.map do |profile|
@@ -317,7 +307,7 @@ module Spaceship
317
307
 
318
308
  # Repair an existing provisioning profile
319
309
  # alias to update!
320
- # @return (ProvisioningProfile) A new provisioning profile, as
310
+ # @return (ProvisioningProfile) A new provisioning profile, as
321
311
  # the repair method will generate a profile with a new ID
322
312
  def repair!
323
313
  update!
@@ -326,56 +316,40 @@ module Spaceship
326
316
  # Updates the provisioning profile from the local data
327
317
  # e.g. after you added new devices to the profile
328
318
  # This will also update the code signing identity if necessary
329
- # @return (ProvisioningProfile) A new provisioning profile, as
319
+ # @return (ProvisioningProfile) A new provisioning profile, as
330
320
  # the repair method will generate a profile with a new ID
331
321
  def update!
332
322
  unless certificate_valid?
333
- if self.kind_of?Development
323
+ if self.kind_of? Development
334
324
  self.certificates = [Spaceship::Certificate::Development.all.first]
335
- elsif self.kind_of?InHouse
325
+ elsif self.kind_of? InHouse
336
326
  self.certificates = [Spaceship::Certificate::InHouse.all.first]
337
327
  else
338
- self.certificates = [Spaceship::Certificate::Production.all.first]
328
+ self.certificates = [Spaceship::Certificate::Production.all.first]
339
329
  end
340
330
  end
341
331
 
342
- def send_update_request(id, name, distribution_method, app_id, certificates, devices)
343
- tries ||= 5
332
+ client.with_retry do
344
333
  client.repair_provisioning_profile!(
345
334
  id,
346
335
  name,
347
336
  distribution_method,
348
- app_id,
349
- certificates,
350
- devices
337
+ app.app_id,
338
+ certificates.map { |c| c.id },
339
+ devices.map { |d| d.id }
351
340
  )
352
- rescue => ex
353
- unless (tries -= 1).zero?
354
- sleep 3
355
- retry
356
- end
357
- raise ex # re-raise the exception
358
341
  end
359
342
 
360
- send_update_request(
361
- self.id,
362
- self.name,
363
- self.distribution_method,
364
- self.app.app_id,
365
- self.certificates.map { |c| c.id },
366
- self.devices.map { |d| d.id }
367
- )
368
-
369
343
  # We need to fetch the provisioning profile again, as the ID changes
370
- profile = Spaceship::ProvisioningProfile.all.find do |profile|
371
- profile.name == self.name # we can use the name as it's valid
344
+ profile = Spaceship::ProvisioningProfile.all.find do |p|
345
+ p.name == self.name # we can use the name as it's valid
372
346
  end
373
347
 
374
348
  return profile
375
349
  end
376
350
 
377
351
  # Is the certificate of this profile available?
378
- # @return (Bool) is the certificate valid?
352
+ # @return (Bool) is the certificate valid?
379
353
  def certificate_valid?
380
354
  return false if (certificates || []).count == 0
381
355
  certificates.each do |c|