fastlane-plugin-appcenter 0.1.7 → 0.2.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
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b7aee7355c0b29ee7bf79054cf8a1036b59ccf37adb847b9dc0dc62cf421f25f
|
4
|
+
data.tar.gz: bb7c314a2e16662fd9aef3d820fb6e31e097f7ccff7807ab2d4e990b8c0d8e72
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2c24839b70a4b7366b8966a57adabf13b66e6f04e94c494f1f851d49e7ca0620cbca701c217af7990abf1f1591b16ca46950c37a0ad2ad4072c4b34d195e7e4c
|
7
|
+
data.tar.gz: 5667213b8132cc43957cd3dc5124b579104e5c3cac9eaeeb012440a3c54bfe13b3618a38646edc31f7b99debe0a2b5f2b748fa649eb2e890d3dec9866f548f5d
|
data/README.md
CHANGED
@@ -38,7 +38,11 @@ The action parameters `api_token` and `owner_name` can also be omitted when thei
|
|
38
38
|
- `APPCENTER_DISTRIBUTE_DSYM` - Path to your symbols file. For iOS provide path to app.dSYM.zip
|
39
39
|
- `APPCENTER_DISTRIBUTE_UPLOAD_DSYM_ONLY` - Flag to upload only the dSYM file to App Center
|
40
40
|
- `APPCENTER_DISTRIBUTE_GROUP` - Comma separated list of Distribution Group names
|
41
|
+
- `APPCENTER_DISTRIBUTE_MANDATORY_UPDATE` - Require users to update to this release
|
42
|
+
- `APPCENTER_DISTRIBUTE_NOTIFY_TESTERS` - Send email notification about release
|
41
43
|
- `APPCENTER_DISTRIBUTE_RELEASE_NOTES` - Release notes
|
44
|
+
- `APPCENTER_DISTRIBUTE_RELEASE_NOTES_CLIPPING` - Clip release notes if its length is more then 5000, `true` by default
|
45
|
+
- `APPCENTER_DISTRIBUTE_RELEASE_NOTES_LINK` - Additional release notes link
|
42
46
|
|
43
47
|
## Example
|
44
48
|
|
@@ -1,262 +1,10 @@
|
|
1
|
-
# rubocop:disable Metrics/ClassLength
|
2
1
|
module Fastlane
|
3
2
|
module Actions
|
4
|
-
module SharedValues
|
5
|
-
APPCENTER_DOWNLOAD_LINK = :APPCENTER_DOWNLOAD_LINK
|
6
|
-
APPCENTER_BUILD_INFORMATION = :APPCENTER_BUILD_INFORMATION
|
7
|
-
end
|
8
|
-
|
9
3
|
module Constants
|
10
4
|
MAX_RELEASE_NOTES_LENGTH = 5000
|
11
5
|
end
|
12
6
|
|
13
7
|
class AppcenterUploadAction < Action
|
14
|
-
# create request
|
15
|
-
def self.connection(upload_url = false, dsym = false)
|
16
|
-
require 'faraday'
|
17
|
-
require 'faraday_middleware'
|
18
|
-
|
19
|
-
options = {
|
20
|
-
url: upload_url ? upload_url : "https://api.appcenter.ms"
|
21
|
-
}
|
22
|
-
|
23
|
-
Faraday.new(options) do |builder|
|
24
|
-
if upload_url
|
25
|
-
builder.request :multipart unless dsym
|
26
|
-
builder.request :url_encoded unless dsym
|
27
|
-
else
|
28
|
-
builder.request :json
|
29
|
-
end
|
30
|
-
builder.response :json, content_type: /\bjson$/
|
31
|
-
builder.use FaradayMiddleware::FollowRedirects
|
32
|
-
builder.adapter :net_http
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
# creates new release upload
|
37
|
-
# returns:
|
38
|
-
# upload_id
|
39
|
-
# upload_url
|
40
|
-
def self.create_release_upload(api_token, owner_name, app_name)
|
41
|
-
connection = self.connection
|
42
|
-
|
43
|
-
response = connection.post do |req|
|
44
|
-
req.url("/v0.1/apps/#{owner_name}/#{app_name}/release_uploads")
|
45
|
-
req.headers['X-API-Token'] = api_token
|
46
|
-
req.headers['internal-request-source'] = "fastlane"
|
47
|
-
req.body = {}
|
48
|
-
end
|
49
|
-
|
50
|
-
case response.status
|
51
|
-
when 200...300
|
52
|
-
UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
|
53
|
-
response.body
|
54
|
-
when 401
|
55
|
-
UI.user_error!("Auth Error, provided invalid token")
|
56
|
-
false
|
57
|
-
when 404
|
58
|
-
UI.error("Not found, invalid owner or application name")
|
59
|
-
false
|
60
|
-
else
|
61
|
-
UI.error("Error #{response.status}: #{response.body}")
|
62
|
-
false
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
# creates new dSYM upload in appcenter
|
67
|
-
# returns:
|
68
|
-
# symbol_upload_id
|
69
|
-
# upload_url
|
70
|
-
# expiration_date
|
71
|
-
def self.create_dsym_upload(api_token, owner_name, app_name)
|
72
|
-
connection = self.connection
|
73
|
-
|
74
|
-
response = connection.post do |req|
|
75
|
-
req.url("/v0.1/apps/#{owner_name}/#{app_name}/symbol_uploads")
|
76
|
-
req.headers['X-API-Token'] = api_token
|
77
|
-
req.headers['internal-request-source'] = "fastlane"
|
78
|
-
req.body = {
|
79
|
-
symbol_type: 'Apple'
|
80
|
-
}
|
81
|
-
end
|
82
|
-
|
83
|
-
case response.status
|
84
|
-
when 200...300
|
85
|
-
UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
|
86
|
-
response.body
|
87
|
-
when 401
|
88
|
-
UI.user_error!("Auth Error, provided invalid token")
|
89
|
-
false
|
90
|
-
when 404
|
91
|
-
UI.error("Not found, invalid owner or application name")
|
92
|
-
false
|
93
|
-
else
|
94
|
-
UI.error("Error #{response.status}: #{response.body}")
|
95
|
-
false
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
# committs or aborts dsym upload
|
100
|
-
def self.update_dsym_upload(api_token, owner_name, app_name, symbol_upload_id, status)
|
101
|
-
connection = self.connection
|
102
|
-
|
103
|
-
response = connection.patch do |req|
|
104
|
-
req.url("/v0.1/apps/#{owner_name}/#{app_name}/symbol_uploads/#{symbol_upload_id}")
|
105
|
-
req.headers['X-API-Token'] = api_token
|
106
|
-
req.headers['internal-request-source'] = "fastlane"
|
107
|
-
req.body = {
|
108
|
-
"status" => status
|
109
|
-
}
|
110
|
-
end
|
111
|
-
|
112
|
-
case response.status
|
113
|
-
when 200...300
|
114
|
-
UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
|
115
|
-
response.body
|
116
|
-
else
|
117
|
-
UI.error("Error #{response.status}: #{response.body}")
|
118
|
-
false
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
# upload dSYM files to specified upload url
|
123
|
-
# if succeed, then commits the upload
|
124
|
-
# otherwise aborts
|
125
|
-
def self.upload_dsym(api_token, owner_name, app_name, dsym, symbol_upload_id, upload_url)
|
126
|
-
connection = self.connection(upload_url, true)
|
127
|
-
|
128
|
-
response = connection.put do |req|
|
129
|
-
req.headers['x-ms-blob-type'] = "BlockBlob"
|
130
|
-
req.headers['Content-Length'] = File.size(dsym).to_s
|
131
|
-
req.headers['internal-request-source'] = "fastlane"
|
132
|
-
req.body = Faraday::UploadIO.new(dsym, 'application/octet-stream') if dsym && File.exist?(dsym)
|
133
|
-
end
|
134
|
-
|
135
|
-
case response.status
|
136
|
-
when 200...300
|
137
|
-
self.update_dsym_upload(api_token, owner_name, app_name, symbol_upload_id, 'committed')
|
138
|
-
UI.success("dSYM uploaded")
|
139
|
-
else
|
140
|
-
UI.error("Error uploading dSYM #{response.status}: #{response.body}")
|
141
|
-
self.update_dsym_upload(api_token, owner_name, app_name, symbol_upload_id, 'aborted')
|
142
|
-
UI.error("dSYM upload aborted")
|
143
|
-
false
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
# upload binary for specified upload_url
|
148
|
-
# if succeed, then commits the release
|
149
|
-
# otherwise aborts
|
150
|
-
def self.upload_build(api_token, owner_name, app_name, file, upload_id, upload_url)
|
151
|
-
connection = self.connection(upload_url)
|
152
|
-
|
153
|
-
options = {}
|
154
|
-
options[:upload_id] = upload_id
|
155
|
-
# ipa field is used both for .apk and .ipa files
|
156
|
-
options[:ipa] = Faraday::UploadIO.new(file, 'application/octet-stream') if file && File.exist?(file)
|
157
|
-
|
158
|
-
response = connection.post do |req|
|
159
|
-
req.headers['internal-request-source'] = "fastlane"
|
160
|
-
req.body = options
|
161
|
-
end
|
162
|
-
|
163
|
-
case response.status
|
164
|
-
when 200...300
|
165
|
-
UI.message("Binary uploaded")
|
166
|
-
self.update_release_upload(api_token, owner_name, app_name, upload_id, 'committed')
|
167
|
-
else
|
168
|
-
UI.error("Error uploading binary #{response.status}: #{response.body}")
|
169
|
-
self.update_release_upload(api_token, owner_name, app_name, upload_id, 'aborted')
|
170
|
-
UI.error("Release aborted")
|
171
|
-
false
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
# Commits or aborts the upload process for a release
|
176
|
-
def self.update_release_upload(api_token, owner_name, app_name, upload_id, status)
|
177
|
-
connection = self.connection
|
178
|
-
|
179
|
-
response = connection.patch do |req|
|
180
|
-
req.url("/v0.1/apps/#{owner_name}/#{app_name}/release_uploads/#{upload_id}")
|
181
|
-
req.headers['X-API-Token'] = api_token
|
182
|
-
req.headers['internal-request-source'] = "fastlane"
|
183
|
-
req.body = {
|
184
|
-
"status" => status
|
185
|
-
}
|
186
|
-
end
|
187
|
-
|
188
|
-
case response.status
|
189
|
-
when 200...300
|
190
|
-
UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
|
191
|
-
response.body
|
192
|
-
else
|
193
|
-
UI.error("Error #{response.status}: #{response.body}")
|
194
|
-
false
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
|
-
# get existing release
|
199
|
-
def self.get_release(api_token, release_url)
|
200
|
-
connection = self.connection
|
201
|
-
response = connection.get do |req|
|
202
|
-
req.url("/#{release_url}")
|
203
|
-
req.headers['X-API-Token'] = api_token
|
204
|
-
req.headers['internal-request-source'] = "fastlane"
|
205
|
-
end
|
206
|
-
|
207
|
-
case response.status
|
208
|
-
when 200...300
|
209
|
-
release = response.body
|
210
|
-
UI.message("DEBUG: #{JSON.pretty_generate(release)}") if ENV['DEBUG']
|
211
|
-
release
|
212
|
-
when 404
|
213
|
-
UI.error("Not found, invalid release url")
|
214
|
-
false
|
215
|
-
else
|
216
|
-
UI.error("Error fetching information about release #{response.status}: #{response.body}")
|
217
|
-
false
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
# add release to distribution group
|
222
|
-
def self.add_to_group(api_token, release_url, group_name, release_notes = '')
|
223
|
-
connection = self.connection
|
224
|
-
|
225
|
-
response = connection.patch do |req|
|
226
|
-
req.url("/#{release_url}")
|
227
|
-
req.headers['X-API-Token'] = api_token
|
228
|
-
req.headers['internal-request-source'] = "fastlane"
|
229
|
-
req.body = {
|
230
|
-
"distribution_group_name" => group_name,
|
231
|
-
"release_notes" => release_notes
|
232
|
-
}
|
233
|
-
end
|
234
|
-
|
235
|
-
case response.status
|
236
|
-
when 200...300
|
237
|
-
# get full release info
|
238
|
-
release = self.get_release(api_token, release_url)
|
239
|
-
return false unless release
|
240
|
-
download_url = release['download_url']
|
241
|
-
|
242
|
-
UI.message("DEBUG: #{JSON.pretty_generate(release)}") if ENV['DEBUG']
|
243
|
-
|
244
|
-
Actions.lane_context[SharedValues::APPCENTER_DOWNLOAD_LINK] = download_url
|
245
|
-
Actions.lane_context[SharedValues::APPCENTER_BUILD_INFORMATION] = release
|
246
|
-
|
247
|
-
UI.message("Public Download URL: #{download_url}") if download_url
|
248
|
-
UI.success("Release #{release['short_version']} was successfully distributed to group \"#{group_name}\"")
|
249
|
-
|
250
|
-
release
|
251
|
-
when 404
|
252
|
-
UI.error("Not found, invalid distribution group name")
|
253
|
-
false
|
254
|
-
else
|
255
|
-
UI.error("Error adding to group #{response.status}: #{response.body}")
|
256
|
-
false
|
257
|
-
end
|
258
|
-
end
|
259
|
-
|
260
8
|
# run whole upload process for dSYM files
|
261
9
|
def self.run_dsym_upload(params)
|
262
10
|
values = params.values
|
@@ -289,14 +37,14 @@ module Fastlane
|
|
289
37
|
values[:dsym_path] = dsym_path
|
290
38
|
|
291
39
|
UI.message("Starting dSYM upload...")
|
292
|
-
dsym_upload_details =
|
40
|
+
dsym_upload_details = Helper::AppcenterHelper.create_dsym_upload(api_token, owner_name, app_name)
|
293
41
|
|
294
42
|
if dsym_upload_details
|
295
43
|
symbol_upload_id = dsym_upload_details['symbol_upload_id']
|
296
44
|
upload_url = dsym_upload_details['upload_url']
|
297
45
|
|
298
46
|
UI.message("Uploading dSYM...")
|
299
|
-
|
47
|
+
Helper::AppcenterHelper.upload_dsym(api_token, owner_name, app_name, dsym_path, symbol_upload_id, upload_url)
|
300
48
|
end
|
301
49
|
end
|
302
50
|
end
|
@@ -308,6 +56,8 @@ module Fastlane
|
|
308
56
|
owner_name = params[:owner_name]
|
309
57
|
app_name = params[:app_name]
|
310
58
|
group = params[:group]
|
59
|
+
mandatory_update = params[:mandatory_update]
|
60
|
+
notify_testers = params[:notify_testers]
|
311
61
|
release_notes = params[:release_notes]
|
312
62
|
should_clip = params[:should_clip]
|
313
63
|
release_notes_link = params[:release_notes_link]
|
@@ -333,48 +83,39 @@ module Fastlane
|
|
333
83
|
UI.user_error!("No Distribute Group given, pass using `group: 'group name'`") unless group && !group.empty?
|
334
84
|
|
335
85
|
UI.message("Starting release upload...")
|
336
|
-
upload_details =
|
86
|
+
upload_details = Helper::AppcenterHelper.create_release_upload(api_token, owner_name, app_name)
|
337
87
|
if upload_details
|
338
88
|
upload_id = upload_details['upload_id']
|
339
89
|
upload_url = upload_details['upload_url']
|
340
90
|
|
341
91
|
UI.message("Uploading release binary...")
|
342
|
-
uploaded =
|
92
|
+
uploaded = Helper::AppcenterHelper.upload_build(api_token, owner_name, app_name, file, upload_id, upload_url)
|
343
93
|
|
344
94
|
if uploaded
|
345
|
-
|
346
|
-
UI.message("Release committed")
|
95
|
+
release_id = uploaded['release_id']
|
96
|
+
UI.message("Release '#{release_id}' committed")
|
97
|
+
|
98
|
+
Helper::AppcenterHelper.update_release(api_token, owner_name, app_name, release_id, release_notes)
|
99
|
+
|
347
100
|
groups = group.split(',')
|
348
101
|
groups.each do |group_name|
|
349
|
-
|
102
|
+
group = Helper::AppcenterHelper.get_group(api_token, owner_name, app_name, group_name)
|
103
|
+
if group
|
104
|
+
group_id = group['id']
|
105
|
+
distributed_release = Helper::AppcenterHelper.add_to_group(api_token, owner_name, app_name, release_id, group_id, mandatory_update, notify_testers)
|
106
|
+
if distributed_release
|
107
|
+
UI.success("Release #{distributed_release['short_version']} was successfully distributed to group \"#{group_name}\"")
|
108
|
+
else
|
109
|
+
UI.error("Release '#{release_id}' was not found")
|
110
|
+
end
|
111
|
+
else
|
112
|
+
UI.error("Group '#{group_name}' was not found")
|
113
|
+
end
|
350
114
|
end
|
351
115
|
end
|
352
116
|
end
|
353
117
|
end
|
354
118
|
|
355
|
-
# returns true if app exists, false in case of 404 and error otherwise
|
356
|
-
def self.get_app(api_token, owner_name, app_name)
|
357
|
-
connection = self.connection
|
358
|
-
|
359
|
-
response = connection.get do |req|
|
360
|
-
req.url("/v0.1/apps/#{owner_name}/#{app_name}")
|
361
|
-
req.headers['X-API-Token'] = api_token
|
362
|
-
req.headers['internal-request-source'] = "fastlane"
|
363
|
-
end
|
364
|
-
|
365
|
-
case response.status
|
366
|
-
when 200...300
|
367
|
-
UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
|
368
|
-
true
|
369
|
-
when 404
|
370
|
-
UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
|
371
|
-
false
|
372
|
-
else
|
373
|
-
UI.error("Error #{response.status}: #{response.body}")
|
374
|
-
false
|
375
|
-
end
|
376
|
-
end
|
377
|
-
|
378
119
|
# checks app existance, if ther is no such - creates it
|
379
120
|
def self.get_or_create_app(params)
|
380
121
|
api_token = params[:api_token]
|
@@ -386,37 +127,14 @@ module Fastlane
|
|
386
127
|
"iOS" => ['Objective-C-Swift', 'React-Native', 'Xamarin']
|
387
128
|
}
|
388
129
|
|
389
|
-
if
|
130
|
+
if Helper::AppcenterHelper.get_app(api_token, owner_name, app_name)
|
390
131
|
true
|
391
132
|
else
|
392
133
|
if Helper.test? || UI.confirm("App with name #{app_name} not found, create one?")
|
393
|
-
connection = self.connection
|
394
|
-
|
395
134
|
os = Helper.test? ? "Android" : UI.select("Select OS", ["Android", "iOS"])
|
396
135
|
platform = Helper.test? ? "Java" : UI.select("Select Platform", platforms[os])
|
397
136
|
|
398
|
-
|
399
|
-
req.url("/v0.1/apps")
|
400
|
-
req.headers['X-API-Token'] = api_token
|
401
|
-
req.headers['internal-request-source'] = "fastlane"
|
402
|
-
req.body = {
|
403
|
-
"display_name" => app_name,
|
404
|
-
"name" => app_name,
|
405
|
-
"os" => os,
|
406
|
-
"platform" => platform
|
407
|
-
}
|
408
|
-
end
|
409
|
-
|
410
|
-
case response.status
|
411
|
-
when 200...300
|
412
|
-
created = response.body
|
413
|
-
UI.message("DEBUG: #{JSON.pretty_generate(created)}") if ENV['DEBUG']
|
414
|
-
UI.success("Created #{os}/#{platform} app with name \"#{created['name']}\"")
|
415
|
-
true
|
416
|
-
else
|
417
|
-
UI.error("Error creating app #{response.status}: #{response.body}")
|
418
|
-
false
|
419
|
-
end
|
137
|
+
Helper::AppcenterHelper.create_app(api_token, owner_name, app_name, os, platform)
|
420
138
|
else
|
421
139
|
UI.error("Lane aborted")
|
422
140
|
false
|
@@ -446,9 +164,7 @@ module Fastlane
|
|
446
164
|
end
|
447
165
|
|
448
166
|
def self.details
|
449
|
-
|
450
|
-
"Symbols will also be uploaded automatically if a `app.dSYM.zip` file is found next to `app.ipa`. In case it is located in a different place you can specify the path explicitly in `:dsym` parameter."
|
451
|
-
]
|
167
|
+
"Symbols will also be uploaded automatically if a `app.dSYM.zip` file is found next to `app.ipa`. In case it is located in a different place you can specify the path explicitly in `:dsym` parameter."
|
452
168
|
end
|
453
169
|
|
454
170
|
def self.available_options
|
@@ -536,6 +252,20 @@ module Fastlane
|
|
536
252
|
optional: true,
|
537
253
|
type: String),
|
538
254
|
|
255
|
+
FastlaneCore::ConfigItem.new(key: :mandatory_update,
|
256
|
+
env_name: "APPCENTER_DISTRIBUTE_MANDATORY_UPDATE",
|
257
|
+
description: "Require users to update to this release",
|
258
|
+
optional: true,
|
259
|
+
is_string: false,
|
260
|
+
default_value: false),
|
261
|
+
|
262
|
+
FastlaneCore::ConfigItem.new(key: :notify_testers,
|
263
|
+
env_name: "APPCENTER_DISTRIBUTE_NOTIFY_TESTERS",
|
264
|
+
description: "Send email notification about release",
|
265
|
+
optional: true,
|
266
|
+
is_string: false,
|
267
|
+
default_value: false),
|
268
|
+
|
539
269
|
FastlaneCore::ConfigItem.new(key: :release_notes,
|
540
270
|
env_name: "APPCENTER_DISTRIBUTE_RELEASE_NOTES",
|
541
271
|
description: "Release notes",
|
@@ -545,7 +275,7 @@ module Fastlane
|
|
545
275
|
|
546
276
|
FastlaneCore::ConfigItem.new(key: :should_clip,
|
547
277
|
env_name: "APPCENTER_DISTRIBUTE_RELEASE_NOTES_CLIPPING",
|
548
|
-
description: "Clip release notes if its
|
278
|
+
description: "Clip release notes if its length is more then #{Constants::MAX_RELEASE_NOTES_LENGTH}, true by default",
|
549
279
|
optional: true,
|
550
280
|
is_string: false,
|
551
281
|
default_value: true),
|
@@ -593,4 +323,3 @@ module Fastlane
|
|
593
323
|
end
|
594
324
|
end
|
595
325
|
end
|
596
|
-
# rubocop:enable Metrics/ClassLength
|
@@ -1,11 +1,370 @@
|
|
1
1
|
module Fastlane
|
2
2
|
module Helper
|
3
|
+
module SharedValues
|
4
|
+
APPCENTER_DOWNLOAD_LINK = :APPCENTER_DOWNLOAD_LINK
|
5
|
+
APPCENTER_BUILD_INFORMATION = :APPCENTER_BUILD_INFORMATION
|
6
|
+
end
|
7
|
+
|
3
8
|
class AppcenterHelper
|
4
|
-
|
5
|
-
#
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
+
|
10
|
+
# create request
|
11
|
+
def self.connection(upload_url = false, dsym = false)
|
12
|
+
require 'faraday'
|
13
|
+
require 'faraday_middleware'
|
14
|
+
|
15
|
+
options = {
|
16
|
+
url: upload_url ? upload_url : "https://api.appcenter.ms"
|
17
|
+
}
|
18
|
+
|
19
|
+
Faraday.new(options) do |builder|
|
20
|
+
if upload_url
|
21
|
+
builder.request :multipart unless dsym
|
22
|
+
builder.request :url_encoded unless dsym
|
23
|
+
else
|
24
|
+
builder.request :json
|
25
|
+
end
|
26
|
+
builder.response :json, content_type: /\bjson$/
|
27
|
+
builder.use FaradayMiddleware::FollowRedirects
|
28
|
+
builder.adapter :net_http
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# creates new release upload
|
33
|
+
# returns:
|
34
|
+
# upload_id
|
35
|
+
# upload_url
|
36
|
+
def self.create_release_upload(api_token, owner_name, app_name)
|
37
|
+
connection = self.connection
|
38
|
+
|
39
|
+
response = connection.post do |req|
|
40
|
+
req.url("/v0.1/apps/#{owner_name}/#{app_name}/release_uploads")
|
41
|
+
req.headers['X-API-Token'] = api_token
|
42
|
+
req.headers['internal-request-source'] = "fastlane"
|
43
|
+
req.body = {}
|
44
|
+
end
|
45
|
+
|
46
|
+
case response.status
|
47
|
+
when 200...300
|
48
|
+
UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
|
49
|
+
response.body
|
50
|
+
when 401
|
51
|
+
UI.user_error!("Auth Error, provided invalid token")
|
52
|
+
false
|
53
|
+
when 404
|
54
|
+
UI.error("Not found, invalid owner or application name")
|
55
|
+
false
|
56
|
+
else
|
57
|
+
UI.error("Error #{response.status}: #{response.body}")
|
58
|
+
false
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# creates new dSYM upload in appcenter
|
63
|
+
# returns:
|
64
|
+
# symbol_upload_id
|
65
|
+
# upload_url
|
66
|
+
# expiration_date
|
67
|
+
def self.create_dsym_upload(api_token, owner_name, app_name)
|
68
|
+
connection = self.connection
|
69
|
+
|
70
|
+
response = connection.post do |req|
|
71
|
+
req.url("/v0.1/apps/#{owner_name}/#{app_name}/symbol_uploads")
|
72
|
+
req.headers['X-API-Token'] = api_token
|
73
|
+
req.headers['internal-request-source'] = "fastlane"
|
74
|
+
req.body = {
|
75
|
+
symbol_type: 'Apple'
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
case response.status
|
80
|
+
when 200...300
|
81
|
+
UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
|
82
|
+
response.body
|
83
|
+
when 401
|
84
|
+
UI.user_error!("Auth Error, provided invalid token")
|
85
|
+
false
|
86
|
+
when 404
|
87
|
+
UI.error("Not found, invalid owner or application name")
|
88
|
+
false
|
89
|
+
else
|
90
|
+
UI.error("Error #{response.status}: #{response.body}")
|
91
|
+
false
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# committs or aborts dsym upload
|
96
|
+
def self.update_dsym_upload(api_token, owner_name, app_name, symbol_upload_id, status)
|
97
|
+
connection = self.connection
|
98
|
+
|
99
|
+
response = connection.patch do |req|
|
100
|
+
req.url("/v0.1/apps/#{owner_name}/#{app_name}/symbol_uploads/#{symbol_upload_id}")
|
101
|
+
req.headers['X-API-Token'] = api_token
|
102
|
+
req.headers['internal-request-source'] = "fastlane"
|
103
|
+
req.body = {
|
104
|
+
"status" => status
|
105
|
+
}
|
106
|
+
end
|
107
|
+
|
108
|
+
case response.status
|
109
|
+
when 200...300
|
110
|
+
UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
|
111
|
+
response.body
|
112
|
+
else
|
113
|
+
UI.error("Error #{response.status}: #{response.body}")
|
114
|
+
false
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# upload dSYM files to specified upload url
|
119
|
+
# if succeed, then commits the upload
|
120
|
+
# otherwise aborts
|
121
|
+
def self.upload_dsym(api_token, owner_name, app_name, dsym, symbol_upload_id, upload_url)
|
122
|
+
connection = self.connection(upload_url, true)
|
123
|
+
|
124
|
+
response = connection.put do |req|
|
125
|
+
req.headers['x-ms-blob-type'] = "BlockBlob"
|
126
|
+
req.headers['Content-Length'] = File.size(dsym).to_s
|
127
|
+
req.headers['internal-request-source'] = "fastlane"
|
128
|
+
req.body = Faraday::UploadIO.new(dsym, 'application/octet-stream') if dsym && File.exist?(dsym)
|
129
|
+
end
|
130
|
+
|
131
|
+
case response.status
|
132
|
+
when 200...300
|
133
|
+
self.update_dsym_upload(api_token, owner_name, app_name, symbol_upload_id, 'committed')
|
134
|
+
UI.success("dSYM uploaded")
|
135
|
+
else
|
136
|
+
UI.error("Error uploading dSYM #{response.status}: #{response.body}")
|
137
|
+
self.update_dsym_upload(api_token, owner_name, app_name, symbol_upload_id, 'aborted')
|
138
|
+
UI.error("dSYM upload aborted")
|
139
|
+
false
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# upload binary for specified upload_url
|
144
|
+
# if succeed, then commits the release
|
145
|
+
# otherwise aborts
|
146
|
+
def self.upload_build(api_token, owner_name, app_name, file, upload_id, upload_url)
|
147
|
+
connection = self.connection(upload_url)
|
148
|
+
|
149
|
+
options = {}
|
150
|
+
options[:upload_id] = upload_id
|
151
|
+
# ipa field is used both for .apk and .ipa files
|
152
|
+
options[:ipa] = Faraday::UploadIO.new(file, 'application/octet-stream') if file && File.exist?(file)
|
153
|
+
|
154
|
+
response = connection.post do |req|
|
155
|
+
req.headers['internal-request-source'] = "fastlane"
|
156
|
+
req.body = options
|
157
|
+
end
|
158
|
+
|
159
|
+
case response.status
|
160
|
+
when 200...300
|
161
|
+
UI.message("Binary uploaded")
|
162
|
+
self.update_release_upload(api_token, owner_name, app_name, upload_id, 'committed')
|
163
|
+
else
|
164
|
+
UI.error("Error uploading binary #{response.status}: #{response.body}")
|
165
|
+
self.update_release_upload(api_token, owner_name, app_name, upload_id, 'aborted')
|
166
|
+
UI.error("Release aborted")
|
167
|
+
false
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# Commits or aborts the upload process for a release
|
172
|
+
def self.update_release_upload(api_token, owner_name, app_name, upload_id, status)
|
173
|
+
connection = self.connection
|
174
|
+
|
175
|
+
response = connection.patch do |req|
|
176
|
+
req.url("/v0.1/apps/#{owner_name}/#{app_name}/release_uploads/#{upload_id}")
|
177
|
+
req.headers['X-API-Token'] = api_token
|
178
|
+
req.headers['internal-request-source'] = "fastlane"
|
179
|
+
req.body = {
|
180
|
+
"status" => status
|
181
|
+
}
|
182
|
+
end
|
183
|
+
|
184
|
+
case response.status
|
185
|
+
when 200...300
|
186
|
+
UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
|
187
|
+
response.body
|
188
|
+
else
|
189
|
+
UI.error("Error #{response.status}: #{response.body}")
|
190
|
+
false
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# get existing release
|
195
|
+
def self.get_release(api_token, owner_name, app_name, release_id)
|
196
|
+
connection = self.connection
|
197
|
+
response = connection.get do |req|
|
198
|
+
req.url("/v0.1/apps/#{owner_name}/#{app_name}/releases/#{release_id}")
|
199
|
+
req.headers['X-API-Token'] = api_token
|
200
|
+
req.headers['internal-request-source'] = "fastlane"
|
201
|
+
end
|
202
|
+
|
203
|
+
case response.status
|
204
|
+
when 200...300
|
205
|
+
release = response.body
|
206
|
+
UI.message("DEBUG: #{JSON.pretty_generate(release)}") if ENV['DEBUG']
|
207
|
+
release
|
208
|
+
when 404
|
209
|
+
UI.error("Not found, invalid release url")
|
210
|
+
false
|
211
|
+
else
|
212
|
+
UI.error("Error fetching information about release #{response.status}: #{response.body}")
|
213
|
+
false
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
# get distribution group
|
218
|
+
def self.get_group(api_token, owner_name, app_name, group_name)
|
219
|
+
connection = self.connection
|
220
|
+
|
221
|
+
response = connection.get do |req|
|
222
|
+
req.url("/v0.1/apps/#{owner_name}/#{app_name}/distribution_groups/#{group_name}")
|
223
|
+
req.headers['X-API-Token'] = api_token
|
224
|
+
req.headers['internal-request-source'] = "fastlane"
|
225
|
+
end
|
226
|
+
|
227
|
+
case response.status
|
228
|
+
when 200...300
|
229
|
+
group = response.body
|
230
|
+
UI.message("DEBUG: received group #{JSON.pretty_generate(group)}") if ENV['DEBUG']
|
231
|
+
group
|
232
|
+
when 404
|
233
|
+
UI.error("Not found, invalid distribution group name")
|
234
|
+
false
|
235
|
+
else
|
236
|
+
UI.error("Error getting group #{response.status}: #{response.body}")
|
237
|
+
false
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
# add release to destination
|
242
|
+
def self.update_release(api_token, owner_name, app_name, release_id, release_notes = '')
|
243
|
+
connection = self.connection
|
244
|
+
|
245
|
+
response = connection.put do |req|
|
246
|
+
req.url("/v0.1/apps/#{owner_name}/#{app_name}/releases/#{release_id}")
|
247
|
+
req.headers['X-API-Token'] = api_token
|
248
|
+
req.headers['internal-request-source'] = "fastlane"
|
249
|
+
req.body = {
|
250
|
+
"release_notes" => release_notes
|
251
|
+
}
|
252
|
+
end
|
253
|
+
|
254
|
+
case response.status
|
255
|
+
when 200...300
|
256
|
+
# get full release info
|
257
|
+
release = self.get_release(api_token, owner_name, app_name, release_id)
|
258
|
+
return false unless release
|
259
|
+
download_url = release['download_url']
|
260
|
+
|
261
|
+
UI.message("DEBUG: #{JSON.pretty_generate(release)}") if ENV['DEBUG']
|
262
|
+
|
263
|
+
Actions.lane_context[SharedValues::APPCENTER_DOWNLOAD_LINK] = download_url
|
264
|
+
Actions.lane_context[SharedValues::APPCENTER_BUILD_INFORMATION] = release
|
265
|
+
|
266
|
+
UI.message("Release #{release['short_version']} was successfully updated")
|
267
|
+
|
268
|
+
release
|
269
|
+
when 404
|
270
|
+
UI.error("Not found, invalid release id")
|
271
|
+
false
|
272
|
+
else
|
273
|
+
UI.error("Error adding updating release #{response.status}: #{response.body}")
|
274
|
+
false
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
# add release to distribution group
|
279
|
+
def self.add_to_group(api_token, owner_name, app_name, release_id, group_id, mandatory_update = false, notify_testers = false)
|
280
|
+
connection = self.connection
|
281
|
+
|
282
|
+
UI.message("DEBUG: getting #{release_id}") if ENV['DEBUG']
|
283
|
+
|
284
|
+
response = connection.post do |req|
|
285
|
+
req.url("/v0.1/apps/#{owner_name}/#{app_name}/releases/#{release_id}/groups")
|
286
|
+
req.headers['X-API-Token'] = api_token
|
287
|
+
req.headers['internal-request-source'] = "fastlane"
|
288
|
+
req.body = {
|
289
|
+
"id" => group_id,
|
290
|
+
"mandatory_update" => mandatory_update,
|
291
|
+
"notify_testers" => notify_testers
|
292
|
+
}
|
293
|
+
end
|
294
|
+
|
295
|
+
case response.status
|
296
|
+
when 200...300
|
297
|
+
# get full release info
|
298
|
+
release = self.get_release(api_token, owner_name, app_name, release_id)
|
299
|
+
return false unless release
|
300
|
+
download_url = release['download_url']
|
301
|
+
|
302
|
+
UI.message("DEBUG: received release #{JSON.pretty_generate(release)}") if ENV['DEBUG']
|
303
|
+
|
304
|
+
Actions.lane_context[SharedValues::APPCENTER_DOWNLOAD_LINK] = download_url
|
305
|
+
Actions.lane_context[SharedValues::APPCENTER_BUILD_INFORMATION] = release
|
306
|
+
|
307
|
+
UI.message("Public Download URL: #{download_url}") if download_url
|
308
|
+
|
309
|
+
release
|
310
|
+
when 404
|
311
|
+
UI.error("Not found, invalid distribution group name")
|
312
|
+
false
|
313
|
+
else
|
314
|
+
UI.error("Error adding to group #{response.status}: #{response.body}")
|
315
|
+
false
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
# returns true if app exists, false in case of 404 and error otherwise
|
320
|
+
def self.get_app(api_token, owner_name, app_name)
|
321
|
+
connection = self.connection
|
322
|
+
|
323
|
+
response = connection.get do |req|
|
324
|
+
req.url("/v0.1/apps/#{owner_name}/#{app_name}")
|
325
|
+
req.headers['X-API-Token'] = api_token
|
326
|
+
req.headers['internal-request-source'] = "fastlane"
|
327
|
+
end
|
328
|
+
|
329
|
+
case response.status
|
330
|
+
when 200...300
|
331
|
+
UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
|
332
|
+
true
|
333
|
+
when 404
|
334
|
+
UI.message("DEBUG: #{JSON.pretty_generate(response.body)}\n") if ENV['DEBUG']
|
335
|
+
false
|
336
|
+
else
|
337
|
+
UI.error("Error #{response.status}: #{response.body}")
|
338
|
+
false
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
# returns true if app exists, false in case of 404 and error otherwise
|
343
|
+
def self.create_app(api_token, owner_name, app_name, os, platform)
|
344
|
+
connection = self.connection
|
345
|
+
|
346
|
+
response = connection.post do |req|
|
347
|
+
req.url("/v0.1/apps")
|
348
|
+
req.headers['X-API-Token'] = api_token
|
349
|
+
req.headers['internal-request-source'] = "fastlane"
|
350
|
+
req.body = {
|
351
|
+
"display_name" => app_name,
|
352
|
+
"name" => app_name,
|
353
|
+
"os" => os,
|
354
|
+
"platform" => platform
|
355
|
+
}
|
356
|
+
end
|
357
|
+
|
358
|
+
case response.status
|
359
|
+
when 200...300
|
360
|
+
created = response.body
|
361
|
+
UI.message("DEBUG: #{JSON.pretty_generate(created)}") if ENV['DEBUG']
|
362
|
+
UI.success("Created #{os}/#{platform} app with name \"#{created['name']}\"")
|
363
|
+
true
|
364
|
+
else
|
365
|
+
UI.error("Error creating app #{response.status}: #{response.body}")
|
366
|
+
false
|
367
|
+
end
|
9
368
|
end
|
10
369
|
end
|
11
370
|
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: 0.
|
4
|
+
version: 0.2.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:
|
11
|
+
date: 2019-04-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -126,7 +126,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
126
126
|
version: '0'
|
127
127
|
requirements: []
|
128
128
|
rubyforge_project:
|
129
|
-
rubygems_version: 2.6
|
129
|
+
rubygems_version: 2.7.6
|
130
130
|
signing_key:
|
131
131
|
specification_version: 4
|
132
132
|
summary: Fastlane plugin for App Center
|