spaceship 0.0.1 → 0.0.2

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,12 @@
1
+ require 'net/http'
2
+
3
+ ## monkey-patch Net::HTTP
4
+ #
5
+ # Certain apple endpoints return 415 responses if a Content-Type is supplied.
6
+ # Net::HTTP will default a content-type if none is provided by faraday
7
+ # This monkey-patch allows us to leave out the content-type if we do not specify one.
8
+ class Net::HTTPGenericRequest
9
+ def supply_default_content_type
10
+ return if content_type()
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ require 'faraday_middleware/response_middleware'
2
+
3
+ module FaradayMiddleware
4
+ class PlistMiddleware < ResponseMiddleware
5
+ dependency do
6
+ require 'plist' unless defined?(::Plist)
7
+ end
8
+
9
+ define_parser do |body|
10
+ Plist::parse_xml(body)
11
+ end
12
+ end
13
+ end
14
+
15
+ Faraday::Response.register_middleware(:plist => FaradayMiddleware::PlistMiddleware)
@@ -0,0 +1,89 @@
1
+ module Spaceship
2
+ class Launcher
3
+ attr_accessor :client
4
+
5
+ # Launch a new spaceship, which can be used to maintain multiple instances of
6
+ # spaceship. You can call `.new` without any parameters, but you'll have to call
7
+ # `.login` at a later point. If you prefer, you can pass the login credentials
8
+ # here already.
9
+ #
10
+ # Authenticates with Apple's web services. This method has to be called once
11
+ # to generate a valid session. The session will automatically be used from then
12
+ # on.
13
+ #
14
+ # This method will automatically use the username from the Appfile (if available)
15
+ # and fetch the password from the Keychain (if available)
16
+ #
17
+ # @param user (String) (optional): The username (usually the email address)
18
+ # @param password (String) (optional): The password
19
+ #
20
+ # @raise InvalidUserCredentialsError: raised if authentication failed
21
+ def initialize(user = nil, password = nil)
22
+ @client = Client.new
23
+
24
+ if user or password
25
+ @client.login(user, password)
26
+ end
27
+ end
28
+
29
+ #####################################################
30
+ # @!group Login Helper
31
+ #####################################################
32
+
33
+ # Authenticates with Apple's web services. This method has to be called once
34
+ # to generate a valid session. The session will automatically be used from then
35
+ # on.
36
+ #
37
+ # This method will automatically use the username from the Appfile (if available)
38
+ # and fetch the password from the Keychain (if available)
39
+ #
40
+ # @param user (String) (optional): The username (usually the email address)
41
+ # @param password (String) (optional): The password
42
+ #
43
+ # @raise InvalidUserCredentialsError: raised if authentication failed
44
+ #
45
+ # @return (Spaceship::Client) The client the login method was called for
46
+ def login(user, password)
47
+ @client.login(user, password)
48
+ end
49
+
50
+ # Open up the team selection for the user (if necessary).
51
+ #
52
+ # If the user is in multiple teams, a team selection is shown.
53
+ # The user can then select a team by entering the number
54
+ #
55
+ # Additionally, the team ID is shown next to each team name
56
+ # so that the user can use the environment variable `FASTLANE_TEAM_ID`
57
+ # for future user.
58
+ #
59
+ # @return (String) The ID of the select team. You also get the value if
60
+ # the user is only in one team.
61
+ def select_team
62
+ @client.select_team
63
+ end
64
+
65
+ #####################################################
66
+ # @!group Helper methods for managing multiple instances of spaceship
67
+ #####################################################
68
+
69
+ # @return (Class) Access the apps for this spaceship
70
+ def app
71
+ Spaceship::App.set_client(@client)
72
+ end
73
+
74
+ # @return (Class) Access the devices for this spaceship
75
+ def device
76
+ Spaceship::Device.set_client(@client)
77
+ end
78
+
79
+ # @return (Class) Access the certificates for this spaceship
80
+ def certificate
81
+ Spaceship::Certificate.set_client(@client)
82
+ end
83
+
84
+ # @return (Class) Access the provisioning profiles for this spaceship
85
+ def provisioning_profile
86
+ Spaceship::ProvisioningProfile.set_client(@client)
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,39 @@
1
+ module Spaceship
2
+ # This class contains the codes used for the different types of profiles
3
+ class Client
4
+ class ProfileTypes
5
+ class SigningCertificate
6
+ def self.development
7
+ "5QPB9NHCEI"
8
+ end
9
+ def self.distribution
10
+ "R58UK2EWSO"
11
+ end
12
+ end
13
+
14
+ class Push
15
+ def self.development
16
+ "BKLRAVXMGM"
17
+ end
18
+ def self.production
19
+ "3BQKVH9I2X"
20
+ end
21
+ end
22
+
23
+ def self.all_profile_types
24
+ [
25
+ "5QPB9NHCEI", # Development Code Signing Identity
26
+ "R58UK2EWSO", # Distribution Code Signing Identity
27
+ "9RQEK7MSXA", # iOS Distribution certificate signing request
28
+ "LA30L5BJEU", # MDM CSR certificate signing request
29
+ "BKLRAVXMGM", # Development Push Certificates
30
+ "3BQKVH9I2X", # Production Push Certificates
31
+ "Y3B2F3TYSI", # Pass Type ID pass certificate request
32
+ "3T2ZP62QW8", # Website Push Id
33
+ "E5D663CMZW", # Website Push Id
34
+ "4APLUP237T" # Apple Pay
35
+ ]
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,352 @@
1
+ module Spaceship
2
+ # Represents a provisioning profile of the Apple Dev Portal
3
+ class ProvisioningProfile < Base
4
+ # @return (String) The ID generated by the Dev Portal
5
+ # You'll probably not really need this value
6
+ # @example
7
+ # "2MAY7NPHAA"
8
+ attr_accessor :id
9
+
10
+ # @return (String) The UDID of this provisioning profile
11
+ # This value is used for example for code signing
12
+ # It is also contained in the actual profile
13
+ # @example
14
+ # "23d7df3b-9767-4e85-a1ea-1df4d8f32fec"
15
+ attr_accessor :uuid
16
+
17
+ # @return (DateTime) The date and time of when the profile
18
+ # expires.
19
+ # @example
20
+ # #<DateTime: 2015-11-25T22:45:50+00:00 ((2457352j,81950s,0n),+0s,2299161j)>
21
+ attr_accessor :expires
22
+
23
+ # @return (String) The profile distribution type. You probably want to
24
+ # use the class type to detect the profile type instead of this string.
25
+ # @example AppStore Profile
26
+ # "store"
27
+ # @example AdHoc Profile
28
+ # "adhoc"
29
+ # @example Development Profile
30
+ # "limited"
31
+ attr_accessor :distribution_method
32
+
33
+ # @return (String) The name of this profile
34
+ # @example
35
+ # "com.krausefx.app AppStore"
36
+ attr_accessor :name
37
+
38
+ # @return (String) The status of this profile
39
+ # @example Active (profile is fine)
40
+ # "Active"
41
+ # @example Expired (time ran out)
42
+ # "Expired"
43
+ # @example Invalid (e.g. code signing identity not available any more)
44
+ # "Invalid"
45
+ attr_accessor :status
46
+
47
+ # @return (String) The type of the profile (development or distribution).
48
+ # You'll probably not need this value
49
+ # @example Distribution
50
+ # "iOS Distribution"
51
+ # @example Development
52
+ # "iOS Development"
53
+ attr_accessor :type
54
+
55
+ # @return (String) This will always be "2"
56
+ # @example
57
+ # "2"
58
+ attr_accessor :version
59
+
60
+ # @return (String) The supported platform for this profile
61
+ # @example
62
+ # "ios"
63
+ attr_accessor :platform
64
+
65
+ # No information about this attribute
66
+ attr_accessor :managing_app
67
+
68
+ # A reference to the app this profile is for.
69
+ # You can then easily access the value directly
70
+ # @return (App) The app this profile is for
71
+ #
72
+ # @example Example Value
73
+ # <Spaceship::App
74
+ # @app_id="2UMR2S6PAA"
75
+ # @name="App Name"
76
+ # @platform="ios"
77
+ # @prefix="5A997XSAAA"
78
+ # @bundle_id="com.krausefx.app"
79
+ # @is_wildcard=false
80
+ # @dev_push_enabled=false
81
+ # @prod_push_enabled=false>
82
+ #
83
+ # @example Usage
84
+ # profile.app.name
85
+ attr_accessor :app
86
+
87
+ # @return (Array) A list of certificates used for this profile
88
+ # @example Example Value
89
+ # [
90
+ # <Spaceship::Certificate::Production
91
+ # @status=nil
92
+ # @id="XC5PH8D4AA"
93
+ # @name="iOS Distribution"
94
+ # @created=nil
95
+ # @expires=#<DateTime: 2015-11-25T22:45:50+00:00 ((2457352j,81950s,0n),+0s,2299161j)>
96
+ # @owner_type="team"
97
+ # @owner_name=nil
98
+ # @owner_id=nil
99
+ # @type_display_id="R58UK2EWAA">]
100
+ # ]
101
+ #
102
+ # @example Usage
103
+ # profile.certificates.first.id
104
+ attr_accessor :certificates
105
+
106
+ # @return (Array) A list of devices this profile is enabled for.
107
+ # This will always be [] for AppStore profiles
108
+ #
109
+ # @example Example Value
110
+ # <Spaceship::Device
111
+ # @id="WXQ7V239BE"
112
+ # @name="Grahams iPhone 4s"
113
+ # @udid="ba0ac7d70f7a14c6fa02ef0e02f4fe9c5178e2f7"
114
+ # @platform="ios"
115
+ # @status="c">]
116
+ #
117
+ # @example Usage
118
+ # profile.devices.first.name
119
+ attr_accessor :devices
120
+
121
+ attr_mapping({
122
+ 'provisioningProfileId' => :id,
123
+ 'UUID' => :uuid,
124
+ 'dateExpire' => :expires,
125
+ 'distributionMethod' => :distribution_method,
126
+ 'name' => :name,
127
+ 'status' => :status,
128
+ 'type' => :type,
129
+ 'version' => :version,
130
+ 'proProPlatform' => :platform,
131
+ 'managingApp' => :managing_app,
132
+ 'appId' => :app
133
+ })
134
+
135
+ class << self
136
+ # @return (String) The profile type used for web requests to the Dev Portal
137
+ # @example
138
+ # "limited"
139
+ # "store"
140
+ # "adhoc"
141
+ # "inhouse"
142
+ def type
143
+ raise "You cannot create a ProvisioningProfile without a type. Use a subclass."
144
+ end
145
+
146
+ # Create a new object based on a hash.
147
+ # This is used to create a new object based on the server response.
148
+ def factory(attrs)
149
+ # Ad Hoc Profiles look exactly like App Store profiles, but usually include devices
150
+ attrs['distributionMethod'] = 'adhoc' if attrs['distributionMethod'] == 'store' && attrs['devices'].size > 0
151
+ # available values of `distributionMethod` at this point: ['adhoc', 'store', 'limited']
152
+
153
+ klass = case attrs['distributionMethod']
154
+ when 'limited'
155
+ Development
156
+ when 'store'
157
+ AppStore
158
+ when 'adhoc'
159
+ AdHoc
160
+ when 'inhouse'
161
+ InHouse
162
+ else
163
+ raise "Can't find class '#{attrs['distributionMethod']}'"
164
+ end
165
+
166
+ attrs['appId'] = App.factory(attrs['appId'])
167
+ attrs['devices'].map! { |device| Device.factory(device) }
168
+ attrs['certificates'].map! { |cert| Certificate.factory(cert) }
169
+
170
+ klass.client = @client
171
+ klass.new(attrs)
172
+ end
173
+
174
+ # @return (String) The human readable name of this profile type.
175
+ # @example
176
+ # "AppStore"
177
+ # "AdHoc"
178
+ # "Development"
179
+ # "InHouse"
180
+ def pretty_type
181
+ name.split('::').last
182
+ end
183
+
184
+ # Create a new provisioning profile
185
+ # @param name (String): The name of the provisioning profile on the Dev Portal
186
+ # @param bundle_id (String): The app identifier, this paramter is required
187
+ # @param certificate (Certificate): The certificate that should be used with this
188
+ # provisioning profile
189
+ # @param devices (Array) (optional): An array of Device objects that should be used in this profile.
190
+ # It is recommend to not pass devices as spaceship will automatically add all devices for AdHoc
191
+ # and Development profiles and add none for AppStore and Enterprise Profiles
192
+ # @return (ProvisioningProfile): The profile that was just created
193
+ def create!(name: nil, bundle_id: nil, certificate: nil, devices: [])
194
+ raise "Missing required parameter 'bundle_id'" if bundle_id.to_s.empty?
195
+ raise "Missing required parameter 'certificate'. e.g. use `Spaceship::Certificate::Production.all.first`" if certificate.to_s.empty?
196
+
197
+ app = Spaceship::App.find(bundle_id)
198
+ raise "Could not find app with bundle id '#{bundle_id}'" unless app
199
+
200
+ # Fill in sensible default values
201
+ name ||= [bundle_id, self.pretty_type].join(' ')
202
+
203
+ devices = [] if self == AppStore # App Store Profiles MUST NOT have devices
204
+
205
+ if devices.nil? or devices.count == 0
206
+ if self == Development or self == AdHoc
207
+ # For Development and AdHoc we usually want all devices by default
208
+ devices = Spaceship::Device.all
209
+ end
210
+ end
211
+
212
+ profile = client.create_provisioning_profile!(name,
213
+ self.type,
214
+ app.app_id,
215
+ [certificate.id],
216
+ devices.map {|d| d.id} )
217
+ self.new(profile)
218
+ end
219
+
220
+ # @return (Array) Returns all profiles registered for this account
221
+ # If you're calling this from a subclass (like AdHoc), this will
222
+ # only return the profiles that are of this type
223
+ def all
224
+ profiles = client.provisioning_profiles.map do |profile|
225
+ self.factory(profile)
226
+ end
227
+
228
+ # filter out the profiles managed by xcode
229
+ profiles.delete_if do |profile|
230
+ profile.managed_by_xcode?
231
+ end
232
+
233
+ return profiles if self == ProvisioningProfile
234
+
235
+ # only return the profiles that match the class
236
+ profiles.select do |profile|
237
+ profile.class == self
238
+ end
239
+ end
240
+
241
+ # @return (ProvisioningProfile) Find a provisioning based on the
242
+ # bundle_id (app identifier). This will return nil if it can't be
243
+ # found.
244
+ def find_by_bundle_id(bundle_id)
245
+ all.find_all do |profile|
246
+ profile.app.bundle_id == bundle_id
247
+ end
248
+ end
249
+
250
+ end
251
+
252
+ # Represents a Development profile from the Dev Portal
253
+ class Development < ProvisioningProfile
254
+ def self.type
255
+ 'limited'
256
+ end
257
+ end
258
+
259
+ # Represents an AppStore profile from the Dev Portal
260
+ class AppStore < ProvisioningProfile
261
+ def self.type
262
+ 'store'
263
+ end
264
+ end
265
+
266
+ # Represents an AdHoc profile from the Dev Portal
267
+ class AdHoc < ProvisioningProfile
268
+ def self.type
269
+ 'adhoc'
270
+ end
271
+ end
272
+
273
+ # Represents an Enterprise InHouse profile from the Dev Portal
274
+ class InHouse < ProvisioningProfile
275
+ def self.type
276
+ 'inhouse'
277
+ end
278
+ end
279
+
280
+ # Download the current provisioning profile. This will *not* store
281
+ # the provisioning profile on the file system. Instead this method
282
+ # will return the content of the profile.
283
+ # @return (String) The content of the provisioning profile
284
+ # You'll probably want to store it on the file system
285
+ # @example
286
+ # File.write("path.mobileprovision", profile.download)
287
+ def download
288
+ client.download_provisioning_profile(self.id)
289
+ end
290
+
291
+ # Delete the provisioning profile
292
+ def delete!
293
+ client.delete_provisioning_profile!(self.id)
294
+ end
295
+
296
+ # Repair an existing provisioning profile
297
+ # alias to update!
298
+ # @return (ProvisioningProfile) A new provisioning profile, as
299
+ # the repair method will generate a profile with a new ID
300
+ def repair!
301
+ update!
302
+ end
303
+
304
+ # Updates the provisioning profile from the local data
305
+ # e.g. after you added new devices to the profile
306
+ # This will also update the code signing identity if necessary
307
+ # @return (ProvisioningProfile) A new provisioning profile, as
308
+ # the repair method will generate a profile with a new ID
309
+ def update!
310
+ unless certificate_valid?
311
+ if self.kind_of?Development
312
+ self.certificates = [Spaceship::Certificate::Development.all.first]
313
+ else
314
+ self.certificates = [Spaceship::Certificate::Production.all.first]
315
+ end
316
+ end
317
+
318
+ client.repair_provisioning_profile!(
319
+ self.id,
320
+ self.name,
321
+ self.distribution_method,
322
+ self.app.app_id,
323
+ self.certificates.map { |c| c.id },
324
+ self.devices.map { |d| d.id }
325
+ )
326
+
327
+ # We need to fetch the provisioning profile again, as the ID changes
328
+ profile = Spaceship::ProvisioningProfile.all.find do |profile|
329
+ profile.name == self.name # we can use the name as it's valid
330
+ end
331
+
332
+ return profile
333
+ end
334
+
335
+ # Is the certificate of this profile available?
336
+ # @return (Bool) is the certificate valid?
337
+ def certificate_valid?
338
+ return false if (certificates || []).count == 0
339
+ certificates.each do |c|
340
+ if Spaceship::Certificate.all.collect { |s| s.id }.include?(c.id)
341
+ return true
342
+ end
343
+ end
344
+ return false
345
+ end
346
+
347
+ # @return (Bool) Is this profile managed by Xcode?
348
+ def managed_by_xcode?
349
+ managing_app == 'Xcode'
350
+ end
351
+ end
352
+ end