fastlane-plugin-appcenter 1.6.0 → 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.
@@ -1,18 +1,31 @@
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
8
23
  def self.file_extname_full(path)
9
- is_zip = File.extname(path) == ".zip"
10
-
11
- # if file is not .zip'ed, these do not change basename and extname
12
- unzip_basename = File.basename(path, ".zip")
13
- unzip_extname = File.extname(unzip_basename)
24
+ %w(.app.zip .dSYM.zip).each do |suffix|
25
+ return suffix if path.to_s.downcase.end_with? suffix.downcase
26
+ end
14
27
 
15
- is_zip ? unzip_extname + ".zip" : unzip_extname
28
+ File.extname path
16
29
  end
17
30
 
18
31
  # create request
@@ -20,10 +33,16 @@ module Fastlane
20
33
  require 'faraday'
21
34
  require 'faraday_middleware'
22
35
 
36
+ default_api_url = "https://api.appcenter.ms"
37
+ if ENV['APPCENTER_ENV']&.upcase == 'INT'
38
+ default_api_url = "https://api-gateway-core-integration.dev.avalanch.es"
39
+ end
23
40
  options = {
24
- url: upload_url || ENV.fetch('APPCENTER_UPLOAD_URL', "https://api.appcenter.ms")
41
+ url: upload_url || default_api_url
25
42
  }
26
43
 
44
+ UI.message("DEBUG: BASE URL #{options[:url]}") if ENV['DEBUG']
45
+
27
46
  Faraday.new(options) do |builder|
28
47
  if upload_url
29
48
  builder.request :multipart unless dsym
@@ -43,16 +62,20 @@ module Fastlane
43
62
  # upload_url
44
63
  def self.create_release_upload(api_token, owner_name, app_name, body)
45
64
  connection = self.connection
65
+ url = "v0.1/apps/#{owner_name}/#{app_name}/uploads/releases"
66
+ body ||= {}
46
67
 
47
- response = connection.post("v0.1/apps/#{owner_name}/#{app_name}/release_uploads") do |req|
68
+ UI.message("DEBUG: POST #{url}") if ENV['DEBUG']
69
+ UI.message("DEBUG: POST body: #{JSON.pretty_generate(body)}\n") if ENV['DEBUG']
70
+ response = connection.post(url) do |req|
48
71
  req.headers['X-API-Token'] = api_token
49
72
  req.headers['internal-request-source'] = "fastlane"
50
- req.body = body.nil? && {} || body
73
+ req.body = body
51
74
  end
52
75
 
76
+ UI.message("DEBUG: #{response.status} #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
53
77
  case response.status
54
78
  when 200...300
55
- UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
56
79
  response.body
57
80
  when 401
58
81
  UI.user_error!("Auth Error, provided invalid token")
@@ -61,7 +84,7 @@ module Fastlane
61
84
  UI.error("Not found, invalid owner or application name")
62
85
  false
63
86
  when 500...600
64
- UI.crash!("Internal Service Error, please try again later")
87
+ UI.abort_with_message!("Internal Service Error, please try again later")
65
88
  else
66
89
  UI.error("Error #{response.status}: #{response.body}")
67
90
  false
@@ -76,20 +99,27 @@ module Fastlane
76
99
  def self.create_mapping_upload(api_token, owner_name, app_name, file_name, build_number, version)
77
100
  connection = self.connection
78
101
 
79
- response = connection.post("v0.1/apps/#{owner_name}/#{app_name}/symbol_uploads") do |req|
102
+ url = "v0.1/apps/#{owner_name}/#{app_name}/symbol_uploads"
103
+ body = {
104
+ symbol_type: "AndroidProguard",
105
+ file_name: file_name,
106
+ build: build_number,
107
+ version: version,
108
+ }
109
+
110
+ UI.message("DEBUG: POST #{url}") if ENV['DEBUG']
111
+ UI.message("DEBUG: POST body #{JSON.pretty_generate(body)}\n") if ENV['DEBUG']
112
+
113
+ response = connection.post(url) do |req|
80
114
  req.headers['X-API-Token'] = api_token
81
115
  req.headers['internal-request-source'] = "fastlane"
82
- req.body = {
83
- symbol_type: "AndroidProguard",
84
- file_name: file_name,
85
- build: build_number,
86
- version: version,
87
- }
116
+ req.body = body
88
117
  end
89
118
 
119
+ UI.message("DEBUG: #{response.status} #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
120
+
90
121
  case response.status
91
122
  when 200...300
92
- UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
93
123
  response.body
94
124
  when 401
95
125
  UI.user_error!("Auth Error, provided invalid token")
@@ -111,17 +141,24 @@ module Fastlane
111
141
  def self.create_dsym_upload(api_token, owner_name, app_name)
112
142
  connection = self.connection
113
143
 
114
- response = connection.post("v0.1/apps/#{owner_name}/#{app_name}/symbol_uploads") do |req|
144
+ url = "v0.1/apps/#{owner_name}/#{app_name}/symbol_uploads"
145
+ body = {
146
+ symbol_type: 'Apple'
147
+ }
148
+
149
+ UI.message("DEBUG: POST #{url}") if ENV['DEBUG']
150
+ UI.message("DEBUG: POST body #{JSON.pretty_generate(body)}\n") if ENV['DEBUG']
151
+
152
+ response = connection.post(url) do |req|
115
153
  req.headers['X-API-Token'] = api_token
116
154
  req.headers['internal-request-source'] = "fastlane"
117
- req.body = {
118
- symbol_type: 'Apple'
119
- }
155
+ req.body = body
120
156
  end
121
157
 
158
+ UI.message("DEBUG: #{response.status} #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
159
+
122
160
  case response.status
123
161
  when 200...300
124
- UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
125
162
  response.body
126
163
  when 401
127
164
  UI.user_error!("Auth Error, provided invalid token")
@@ -139,17 +176,24 @@ module Fastlane
139
176
  def self.update_symbol_upload(api_token, owner_name, app_name, symbol_upload_id, status)
140
177
  connection = self.connection
141
178
 
142
- response = connection.patch("v0.1/apps/#{owner_name}/#{app_name}/symbol_uploads/#{symbol_upload_id}") do |req|
179
+ url = "v0.1/apps/#{owner_name}/#{app_name}/symbol_uploads/#{symbol_upload_id}"
180
+ body = {
181
+ status: status
182
+ }
183
+
184
+ UI.message("DEBUG: PATCH #{url}") if ENV['DEBUG']
185
+ UI.message("DEBUG: PATCH body #{JSON.pretty_generate(body)}\n") if ENV['DEBUG']
186
+
187
+ response = connection.patch(url) do |req|
143
188
  req.headers['X-API-Token'] = api_token
144
189
  req.headers['internal-request-source'] = "fastlane"
145
- req.body = {
146
- "status" => status
147
- }
190
+ req.body = body
148
191
  end
149
192
 
193
+ UI.message("DEBUG: #{response.status} #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
194
+
150
195
  case response.status
151
196
  when 200...300
152
- UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
153
197
  response.body
154
198
  when 401
155
199
  UI.user_error!("Auth Error, provided invalid token")
@@ -166,6 +210,9 @@ module Fastlane
166
210
  def self.upload_symbol(api_token, owner_name, app_name, symbol, symbol_type, symbol_upload_id, upload_url)
167
211
  connection = self.connection(upload_url, true)
168
212
 
213
+ UI.message("DEBUG: PUT #{upload_url}") if ENV['DEBUG']
214
+ UI.message("DEBUG: PUT body <data>\n") if ENV['DEBUG']
215
+
169
216
  response = connection.put do |req|
170
217
  req.headers['x-ms-blob-type'] = "BlockBlob"
171
218
  req.headers['Content-Length'] = File.size(symbol).to_s
@@ -173,77 +220,182 @@ module Fastlane
173
220
  req.body = Faraday::UploadIO.new(symbol, 'application/octet-stream') if symbol && File.exist?(symbol)
174
221
  end
175
222
 
176
- logType = "dSYM" if (symbol_type == "Apple")
177
- logType = "mapping" if (symbol_type == "Android")
223
+ UI.message("DEBUG: #{response.status} #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
224
+
225
+ log_type = "dSYM" if symbol_type == "Apple"
226
+ log_type = "mapping" if symbol_type == "Android"
178
227
 
179
228
  case response.status
180
229
  when 200...300
181
230
  self.update_symbol_upload(api_token, owner_name, app_name, symbol_upload_id, 'committed')
182
- UI.success("#{logType} uploaded")
231
+ UI.success("#{log_type} uploaded")
183
232
  when 401
184
233
  UI.user_error!("Auth Error, provided invalid token")
185
234
  false
186
235
  else
187
- UI.error("Error uploading #{logType} #{response.status}: #{response.body}")
236
+ UI.error("Error uploading #{log_type} #{response.status}: #{response.body}")
188
237
  self.update_symbol_upload(api_token, owner_name, app_name, symbol_upload_id, 'aborted')
189
- UI.error("#{logType} upload aborted")
238
+ UI.error("#{log_type} upload aborted")
190
239
  false
191
240
  end
192
241
  end
193
242
 
194
- # upload binary for specified upload_url
195
- # if succeed, then commits the release
196
- # otherwise aborts
197
- def self.upload_build(api_token, owner_name, app_name, file, upload_id, upload_url, timeout)
198
- connection = self.connection(upload_url)
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)
199
248
 
200
- options = {}
201
- options[:upload_id] = upload_id
202
- # ipa field is used for .apk, .aab and .ipa files
203
- options[:ipa] = Faraday::UploadIO.new(file, 'application/octet-stream') if file && File.exist?(file)
249
+ UI.message("DEBUG: POST #{set_metadata_url}") if ENV['DEBUG']
250
+ UI.message("DEBUG: POST body <data>\n") if ENV['DEBUG']
251
+ response = connection.post do |req|
252
+ req.options.timeout = timeout
253
+ req.headers['internal-request-source'] = "fastlane"
254
+ end
255
+ UI.message("DEBUG: #{response.status} #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
204
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']
205
283
  response = connection.post do |req|
206
284
  req.options.timeout = timeout
207
285
  req.headers['internal-request-source'] = "fastlane"
208
- req.body = options
209
286
  end
287
+ UI.message("DEBUG: #{response.status} #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
210
288
 
211
289
  case response.status
212
290
  when 200...300
213
- UI.message("Binary uploaded")
214
- 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
215
298
  when 401
216
299
  UI.user_error!("Auth Error, provided invalid token")
217
300
  false
218
301
  else
219
- UI.error("Error uploading binary #{response.status}: #{response.body}")
220
- self.update_release_upload(api_token, owner_name, app_name, upload_id, 'aborted')
221
- UI.error("Release aborted")
302
+ UI.error("Error finishing upload: #{response.status}: #{response.body}")
222
303
  false
223
304
  end
224
305
  end
225
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
+
226
370
  # Commits or aborts the upload process for a release
227
371
  def self.update_release_upload(api_token, owner_name, app_name, upload_id, status)
228
372
  connection = self.connection
229
373
 
230
- response = connection.patch("v0.1/apps/#{owner_name}/#{app_name}/release_uploads/#{upload_id}") do |req|
374
+ url = "v0.1/apps/#{owner_name}/#{app_name}/uploads/releases/#{upload_id}"
375
+ body = {
376
+ upload_status: status,
377
+ id: upload_id
378
+ }
379
+
380
+ UI.message("DEBUG: PATCH #{url}") if ENV['DEBUG']
381
+ UI.message("DEBUG: PATCH body #{JSON.pretty_generate(body)}\n") if ENV['DEBUG']
382
+
383
+ response = connection.patch(url) do |req|
231
384
  req.headers['X-API-Token'] = api_token
232
385
  req.headers['internal-request-source'] = "fastlane"
233
- req.body = {
234
- "status" => status
235
- }
386
+ req.body = body
236
387
  end
237
388
 
389
+ UI.message("DEBUG: #{response.status} #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
390
+
238
391
  case response.status
239
392
  when 200...300
240
- UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
241
393
  response.body
242
394
  when 401
243
395
  UI.user_error!("Auth Error, provided invalid token")
244
396
  false
245
397
  when 500...600
246
- UI.crash!("Internal Service Error, please try again later")
398
+ UI.abort_with_message!("Internal Service Error, please try again later")
247
399
  else
248
400
  UI.error("Error #{response.status}: #{response.body}")
249
401
  false
@@ -253,15 +405,21 @@ module Fastlane
253
405
  # get existing release
254
406
  def self.get_release(api_token, owner_name, app_name, release_id)
255
407
  connection = self.connection
256
- response = connection.get("v0.1/apps/#{owner_name}/#{app_name}/releases/#{release_id}") do |req|
408
+
409
+ url = "v0.1/apps/#{owner_name}/#{app_name}/releases/#{release_id}"
410
+
411
+ UI.message("DEBUG: GET #{url}") if ENV['DEBUG']
412
+
413
+ response = connection.get(url) do |req|
257
414
  req.headers['X-API-Token'] = api_token
258
415
  req.headers['internal-request-source'] = "fastlane"
259
416
  end
260
417
 
418
+ UI.message("DEBUG: #{response.status} #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
419
+
261
420
  case response.status
262
421
  when 200...300
263
422
  release = response.body
264
- UI.message("DEBUG: #{JSON.pretty_generate(release)}") if ENV['DEBUG']
265
423
  release
266
424
  when 404
267
425
  UI.error("Not found, invalid release url")
@@ -275,19 +433,58 @@ module Fastlane
275
433
  end
276
434
  end
277
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
+
278
470
  # get distribution group or store
279
471
  def self.get_destination(api_token, owner_name, app_name, destination_type, destination_name)
280
472
  connection = self.connection
281
473
 
282
- response = connection.get("v0.1/apps/#{owner_name}/#{app_name}/distribution_#{destination_type}s/#{ERB::Util.url_encode(destination_name)}") do |req|
474
+ url = "v0.1/apps/#{owner_name}/#{app_name}/distribution_#{destination_type}s/#{ERB::Util.url_encode(destination_name)}"
475
+
476
+ UI.message("DEBUG: GET #{url}") if ENV['DEBUG']
477
+
478
+ response = connection.get(url) do |req|
283
479
  req.headers['X-API-Token'] = api_token
284
480
  req.headers['internal-request-source'] = "fastlane"
285
481
  end
286
482
 
483
+ UI.message("DEBUG: #{response.status} #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
484
+
287
485
  case response.status
288
486
  when 200...300
289
487
  destination = response.body
290
- UI.message("DEBUG: received #{destination_type} #{JSON.pretty_generate(destination)}") if ENV['DEBUG']
291
488
  destination
292
489
  when 404
293
490
  UI.error("Not found, invalid distribution #{destination_type} name")
@@ -305,14 +502,22 @@ module Fastlane
305
502
  def self.update_release(api_token, owner_name, app_name, release_id, release_notes = '')
306
503
  connection = self.connection
307
504
 
308
- response = connection.put("v0.1/apps/#{owner_name}/#{app_name}/releases/#{release_id}") do |req|
505
+ url = "v0.1/apps/#{owner_name}/#{app_name}/releases/#{release_id}"
506
+ body = {
507
+ release_notes: release_notes
508
+ }
509
+
510
+ UI.message("DEBUG: PUT #{url}") if ENV['DEBUG']
511
+ UI.message("DEBUG: PUT body #{JSON.pretty_generate(body)}\n") if ENV['DEBUG']
512
+
513
+ response = connection.put(url) do |req|
309
514
  req.headers['X-API-Token'] = api_token
310
515
  req.headers['internal-request-source'] = "fastlane"
311
- req.body = {
312
- release_notes: release_notes
313
- }
516
+ req.body = body
314
517
  end
315
518
 
519
+ UI.message("DEBUG: #{response.status} #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
520
+
316
521
  case response.status
317
522
  when 200...300
318
523
  # get full release info
@@ -321,8 +526,6 @@ module Fastlane
321
526
 
322
527
  download_url = release['download_url']
323
528
 
324
- UI.message("DEBUG: #{JSON.pretty_generate(release)}") if ENV['DEBUG']
325
-
326
529
  Actions.lane_context[Fastlane::Actions::SharedValues::APPCENTER_DOWNLOAD_LINK] = download_url
327
530
  Actions.lane_context[Fastlane::Actions::SharedValues::APPCENTER_BUILD_INFORMATION] = release
328
531
 
@@ -345,19 +548,25 @@ module Fastlane
345
548
  def self.update_release_metadata(api_token, owner_name, app_name, release_id, dsa_signature)
346
549
  return if dsa_signature.to_s == ''
347
550
 
348
- release_metadata = {
349
- dsa_signature: dsa_signature
551
+ url = "v0.1/apps/#{owner_name}/#{app_name}/releases/#{release_id}"
552
+ body = {
553
+ metadata: {
554
+ dsa_signature: dsa_signature
555
+ }
350
556
  }
351
557
 
558
+ UI.message("DEBUG: PATCH #{url}") if ENV['DEBUG']
559
+ UI.message("DEBUG: PATCH body #{JSON.pretty_generate(body)}\n") if ENV['DEBUG']
560
+
352
561
  connection = self.connection
353
- response = connection.patch("v0.1/apps/#{owner_name}/#{app_name}/releases/#{release_id}") do |req|
562
+ response = connection.patch(url) do |req|
354
563
  req.headers['X-API-Token'] = api_token
355
564
  req.headers['internal-request-source'] = "fastlane"
356
- req.body = {
357
- metadata: release_metadata
358
- }
565
+ req.body = body
359
566
  end
360
567
 
568
+ UI.message("DEBUG: #{response.status} #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
569
+
361
570
  case response.status
362
571
  when 200...300
363
572
  UI.message("Release Metadata was successfully updated for release '#{release_id}'")
@@ -372,25 +581,32 @@ module Fastlane
372
581
  false
373
582
  end
374
583
  end
375
-
584
+
376
585
  # add release to distribution group or store
377
586
  def self.add_to_destination(api_token, owner_name, app_name, release_id, destination_type, destination_id, mandatory_update = false, notify_testers = false)
378
587
  connection = self.connection
379
588
 
380
- UI.message("DEBUG: getting #{release_id}") if ENV['DEBUG']
589
+ url = "v0.1/apps/#{owner_name}/#{app_name}/releases/#{release_id}/#{destination_type}s"
590
+ body = {
591
+ id: destination_id
592
+ }
381
593
 
382
- body = { "id" => destination_id }
383
594
  if destination_type == "group"
384
595
  body["mandatory_update"] = mandatory_update
385
596
  body["notify_testers"] = notify_testers
386
597
  end
387
598
 
388
- response = connection.post("v0.1/apps/#{owner_name}/#{app_name}/releases/#{release_id}/#{destination_type}s") do |req|
599
+ UI.message("DEBUG: POST #{url}") if ENV['DEBUG']
600
+ UI.message("DEBUG: POST body #{JSON.pretty_generate(body)}\n") if ENV['DEBUG']
601
+
602
+ response = connection.post(url) do |req|
389
603
  req.headers['X-API-Token'] = api_token
390
604
  req.headers['internal-request-source'] = "fastlane"
391
605
  req.body = body
392
606
  end
393
607
 
608
+ UI.message("DEBUG: #{response.status} #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
609
+
394
610
  case response.status
395
611
  when 200...300
396
612
  # get full release info
@@ -399,8 +615,6 @@ module Fastlane
399
615
 
400
616
  download_url = release['download_url']
401
617
 
402
- UI.message("DEBUG: received release #{JSON.pretty_generate(release)}") if ENV['DEBUG']
403
-
404
618
  Actions.lane_context[Fastlane::Actions::SharedValues::APPCENTER_DOWNLOAD_LINK] = download_url
405
619
  Actions.lane_context[Fastlane::Actions::SharedValues::APPCENTER_BUILD_INFORMATION] = release
406
620
 
@@ -423,11 +637,17 @@ module Fastlane
423
637
  def self.get_app(api_token, owner_name, app_name)
424
638
  connection = self.connection
425
639
 
426
- response = connection.get("v0.1/apps/#{owner_name}/#{app_name}") do |req|
640
+ url = "v0.1/apps/#{owner_name}/#{app_name}"
641
+
642
+ UI.message("DEBUG: GET #{url}") if ENV['DEBUG']
643
+
644
+ response = connection.get(url) do |req|
427
645
  req.headers['X-API-Token'] = api_token
428
646
  req.headers['internal-request-source'] = "fastlane"
429
647
  end
430
648
 
649
+ UI.message("DEBUG: #{response.status} #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
650
+
431
651
  case response.status
432
652
  when 200...300
433
653
  UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
@@ -448,23 +668,28 @@ module Fastlane
448
668
  def self.create_app(api_token, owner_type, owner_name, app_name, app_display_name, os, platform)
449
669
  connection = self.connection
450
670
 
451
- endpoint = owner_type == "user" ? "v0.1/apps" : "v0.1/orgs/#{owner_name}/apps"
671
+ url = owner_type == "user" ? "v0.1/apps" : "v0.1/orgs/#{owner_name}/apps"
672
+ body = {
673
+ display_name: app_display_name,
674
+ name: app_name,
675
+ os: os,
676
+ platform: platform
677
+ }
678
+
679
+ UI.message("DEBUG: POST #{url}") if ENV['DEBUG']
680
+ UI.message("DEBUG: POST body #{JSON.pretty_generate(body)}\n") if ENV['DEBUG']
452
681
 
453
- response = connection.post(endpoint) do |req|
682
+ response = connection.post(url) do |req|
454
683
  req.headers['X-API-Token'] = api_token
455
684
  req.headers['internal-request-source'] = "fastlane"
456
- req.body = {
457
- "display_name" => app_display_name,
458
- "name" => app_name,
459
- "os" => os,
460
- "platform" => platform
461
- }
685
+ req.body = body
462
686
  end
463
687
 
688
+ UI.message("DEBUG: #{response.status} #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
689
+
464
690
  case response.status
465
691
  when 200...300
466
692
  created = response.body
467
- UI.message("DEBUG: #{JSON.pretty_generate(created)}") if ENV['DEBUG']
468
693
  UI.success("Created #{os}/#{platform} app with name \"#{created['name']}\" and display name \"#{created['display_name']}\" for #{owner_type} \"#{owner_name}\"")
469
694
  true
470
695
  when 401
@@ -479,16 +704,19 @@ module Fastlane
479
704
  def self.fetch_distribution_groups(api_token:, owner_name:, app_name:)
480
705
  connection = self.connection
481
706
 
482
- endpoint = "/v0.1/apps/#{owner_name}/#{app_name}/distribution_groups"
707
+ url = "/v0.1/apps/#{owner_name}/#{app_name}/distribution_groups"
483
708
 
484
- response = connection.get(endpoint) do |req|
709
+ UI.message("DEBUG: GET #{url}") if ENV['DEBUG']
710
+
711
+ response = connection.get(url) do |req|
485
712
  req.headers['X-API-Token'] = api_token
486
713
  req.headers['internal-request-source'] = "fastlane"
487
714
  end
488
715
 
716
+ UI.message("DEBUG: #{response.status} #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
717
+
489
718
  case response.status
490
719
  when 200...300
491
- UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
492
720
  response.body
493
721
  when 401
494
722
  UI.user_error!("Auth Error, provided invalid token")
@@ -505,16 +733,19 @@ module Fastlane
505
733
  def self.fetch_devices(api_token:, owner_name:, app_name:, distribution_group:)
506
734
  connection = self.connection(nil, false, true)
507
735
 
508
- endpoint = "/v0.1/apps/#{owner_name}/#{app_name}/distribution_groups/#{ERB::Util.url_encode(distribution_group)}/devices/download_devices_list"
736
+ url = "/v0.1/apps/#{owner_name}/#{app_name}/distribution_groups/#{ERB::Util.url_encode(distribution_group)}/devices/download_devices_list"
509
737
 
510
- response = connection.get(endpoint) do |req|
738
+ UI.message("DEBUG: GET #{url}") if ENV['DEBUG']
739
+
740
+ response = connection.get(url) do |req|
511
741
  req.headers['X-API-Token'] = api_token
512
742
  req.headers['internal-request-source'] = "fastlane"
513
743
  end
514
744
 
745
+ UI.message("DEBUG: #{response.status} #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
746
+
515
747
  case response.status
516
748
  when 200...300
517
- UI.message("DEBUG: #{response.body.inspect}") if ENV['DEBUG']
518
749
  response.body
519
750
  when 401
520
751
  UI.user_error!("Auth Error, provided invalid token")
@@ -528,17 +759,87 @@ module Fastlane
528
759
  end
529
760
  end
530
761
 
531
- # Note: This does not support testing environment (INT)
762
+ def self.fetch_releases(api_token:, owner_name:, app_name:)
763
+ connection = self.connection(nil, false, true)
764
+
765
+ url = "/v0.1/apps/#{owner_name}/#{app_name}/releases"
766
+
767
+ UI.message("DEBUG: GET #{url}") if ENV['DEBUG']
768
+
769
+ response = connection.get(url) do |req|
770
+ req.headers['X-API-Token'] = api_token
771
+ req.headers['internal-request-source'] = "fastlane"
772
+ end
773
+
774
+ UI.message("DEBUG: #{response.status} #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
775
+
776
+ case response.status
777
+ when 200...300
778
+ JSON.parse(response.body)
779
+ when 401
780
+ UI.user_error!("Auth Error, provided invalid token")
781
+ false
782
+ when 404
783
+ UI.error("Not found, invalid owner or application name")
784
+ false
785
+ else
786
+ UI.error("Error #{response.status}: #{response.body}")
787
+ false
788
+ end
789
+ end
790
+
532
791
  def self.get_release_url(owner_type, owner_name, app_name, release_id)
533
792
  owner_path = owner_type == "user" ? "users/#{owner_name}" : "orgs/#{owner_name}"
793
+ if ENV['APPCENTER_ENV']&.upcase == 'INT'
794
+ return "https://portal-server-core-integration.dev.avalanch.es/#{owner_path}/apps/#{app_name}/distribute/releases/#{release_id}"
795
+ end
796
+
534
797
  return "https://appcenter.ms/#{owner_path}/apps/#{app_name}/distribute/releases/#{release_id}"
535
798
  end
536
799
 
537
- # Note: This does not support testing environment (INT)
538
800
  def self.get_install_url(owner_type, owner_name, app_name)
539
801
  owner_path = owner_type == "user" ? "users/#{owner_name}" : "orgs/#{owner_name}"
802
+ if ENV['APPCENTER_ENV']&.upcase == 'INT'
803
+ return "https://install.portal-server-core-integration.dev.avalanch.es/#{owner_path}/apps/#{app_name}"
804
+ end
805
+
540
806
  return "https://install.appcenter.ms/#{owner_path}/apps/#{app_name}"
541
807
  end
808
+
809
+ # add new created app to existing distribution group
810
+ def self.add_new_app_to_distribution_group(api_token:, owner_name:, app_name:, destination_name:)
811
+ url = URI.escape("/v0.1/orgs/#{owner_name}/distribution_groups/#{destination_name}/apps")
812
+ body = {
813
+ apps: [
814
+ { name: app_name }
815
+ ]
816
+ }
817
+
818
+ UI.message("DEBUG: POST #{url}") if ENV['DEBUG']
819
+ UI.message("DEBUG: POST body #{JSON.pretty_generate(body)}\n") if ENV['DEBUG']
820
+
821
+ response = connection.post(url) do |req|
822
+ req.headers['X-API-Token'] = api_token
823
+ req.headers['internal-request-source'] = "fastlane"
824
+ req.body = body
825
+ end
826
+
827
+ UI.message("DEBUG: #{response.status} #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
828
+
829
+ case response.status
830
+ when 200...300
831
+ created = response.body
832
+ UI.success("Added new app #{app_name} to distribution group #{destination_name}")
833
+ when 401
834
+ UI.user_error!("Auth Error, provided invalid token")
835
+ when 404
836
+ UI.error("Not found, invalid distribution group name #{destination_name}")
837
+ when 409
838
+ UI.success("App already added to distribution group #{destination_name}")
839
+ else
840
+ UI.error("Error adding app to distribution group #{response.status}: #{response.body}")
841
+ end
842
+ end
542
843
  end
543
844
  end
544
845
  end