fastlane-plugin-appcenter 1.8.1 → 1.9.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
  SHA256:
3
- metadata.gz: f3383c94635e8212980d620bf95d5e61eca5a3bbacfeea4875e8128884670d66
4
- data.tar.gz: 12d16a1669fb1f2c0239d3d0e5feba0e7a076d48c109047ba474b7e59672be5f
3
+ metadata.gz: 89d582118585c4f375abd0b893ad9648180e33d19308d20c881aea0262356a82
4
+ data.tar.gz: 57a447973defb5be52cee9ed03a9d635e442cc30b523152b23d0754b63638c14
5
5
  SHA512:
6
- metadata.gz: 1b8e7acb6b7dc353a0aa443e9d0737b08a634f88e8f311cd78be834d38ceb23d15e81038d8cfba88aca7e09be3f83431fb2681829b969c3f074fe21601b67fdb
7
- data.tar.gz: 0f143cb13db8034eca974c0c7a4a6c773c1a9faa3b11c29cf9419397957124f331bfa642ecaad475c4fa6cd41d5d11a53145035de16157b10e239c39573683bd
6
+ metadata.gz: 3c9c355551542046daffa8383aee7521d9185b76cc4351e494e6645de638a982af60812bb14982ef171707716dce5761d87e98dd9455a2548bde66d3157237db
7
+ data.tar.gz: c9a7690e05d9251cae36125ab72669bf11733b2fbf25c8d0f15544fe741b9f09e525d924524eb4daccc7056bdd290c06bc68df5f6a72d64558fc225fb4597544
data/README.md CHANGED
@@ -119,7 +119,7 @@ Here is the list of all existing parameters:
119
119
  | `release_notes_link` <br/> `APPCENTER_DISTRIBUTE_RELEASE_NOTES_LINK` | Additional release notes link |
120
120
  | `build_number` <br/> `APPCENTER_DISTRIBUTE_BUILD_NUMBER` | The build number, required for macOS .pkg and .dmg builds, as well as Android ProGuard `mapping.txt` when using `upload_mapping_only` |
121
121
  | `version` <br/> `APPCENTER_DISTRIBUTE_VERSION` | The build version, required for .pkg, .dmg, .zip and .msi builds, as well as Android ProGuard `mapping.txt` when using `upload_mapping_only` |
122
- | `timeout` <br/> `APPCENTER_DISTRIBUTE_TIMEOUT` | Request timeout in seconds |
122
+ | `timeout` <br/> `APPCENTER_DISTRIBUTE_TIMEOUT` | Request timeout in seconds applied to individual HTTP requests. Some commands use multiple HTTP requests, large file uploads are also split in multiple HTTP requests |
123
123
  | `dsa_signature` <br/> `APPCENTER_DISTRIBUTE_DSA_SIGNATURE` | DSA signature of the macOS or Windows release for Sparkle update feed |
124
124
  | `strict` <br/> `APPCENTER_STRICT_MODE` | Strict mode, set to 'true' to fail early in case a potential error was detected |
125
125
 
@@ -9,6 +9,23 @@ module Fastlane
9
9
  windows: %w(.appx .appxbundle .appxupload .msix .msixbundle .msixupload .zip .msi),
10
10
  custom: %w(.zip)
11
11
  }
12
+ CONTENT_TYPES = {
13
+ apk: "application/vnd.android.package-archive",
14
+ aab: "application/vnd.android.package-archive",
15
+ msi: "application/x-msi",
16
+ plist: "application/xml",
17
+ aetx: "application/c-x509-ca-cert",
18
+ cer: "application/pkix-cert",
19
+ xap: "application/x-silverlight-app",
20
+ appx: "application/x-appx",
21
+ appxbundle: "application/x-appxbundle",
22
+ appxupload: "application/x-appxupload",
23
+ appxsym: "application/x-appxupload",
24
+ msix: "application/x-msix",
25
+ msixbundle: "application/x-msixbundle",
26
+ msixupload: "application/x-msixupload",
27
+ msixsym: "application/x-msixupload",
28
+ }
12
29
  ALL_SUPPORTED_EXTENSIONS = SUPPORTED_EXTENSIONS.values.flatten.sort!.uniq!
13
30
  STORE_ONLY_EXTENSIONS = %w(.aab)
14
31
  STORE_SUPPORTED_EXTENSIONS = %w(.aab .apk .ipa)
@@ -186,14 +203,29 @@ module Fastlane
186
203
  UI.message("Starting release upload...")
187
204
  upload_details = Helper::AppcenterHelper.create_release_upload(api_token, owner_name, app_name, release_upload_body)
188
205
  if upload_details
189
- upload_id = upload_details['upload_id']
190
- upload_url = upload_details['upload_url']
206
+ upload_id = upload_details['id']
207
+
208
+ UI.message("Setting Metadata...")
209
+ content_type = Constants::CONTENT_TYPES[File.extname(file)&.delete('.').downcase.to_sym] || "application/octet-stream"
210
+ set_metadata_url = "#{upload_details['upload_domain']}/upload/set_metadata/#{upload_details['package_asset_id']}?file_name=#{File.basename(file)}&file_size=#{File.size(file)}&token=#{upload_details['url_encoded_token']}&content_type=#{content_type}"
211
+ chunk_size = Helper::AppcenterHelper.set_release_upload_metadata(set_metadata_url, api_token, owner_name, app_name, upload_id, timeout)
212
+ UI.abort_with_message!("Upload aborted") unless chunk_size
191
213
 
192
214
  UI.message("Uploading release binary...")
193
- uploaded = Helper::AppcenterHelper.upload_build(api_token, owner_name, app_name, file, upload_id, upload_url, timeout)
215
+ upload_url = "#{upload_details['upload_domain']}/upload/upload_chunk/#{upload_details['package_asset_id']}?token=#{upload_details['url_encoded_token']}"
216
+ uploaded = Helper::AppcenterHelper.upload_build(api_token, owner_name, app_name, file, upload_id, upload_url, content_type, chunk_size, timeout)
217
+ UI.abort_with_message!("Upload aborted") unless uploaded
218
+
219
+ UI.message("Finishing release...")
220
+ finish_url = "#{upload_details['upload_domain']}/upload/finished/#{upload_details['package_asset_id']}?token=#{upload_details['url_encoded_token']}"
221
+ finished = Helper::AppcenterHelper.finish_release_upload(finish_url, api_token, owner_name, app_name, upload_id, timeout)
222
+ UI.abort_with_message!("Upload aborted") unless finished
194
223
 
195
- if uploaded
196
- release_id = uploaded['release_id']
224
+ UI.message("Waiting for release to be ready...")
225
+ release_status_url = "v0.1/apps/#{owner_name}/#{app_name}/uploads/releases/#{upload_id}"
226
+ release_id = Helper::AppcenterHelper.poll_for_release_id(api_token, release_status_url)
227
+
228
+ if release_id.is_a? Integer
197
229
  release_url = Helper::AppcenterHelper.get_release_url(owner_type, owner_name, app_name, release_id)
198
230
  UI.message("Release '#{release_id}' committed: #{release_url}")
199
231
 
@@ -323,7 +355,6 @@ module Fastlane
323
355
  release = self.run_release_upload(params) unless upload_dsym_only || upload_mapping_only
324
356
  params[:version] = release['short_version'] if release
325
357
  params[:build_number] = release['version'] if release
326
-
327
358
  self.run_dsym_upload(params) unless upload_mapping_only || upload_build_only
328
359
  self.run_mapping_upload(params) unless upload_dsym_only || upload_build_only
329
360
  end
@@ -603,7 +634,7 @@ module Fastlane
603
634
 
604
635
  FastlaneCore::ConfigItem.new(key: :timeout,
605
636
  env_name: "APPCENTER_DISTRIBUTE_TIMEOUT",
606
- description: "Request timeout in seconds",
637
+ description: "Request timeout in seconds applied to individual HTTP requests. Some commands use multiple HTTP requests, large file uploads are also split in multiple HTTP requests",
607
638
  optional: true,
608
639
  type: Integer),
609
640
 
@@ -1,7 +1,22 @@
1
+ class File
2
+ def each_chunk(chunk_size)
3
+ yield read(chunk_size) until eof?
4
+ end
5
+ end
6
+
1
7
  module Fastlane
2
8
  module Helper
3
9
  class AppcenterHelper
4
10
 
11
+ # Time to wait between 2 status polls in seconds
12
+ RELEASE_UPLOAD_STATUS_POLL_INTERVAL = 1
13
+
14
+ # Maximum number of retries for a request
15
+ MAX_REQUEST_RETRIES = 2
16
+
17
+ # Delay between retries in seconds
18
+ REQUEST_RETRY_INTERVAL = 5
19
+
5
20
  # basic utility method to check file types that App Center will accept,
6
21
  # accounting for file types that can and should be zip-compressed
7
22
  # before they are uploaded
@@ -22,7 +37,6 @@ module Fastlane
22
37
  if ENV['APPCENTER_ENV']&.upcase == 'INT'
23
38
  default_api_url = "https://api-gateway-core-integration.dev.avalanch.es"
24
39
  end
25
-
26
40
  options = {
27
41
  url: upload_url || default_api_url
28
42
  }
@@ -48,13 +62,11 @@ module Fastlane
48
62
  # upload_url
49
63
  def self.create_release_upload(api_token, owner_name, app_name, body)
50
64
  connection = self.connection
51
-
52
- url = "v0.1/apps/#{owner_name}/#{app_name}/release_uploads"
65
+ url = "v0.1/apps/#{owner_name}/#{app_name}/uploads/releases"
53
66
  body ||= {}
54
67
 
55
68
  UI.message("DEBUG: POST #{url}") if ENV['DEBUG']
56
69
  UI.message("DEBUG: POST body: #{JSON.pretty_generate(body)}\n") if ENV['DEBUG']
57
-
58
70
  response = connection.post(url) do |req|
59
71
  req.headers['X-API-Token'] = api_token
60
72
  req.headers['internal-request-source'] = "fastlane"
@@ -62,7 +74,6 @@ module Fastlane
62
74
  end
63
75
 
64
76
  UI.message("DEBUG: #{response.status} #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
65
-
66
77
  case response.status
67
78
  when 200...300
68
79
  response.body
@@ -229,50 +240,141 @@ module Fastlane
229
240
  end
230
241
  end
231
242
 
232
- # upload binary for specified upload_url
233
- # if succeed, then commits the release
234
- # otherwise aborts
235
- def self.upload_build(api_token, owner_name, app_name, file, upload_id, upload_url, timeout)
236
- connection = self.connection(upload_url)
237
-
238
- options = {}
239
- options[:upload_id] = upload_id
240
- # ipa field is used for .apk, .aab and .ipa files
241
- options[:ipa] = Faraday::UploadIO.new(file, 'application/octet-stream') if file && File.exist?(file)
243
+ # sets metadata for new upload in App Center
244
+ # returns:
245
+ # chunk size
246
+ def self.set_release_upload_metadata(set_metadata_url, api_token, owner_name, app_name, upload_id, timeout)
247
+ connection = self.connection(set_metadata_url)
242
248
 
243
- UI.message("DEBUG: POST #{upload_url}") if ENV['DEBUG']
249
+ UI.message("DEBUG: POST #{set_metadata_url}") if ENV['DEBUG']
244
250
  UI.message("DEBUG: POST body <data>\n") if ENV['DEBUG']
245
-
246
251
  response = connection.post do |req|
247
252
  req.options.timeout = timeout
248
253
  req.headers['internal-request-source'] = "fastlane"
249
- req.body = options
250
254
  end
255
+ UI.message("DEBUG: #{response.status} #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
251
256
 
257
+ case response.status
258
+ when 200...300
259
+ chunk_size = response.body['chunk_size']
260
+ unless chunk_size.is_a? Integer
261
+ UI.error("Set metadata didn't return chunk size: #{response.status}: #{response.body}")
262
+ false
263
+ else
264
+ UI.message("Metadata set")
265
+ chunk_size
266
+ end
267
+ when 401
268
+ UI.user_error!("Auth Error, provided invalid token")
269
+ false
270
+ else
271
+ UI.error("Error setting metadata: #{response.status}: #{response.body}")
272
+ false
273
+ end
274
+ end
275
+
276
+ # Verifies a successful upload to App Center
277
+ # returns:
278
+ # successful upload response body.
279
+ def self.finish_release_upload(finish_url, api_token, owner_name, app_name, upload_id, timeout)
280
+ connection = self.connection(finish_url)
281
+
282
+ UI.message("DEBUG: POST #{finish_url}") if ENV['DEBUG']
283
+ response = connection.post do |req|
284
+ req.options.timeout = timeout
285
+ req.headers['internal-request-source'] = "fastlane"
286
+ end
252
287
  UI.message("DEBUG: #{response.status} #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
253
288
 
254
289
  case response.status
255
290
  when 200...300
256
- UI.message("Binary uploaded")
257
- self.update_release_upload(api_token, owner_name, app_name, upload_id, 'committed')
291
+ if response.body['error'] == false
292
+ UI.message("Upload finished")
293
+ self.update_release_upload(api_token, owner_name, app_name, upload_id, 'uploadFinished')
294
+ else
295
+ UI.error("Error finishing upload: #{response.body['message']}")
296
+ false
297
+ end
258
298
  when 401
259
299
  UI.user_error!("Auth Error, provided invalid token")
260
300
  false
261
301
  else
262
- UI.error("Error uploading binary #{response.status}: #{response.body}")
263
- self.update_release_upload(api_token, owner_name, app_name, upload_id, 'aborted')
264
- UI.error("Release aborted")
302
+ UI.error("Error finishing upload: #{response.status}: #{response.body}")
265
303
  false
266
304
  end
267
305
  end
268
306
 
307
+ # upload binary for specified upload_url
308
+ # if succeed, then commits the release
309
+ # otherwise aborts
310
+ def self.upload_build(api_token, owner_name, app_name, file, upload_id, upload_url, content_type, chunk_size, timeout)
311
+ block_number = 1
312
+
313
+ File.open(file).each_chunk(chunk_size) do |chunk|
314
+ upload_chunk_url = "#{upload_url}&block_number=#{block_number}"
315
+ retries = 0
316
+
317
+ while retries <= MAX_REQUEST_RETRIES
318
+ begin
319
+ connection = self.connection(upload_chunk_url, true)
320
+
321
+ UI.message("DEBUG: POST #{upload_chunk_url}") if ENV['DEBUG']
322
+ UI.message("DEBUG: POST body <data>\n") if ENV['DEBUG']
323
+ response = connection.post do |req|
324
+ req.options.timeout = timeout
325
+ req.headers['internal-request-source'] = "fastlane"
326
+ req.headers['Content-Length'] = chunk.length.to_s
327
+ req.body = chunk
328
+ end
329
+ UI.message("DEBUG: #{response.status} #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
330
+ status = response.status
331
+ message = response.body
332
+ rescue Faraday::Error => e
333
+
334
+ # Low level HTTP errors, we will retry them
335
+ status = 0
336
+ message = e.message
337
+ end
338
+
339
+ case status
340
+ when 200...300
341
+ if response.body['error'] == false
342
+ UI.message("Chunk uploaded")
343
+ block_number += 1
344
+ break
345
+ else
346
+ UI.error("Error uploading binary #{response.body['message']}")
347
+ return false
348
+ end
349
+ when 401
350
+ UI.user_error!("Auth Error, provided invalid token")
351
+ return false
352
+ when 400...407, 409...428, 430...499
353
+ UI.user_error!("Client error: #{response.status}: #{response.body}")
354
+ return false
355
+ else
356
+ if retries < MAX_REQUEST_RETRIES
357
+ UI.message("DEBUG: Retryable error uploading binary #{status}: #{message}")
358
+ retries += 1
359
+ sleep(REQUEST_RETRY_INTERVAL)
360
+ else
361
+ UI.error("Error uploading binary #{status}: #{message}")
362
+ return false
363
+ end
364
+ end
365
+ end
366
+ end
367
+ UI.message("Binary uploaded")
368
+ end
369
+
269
370
  # Commits or aborts the upload process for a release
270
371
  def self.update_release_upload(api_token, owner_name, app_name, upload_id, status)
271
372
  connection = self.connection
272
373
 
273
- url = "v0.1/apps/#{owner_name}/#{app_name}/release_uploads/#{upload_id}"
374
+ url = "v0.1/apps/#{owner_name}/#{app_name}/uploads/releases/#{upload_id}"
274
375
  body = {
275
- status: status
376
+ upload_status: status,
377
+ id: upload_id
276
378
  }
277
379
 
278
380
  UI.message("DEBUG: PATCH #{url}") if ENV['DEBUG']
@@ -331,6 +433,40 @@ module Fastlane
331
433
  end
332
434
  end
333
435
 
436
+ # Polls the upload for a release id. When a release is uploaded, we have to check
437
+ # for a successful extraction before we can continue.
438
+ # returns:
439
+ # release_distinct_id
440
+ def self.poll_for_release_id(api_token, url)
441
+ connection = self.connection
442
+
443
+ while true
444
+ UI.message("DEBUG: GET #{url}") if ENV['DEBUG']
445
+ response = connection.get(url) do |req|
446
+ req.headers['X-API-Token'] = api_token
447
+ req.headers['internal-request-source'] = "fastlane"
448
+ end
449
+
450
+ UI.message("DEBUG: #{response.status} #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
451
+
452
+ case response.status
453
+ when 200...300
454
+ case response.body['upload_status']
455
+ when "readyToBePublished"
456
+ return response.body['release_distinct_id']
457
+ when "error"
458
+ UI.error("Error fetching release: #{response.body['error_details']}")
459
+ return false
460
+ else
461
+ sleep(RELEASE_UPLOAD_STATUS_POLL_INTERVAL)
462
+ end
463
+ else
464
+ UI.error("Error fetching information about release #{response.status}: #{response.body}")
465
+ return false
466
+ end
467
+ end
468
+ end
469
+
334
470
  # get distribution group or store
335
471
  def self.get_destination(api_token, owner_name, app_name, destination_type, destination_name)
336
472
  connection = self.connection
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
2
  module Appcenter
3
- VERSION = "1.8.1"
3
+ VERSION = "1.9.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fastlane-plugin-appcenter
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.1
4
+ version: 1.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Microsoft Corporation
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-23 00:00:00.000000000 Z
11
+ date: 2020-07-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -155,7 +155,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
155
155
  - !ruby/object:Gem::Version
156
156
  version: '0'
157
157
  requirements: []
158
- rubygems_version: 3.0.8
158
+ rubygems_version: 3.0.3
159
159
  signing_key:
160
160
  specification_version: 4
161
161
  summary: Fastlane plugin for App Center