fastlane-plugin-appcenter 1.8.1 → 1.9.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
  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