fastlane-plugin-appcenter 1.6.0 → 1.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +32 -6
- data/lib/fastlane/plugin/appcenter/actions/appcenter_fetch_version_number.rb +84 -0
- data/lib/fastlane/plugin/appcenter/actions/appcenter_upload_action.rb +142 -31
- data/lib/fastlane/plugin/appcenter/helper/appcenter_helper.rb +396 -95
- data/lib/fastlane/plugin/appcenter/version.rb +1 -1
- metadata +18 -3
@@ -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
|
-
|
10
|
-
|
11
|
-
|
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
|
-
|
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 ||
|
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
|
-
|
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
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
177
|
-
|
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("#{
|
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 #{
|
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("#{
|
238
|
+
UI.error("#{log_type} upload aborted")
|
190
239
|
false
|
191
240
|
end
|
192
241
|
end
|
193
242
|
|
194
|
-
#
|
195
|
-
#
|
196
|
-
#
|
197
|
-
def self.
|
198
|
-
connection = self.connection(
|
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
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
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
|
-
|
214
|
-
|
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
|
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
|
-
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
349
|
-
|
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(
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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(
|
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
|
-
|
707
|
+
url = "/v0.1/apps/#{owner_name}/#{app_name}/distribution_groups"
|
483
708
|
|
484
|
-
|
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
|
-
|
736
|
+
url = "/v0.1/apps/#{owner_name}/#{app_name}/distribution_groups/#{ERB::Util.url_encode(distribution_group)}/devices/download_devices_list"
|
509
737
|
|
510
|
-
|
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
|
-
|
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
|