fastlane-plugin-firebase_app_distribution 0.7.3 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_action.rb +235 -55
- data/lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_add_testers_action.rb +8 -2
- data/lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_create_group_action.rb +8 -5
- data/lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_delete_group_action.rb +8 -2
- data/lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_get_latest_release.rb +7 -1
- data/lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_get_udids.rb +30 -9
- data/lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_remove_testers_action.rb +8 -2
- data/lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_auth_client.rb +34 -17
- data/lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_error_message.rb +15 -15
- data/lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_helper.rb +9 -8
- data/lib/fastlane/plugin/firebase_app_distribution/version.rb +1 -1
- metadata +17 -4
- data/lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb +0 -97
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1ed146d48ff09a46b608e01e483f207ce62a2809d9a8ded675c56d0b22802ba7
|
4
|
+
data.tar.gz: 18b893dc6b1a79a339fb7aeb8c432c13bd419d04df78dc4c5606dbfd24db64a6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: afa135119953bc3ee405aa868e1f5012b0e1dc9ac1240b02a895f437125b142b5e58a9930e4dc137fcfac92e3711db9ac364d07c98f33e61a6de652e878bdf46
|
7
|
+
data.tar.gz: c263365d7d45466c43486fb78548c7b0191b3f502e4e207f83b4674180badb457cb35e0f14e927f3a7c82841b6544ff5d44e0e05f99837f6ae45331dda9dbd92
|
data/lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_action.rb
CHANGED
@@ -2,6 +2,7 @@ require 'fastlane/action'
|
|
2
2
|
require 'open3'
|
3
3
|
require 'shellwords'
|
4
4
|
require 'googleauth'
|
5
|
+
require 'google/apis/firebaseappdistribution_v1'
|
5
6
|
require_relative '../helper/firebase_app_distribution_helper'
|
6
7
|
require_relative '../helper/firebase_app_distribution_error_message'
|
7
8
|
require_relative '../helper/firebase_app_distribution_auth_client'
|
@@ -19,8 +20,10 @@ module Fastlane
|
|
19
20
|
extend Helper::FirebaseAppDistributionHelper
|
20
21
|
|
21
22
|
DEFAULT_UPLOAD_TIMEOUT_SECONDS = 300
|
22
|
-
|
23
|
-
|
23
|
+
UPLOAD_MAX_POLLING_RETRIES = 60
|
24
|
+
UPLOAD_POLLING_INTERVAL_SECONDS = 5
|
25
|
+
TEST_MAX_POLLING_RETRIES = 40
|
26
|
+
TEST_POLLING_INTERVAL_SECONDS = 30
|
24
27
|
|
25
28
|
def self.run(params)
|
26
29
|
params.values # to validate all inputs before looking for the ipa/apk/aab
|
@@ -31,40 +34,31 @@ module Fastlane
|
|
31
34
|
timeout = get_upload_timeout(params)
|
32
35
|
|
33
36
|
binary_path = get_binary_path(platform, params)
|
34
|
-
UI.user_error!("Couldn't find binary") if binary_path.nil?
|
35
|
-
UI.user_error!("Couldn't find binary at path #{binary_path}") unless File.exist?(binary_path)
|
37
|
+
UI.user_error!("Couldn't find binary.") if binary_path.nil?
|
38
|
+
UI.user_error!("Couldn't find binary at path #{binary_path}.") unless File.exist?(binary_path)
|
36
39
|
binary_type = binary_type_from_path(binary_path)
|
37
40
|
|
38
41
|
# TODO(lkellogg): This sets the send timeout for all POST requests made by the client, but
|
39
|
-
#
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
42
|
+
# ideally the timeout should only apply to the binary upload
|
43
|
+
init_google_api_client(params[:debug], timeout)
|
44
|
+
authorization = get_authorization(params[:service_credentials_file], params[:firebase_cli_token], params[:service_credentials_json_data], params[:debug])
|
45
|
+
client = Google::Apis::FirebaseappdistributionV1::FirebaseAppDistributionService.new
|
46
|
+
client.authorization = authorization
|
47
|
+
alpha_client = Google::Apis::FirebaseappdistributionV1alpha::FirebaseAppDistributionService.new
|
48
|
+
alpha_client.authorization = authorization
|
49
|
+
|
50
|
+
# If binary is an AAB, get the AAB info for this app, which includes the integration state
|
51
|
+
# and certificate data
|
46
52
|
if binary_type == :AAB
|
47
53
|
aab_info = get_aab_info(client, app_name)
|
48
54
|
validate_aab_setup!(aab_info)
|
49
55
|
end
|
50
56
|
|
51
57
|
binary_type = binary_type_from_path(binary_path)
|
52
|
-
UI.message("
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
# (https://github.com/googleapis/google-api-ruby-client/blob/main/generated/google-apis-firebaseappdistribution_v1/lib/google/apis/firebaseappdistribution_v1/service.rb#L79).
|
57
|
-
# We could use client.http, but is much slower
|
58
|
-
# (https://github.com/firebase/fastlane-plugin-firebase_app_distribution/issues/330),
|
59
|
-
# so we still use the old client for now.
|
60
|
-
# TODO(kbolay) Prefer client.upload_medium, assuming it is sufficiently fast
|
61
|
-
fad_api_client = Client::FirebaseAppDistributionApiClient.new(client.authorization.access_token, params[:debug])
|
62
|
-
operation_name = fad_api_client.upload_binary(app_name,
|
63
|
-
binary_path,
|
64
|
-
platform.to_s,
|
65
|
-
get_upload_timeout(params))
|
66
|
-
|
67
|
-
release = poll_upload_release_operation(client, operation_name, binary_type)
|
58
|
+
UI.message("📡 Uploading the #{binary_type}.")
|
59
|
+
operation = upload_binary(app_name, binary_path, client, timeout)
|
60
|
+
UI.message("🕵️ Validating upload…")
|
61
|
+
release = poll_upload_release_operation(client, operation, binary_type)
|
68
62
|
|
69
63
|
if binary_type == :AAB && aab_info && !aab_certs_included?(aab_info.test_certificate)
|
70
64
|
updated_aab_info = get_aab_info(client, app_name)
|
@@ -86,9 +80,21 @@ module Fastlane
|
|
86
80
|
release.release_notes = Google::Apis::FirebaseappdistributionV1::GoogleFirebaseAppdistroV1ReleaseNotes.new(
|
87
81
|
text: release_notes
|
88
82
|
)
|
83
|
+
UI.message("📜 Setting release notes.")
|
89
84
|
release = update_release(client, release)
|
90
85
|
end
|
91
86
|
|
87
|
+
test_devices =
|
88
|
+
get_value_from_value_or_file(params[:test_devices], params[:test_devices_file])
|
89
|
+
if present?(test_devices)
|
90
|
+
UI.message("🤖 Starting automated tests. Note: This feature is in beta.")
|
91
|
+
test_password = test_password_from_params(params)
|
92
|
+
release_test = test_release(alpha_client, release, test_devices, params[:test_username], test_password, params[:test_username_resource], params[:test_password_resource])
|
93
|
+
unless params[:test_non_blocking]
|
94
|
+
poll_test_finished(alpha_client, release_test.name)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
92
98
|
testers = get_value_from_value_or_file(params[:testers], params[:testers_file])
|
93
99
|
groups = get_value_from_value_or_file(params[:groups], params[:groups_file])
|
94
100
|
emails = string_to_array(testers)
|
@@ -98,6 +104,7 @@ module Fastlane
|
|
98
104
|
tester_emails: emails,
|
99
105
|
group_aliases: group_aliases
|
100
106
|
)
|
107
|
+
UI.message("📦 Distributing release.")
|
101
108
|
distribute_release(client, release, request)
|
102
109
|
else
|
103
110
|
UI.message("⏩ No testers or groups passed in. Skipping this step.")
|
@@ -127,6 +134,12 @@ module Fastlane
|
|
127
134
|
"Release your beta builds with Firebase App Distribution"
|
128
135
|
end
|
129
136
|
|
137
|
+
def self.test_password_from_params(params)
|
138
|
+
test_password = get_value_from_value_or_file(params[:test_password], params[:test_password_file])
|
139
|
+
# Remove trailing newline if present
|
140
|
+
test_password && test_password.sub(/\r?\n$/, "")
|
141
|
+
end
|
142
|
+
|
130
143
|
def self.app_id_from_params(params)
|
131
144
|
if params[:app]
|
132
145
|
app_id = params[:app]
|
@@ -222,25 +235,23 @@ module Fastlane
|
|
222
235
|
release_notes_param || Actions.lane_context[SharedValues::FL_CHANGELOG]
|
223
236
|
end
|
224
237
|
|
225
|
-
def self.poll_upload_release_operation(client,
|
226
|
-
|
227
|
-
|
238
|
+
def self.poll_upload_release_operation(client, operation, binary_type)
|
239
|
+
UPLOAD_MAX_POLLING_RETRIES.times do
|
240
|
+
sleep(UPLOAD_POLLING_INTERVAL_SECONDS)
|
241
|
+
operation = client.get_project_app_release_operation(operation.name)
|
228
242
|
if operation.done && operation.response && operation.response['release']
|
229
243
|
release = extract_release(operation)
|
230
|
-
|
231
|
-
|
244
|
+
case operation.response['result']
|
245
|
+
when 'RELEASE_UPDATED'
|
232
246
|
UI.success("✅ Uploaded #{binary_type} successfully; updated provisioning profile of existing release #{release_version(release)}.")
|
233
|
-
|
234
|
-
elsif result == 'RELEASE_UNMODIFIED'
|
247
|
+
when 'RELEASE_UNMODIFIED'
|
235
248
|
UI.success("✅ The same #{binary_type} was found in release #{release_version(release)} with no changes, skipping.")
|
236
|
-
break
|
237
249
|
else
|
238
250
|
UI.success("✅ Uploaded #{binary_type} successfully and created release #{release_version(release)}.")
|
239
251
|
end
|
240
252
|
break
|
241
253
|
elsif !operation.done
|
242
|
-
|
243
|
-
operation = client.get_project_app_release_operation(operation.name)
|
254
|
+
next
|
244
255
|
else
|
245
256
|
if operation.error && operation.error.message
|
246
257
|
UI.user_error!("#{ErrorMessage.upload_binary_error(binary_type)}: #{operation.error.message}")
|
@@ -257,6 +268,30 @@ module Fastlane
|
|
257
268
|
extract_release(operation)
|
258
269
|
end
|
259
270
|
|
271
|
+
def self.upload_binary(app_name, binary_path, client, timeout)
|
272
|
+
options = Google::Apis::RequestOptions.new
|
273
|
+
options.max_elapsed_time = timeout # includes retries (default = no retries)
|
274
|
+
options.header = {
|
275
|
+
'Content-Type' => 'application/octet-stream',
|
276
|
+
'X-Goog-Upload-File-Name' => CGI.escape(File.basename(binary_path)),
|
277
|
+
'X-Goog-Upload-Protocol' => 'raw'
|
278
|
+
}
|
279
|
+
|
280
|
+
# For some reason calling the client.upload_medium returns nil when
|
281
|
+
# it should return a long running operation object, so we make a
|
282
|
+
# standard http call instead and convert it to a long running object
|
283
|
+
# https://github.com/googleapis/google-api-ruby-client/blob/main/generated/google-apis-firebaseappdistribution_v1/lib/google/apis/firebaseappdistribution_v1/service.rb#L79
|
284
|
+
# TODO(kbolay): Prefer client.upload_medium
|
285
|
+
response = client.http(
|
286
|
+
:post,
|
287
|
+
"https://firebaseappdistribution.googleapis.com/upload/v1/#{app_name}/releases:upload",
|
288
|
+
body: File.open(binary_path, 'rb'),
|
289
|
+
options: options
|
290
|
+
)
|
291
|
+
|
292
|
+
Google::Apis::FirebaseappdistributionV1::GoogleLongrunningOperation.from_json(response)
|
293
|
+
end
|
294
|
+
|
260
295
|
def self.extract_release(operation)
|
261
296
|
Google::Apis::FirebaseappdistributionV1::GoogleFirebaseAppdistroV1Release.from_json(operation.response['release'].to_json)
|
262
297
|
end
|
@@ -304,6 +339,99 @@ module Fastlane
|
|
304
339
|
end
|
305
340
|
end
|
306
341
|
|
342
|
+
def self.test_release(alpha_client, release, test_devices, username = nil, password = nil, username_resource = nil, password_resource = nil)
|
343
|
+
if username_resource.nil? ^ password_resource.nil?
|
344
|
+
UI.user_error!("Username and password resource names for automated tests need to be specified together.")
|
345
|
+
end
|
346
|
+
field_hints = nil
|
347
|
+
if !username_resource.nil? && !password_resource.nil?
|
348
|
+
field_hints =
|
349
|
+
Google::Apis::FirebaseappdistributionV1alpha::GoogleFirebaseAppdistroV1alphaLoginCredentialFieldHints.new(
|
350
|
+
username_resource_name: username_resource,
|
351
|
+
password_resource_name: password_resource
|
352
|
+
)
|
353
|
+
end
|
354
|
+
|
355
|
+
if username.nil? ^ password.nil?
|
356
|
+
UI.user_error!("Username and password for automated tests need to be specified together.")
|
357
|
+
end
|
358
|
+
login_credential = nil
|
359
|
+
if !username.nil? && !password.nil?
|
360
|
+
login_credential =
|
361
|
+
Google::Apis::FirebaseappdistributionV1alpha::GoogleFirebaseAppdistroV1alphaLoginCredential.new(
|
362
|
+
username: username,
|
363
|
+
password: password,
|
364
|
+
field_hints: field_hints
|
365
|
+
)
|
366
|
+
else
|
367
|
+
unless field_hints.nil?
|
368
|
+
UI.user_error!("Must specify username and password for automated tests if resource names are set.")
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
device_executions = string_to_array(test_devices, ';').map do |td_string|
|
373
|
+
td_hash = parse_test_device_string(td_string)
|
374
|
+
Google::Apis::FirebaseappdistributionV1alpha::GoogleFirebaseAppdistroV1alphaDeviceExecution.new(
|
375
|
+
device: Google::Apis::FirebaseappdistributionV1alpha::GoogleFirebaseAppdistroV1alphaTestDevice.new(
|
376
|
+
model: td_hash['model'],
|
377
|
+
version: td_hash['version'],
|
378
|
+
orientation: td_hash['orientation'],
|
379
|
+
locale: td_hash['locale']
|
380
|
+
)
|
381
|
+
)
|
382
|
+
end
|
383
|
+
|
384
|
+
release_test =
|
385
|
+
Google::Apis::FirebaseappdistributionV1alpha::GoogleFirebaseAppdistroV1alphaReleaseTest.new(
|
386
|
+
login_credential: login_credential,
|
387
|
+
device_executions: device_executions
|
388
|
+
)
|
389
|
+
alpha_client.create_project_app_release_test(release.name, release_test)
|
390
|
+
rescue Google::Apis::Error => err
|
391
|
+
UI.crash!(err)
|
392
|
+
end
|
393
|
+
|
394
|
+
def self.poll_test_finished(alpha_client, release_test_name)
|
395
|
+
TEST_MAX_POLLING_RETRIES.times do
|
396
|
+
UI.message("⏳ The automated test results are pending.")
|
397
|
+
sleep(TEST_POLLING_INTERVAL_SECONDS)
|
398
|
+
release_test = alpha_client.get_project_app_release_test(release_test_name)
|
399
|
+
if release_test.device_executions.all? { |e| e.state == 'PASSED' }
|
400
|
+
UI.success("✅ Passed automated test(s).")
|
401
|
+
return
|
402
|
+
end
|
403
|
+
release_test.device_executions.each do |de|
|
404
|
+
case de.state
|
405
|
+
when 'PASSED', 'IN_PROGRESS'
|
406
|
+
next
|
407
|
+
when 'FAILED'
|
408
|
+
UI.test_failure!("Automated test failed for #{device_to_s(de.device)}: #{de.failed_reason}.")
|
409
|
+
when 'INCONCLUSIVE'
|
410
|
+
UI.test_failure!("Automated test inconclusive for #{device_to_s(de.device)}: #{de.inconclusive_reason}.")
|
411
|
+
else
|
412
|
+
UI.test_failure!("Unsupported automated test state for #{device_to_s(de.device)}: #{de.state}.")
|
413
|
+
end
|
414
|
+
end
|
415
|
+
end
|
416
|
+
UI.test_failure!("It took longer than expected to process your test, please try again.")
|
417
|
+
end
|
418
|
+
|
419
|
+
def self.parse_test_device_string(td_string)
|
420
|
+
allowed_keys = %w[model version locale orientation]
|
421
|
+
key_value_pairs = td_string.split(',').map do |key_value_string|
|
422
|
+
key, value = key_value_string.split('=')
|
423
|
+
unless allowed_keys.include?(key)
|
424
|
+
UI.user_error!("Unrecognized key in test_devices. Can only contain keys #{allowed_keys.join(', ')}.")
|
425
|
+
end
|
426
|
+
[key, value]
|
427
|
+
end
|
428
|
+
Hash[key_value_pairs]
|
429
|
+
end
|
430
|
+
|
431
|
+
def self.device_to_s(device)
|
432
|
+
"#{device.model} (#{device.version}/#{device.orientation}/#{device.locale})"
|
433
|
+
end
|
434
|
+
|
307
435
|
def self.available_options
|
308
436
|
[
|
309
437
|
# iOS Specific
|
@@ -335,7 +463,7 @@ module Fastlane
|
|
335
463
|
verify_block: proc do |value|
|
336
464
|
UI.user_error!("firebase_app_distribution: '#{value}' is not a valid value for android_artifact_type. Should be 'APK' or 'AAB'") unless ['APK', 'AAB'].include?(value)
|
337
465
|
end),
|
338
|
-
#
|
466
|
+
# General
|
339
467
|
FastlaneCore::ConfigItem.new(key: :app,
|
340
468
|
env_name: "FIREBASEAPPDISTRO_APP",
|
341
469
|
description: "Your app's Firebase App ID. You can find the App ID in the Firebase console, on the General Settings page",
|
@@ -344,26 +472,38 @@ module Fastlane
|
|
344
472
|
FastlaneCore::ConfigItem.new(key: :firebase_cli_path,
|
345
473
|
deprecated: "This plugin no longer uses the Firebase CLI",
|
346
474
|
env_name: "FIREBASEAPPDISTRO_FIREBASE_CLI_PATH",
|
347
|
-
description: "
|
475
|
+
description: "Absolute path of the Firebase CLI command",
|
348
476
|
type: String),
|
477
|
+
FastlaneCore::ConfigItem.new(key: :debug,
|
478
|
+
description: "Print verbose debug output",
|
479
|
+
optional: true,
|
480
|
+
default_value: false,
|
481
|
+
type: Boolean),
|
482
|
+
|
483
|
+
# Release Distribution
|
484
|
+
FastlaneCore::ConfigItem.new(key: :upload_timeout,
|
485
|
+
description: "Amount of seconds before the upload will timeout, if not completed",
|
486
|
+
optional: true,
|
487
|
+
default_value: DEFAULT_UPLOAD_TIMEOUT_SECONDS,
|
488
|
+
type: Integer),
|
349
489
|
FastlaneCore::ConfigItem.new(key: :groups,
|
350
490
|
env_name: "FIREBASEAPPDISTRO_GROUPS",
|
351
|
-
description: "
|
491
|
+
description: "Group aliases used for distribution, separated by commas",
|
352
492
|
optional: true,
|
353
493
|
type: String),
|
354
494
|
FastlaneCore::ConfigItem.new(key: :groups_file,
|
355
495
|
env_name: "FIREBASEAPPDISTRO_GROUPS_FILE",
|
356
|
-
description: "
|
496
|
+
description: "Path to file containing group aliases used for distribution, separated by commas",
|
357
497
|
optional: true,
|
358
498
|
type: String),
|
359
499
|
FastlaneCore::ConfigItem.new(key: :testers,
|
360
500
|
env_name: "FIREBASEAPPDISTRO_TESTERS",
|
361
|
-
description: "
|
501
|
+
description: "Email addresses of testers, separated by commas",
|
362
502
|
optional: true,
|
363
503
|
type: String),
|
364
504
|
FastlaneCore::ConfigItem.new(key: :testers_file,
|
365
505
|
env_name: "FIREBASEAPPDISTRO_TESTERS_FILE",
|
366
|
-
description: "
|
506
|
+
description: "Path to file containing email addresses of testers, separated by commas",
|
367
507
|
optional: true,
|
368
508
|
type: String),
|
369
509
|
FastlaneCore::ConfigItem.new(key: :release_notes,
|
@@ -373,27 +513,66 @@ module Fastlane
|
|
373
513
|
type: String),
|
374
514
|
FastlaneCore::ConfigItem.new(key: :release_notes_file,
|
375
515
|
env_name: "FIREBASEAPPDISTRO_RELEASE_NOTES_FILE",
|
376
|
-
description: "
|
516
|
+
description: "Path to file containing release notes for this build",
|
377
517
|
optional: true,
|
378
518
|
type: String),
|
379
|
-
|
380
|
-
|
519
|
+
|
520
|
+
# Release Testing
|
521
|
+
FastlaneCore::ConfigItem.new(key: :test_devices,
|
522
|
+
env_name: "FIREBASEAPPDISTRO_TEST_DEVICES",
|
523
|
+
description: "List of devices to run automated tests on, in the format 'model=<model-id>,version=<os-version-id>,locale=<locale>,orientation=<orientation>;model=<model-id>,...'. Run 'gcloud firebase test android|ios models list' to see available devices. Note: This feature is in beta",
|
381
524
|
optional: true,
|
382
525
|
type: String),
|
383
|
-
FastlaneCore::ConfigItem.new(key: :
|
384
|
-
|
526
|
+
FastlaneCore::ConfigItem.new(key: :test_devices_file,
|
527
|
+
env_name: "FIREBASEAPPDISTRO_TEST_DEVICES_FILE",
|
528
|
+
description: "Path to file containing a list of devices to run automated tests on, in the format 'model=<model-id>,version=<os-version-id>,locale=<locale>,orientation=<orientation>;model=<model-id>,...'. Run 'gcloud firebase test android|ios models list' to see available devices. Note: This feature is in beta",
|
529
|
+
optional: true,
|
530
|
+
type: String),
|
531
|
+
FastlaneCore::ConfigItem.new(key: :test_username,
|
532
|
+
env_name: "FIREBASEAPPDISTRO_TEST_USERNAME",
|
533
|
+
description: "Username for automatic login",
|
534
|
+
optional: true,
|
535
|
+
type: String),
|
536
|
+
FastlaneCore::ConfigItem.new(key: :test_password,
|
537
|
+
env_name: "FIREBASEAPPDISTRO_TEST_PASSWORD",
|
538
|
+
description: "Password for automatic login. If using a real password consider using test_password_file or setting FIREBASEAPPDISTRO_TEST_PASSWORD to avoid exposing sensitive info",
|
539
|
+
optional: true,
|
540
|
+
type: String),
|
541
|
+
FastlaneCore::ConfigItem.new(key: :test_password_file,
|
542
|
+
env_name: "FIREBASEAPPDISTRO_TEST_PASSWORD_FILE",
|
543
|
+
description: "Path to file containing password for automatic login",
|
544
|
+
optional: true,
|
545
|
+
type: String),
|
546
|
+
FastlaneCore::ConfigItem.new(key: :test_username_resource,
|
547
|
+
env_name: "FIREBASEAPPDISTRO_TEST_USERNAME_RESOURCE",
|
548
|
+
description: "Resource name for the username field for automatic login",
|
549
|
+
optional: true,
|
550
|
+
type: String),
|
551
|
+
FastlaneCore::ConfigItem.new(key: :test_password_resource,
|
552
|
+
env_name: "FIREBASEAPPDISTRO_TEST_PASSWORD_RESOURCE",
|
553
|
+
description: "Resource name for the password field for automatic login",
|
385
554
|
optional: true,
|
555
|
+
type: String),
|
556
|
+
FastlaneCore::ConfigItem.new(key: :test_non_blocking,
|
557
|
+
env_name: "FIREBASEAPPDISTRO_TEST_NON_BLOCKING",
|
558
|
+
description: "Run automated tests without waiting for them to finish. Visit the Firebase console for the test results",
|
559
|
+
optional: false,
|
386
560
|
default_value: false,
|
387
|
-
|
561
|
+
type: Boolean),
|
562
|
+
|
563
|
+
# Auth
|
564
|
+
FastlaneCore::ConfigItem.new(key: :firebase_cli_token,
|
565
|
+
description: "Auth token generated using the Firebase CLI's login:ci command",
|
566
|
+
optional: true,
|
567
|
+
type: String),
|
388
568
|
FastlaneCore::ConfigItem.new(key: :service_credentials_file,
|
389
|
-
description: "Path to Google service account json",
|
569
|
+
description: "Path to Google service account json file",
|
390
570
|
optional: true,
|
391
571
|
type: String),
|
392
|
-
FastlaneCore::ConfigItem.new(key: :
|
393
|
-
description: "
|
572
|
+
FastlaneCore::ConfigItem.new(key: :service_credentials_json_data,
|
573
|
+
description: "Google service account json file content",
|
394
574
|
optional: true,
|
395
|
-
|
396
|
-
type: Integer)
|
575
|
+
type: String)
|
397
576
|
]
|
398
577
|
end
|
399
578
|
|
@@ -410,7 +589,8 @@ module Fastlane
|
|
410
589
|
<<-CODE
|
411
590
|
firebase_app_distribution(
|
412
591
|
app: "<your Firebase app ID>",
|
413
|
-
testers: "snatchev@google.com, rebeccahe@google.com"
|
592
|
+
testers: "snatchev@google.com, rebeccahe@google.com",
|
593
|
+
test_devices: "model=shiba,version=34,locale=en,orientation=portrait;model=b0q,version=33,locale=en,orientation=portrait",
|
414
594
|
)
|
415
595
|
CODE
|
416
596
|
]
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'fastlane/action'
|
2
2
|
require 'fastlane_core/ui/ui'
|
3
|
-
|
3
|
+
require 'google/apis/firebaseappdistribution_v1'
|
4
4
|
require_relative '../helper/firebase_app_distribution_helper'
|
5
5
|
require_relative '../helper/firebase_app_distribution_auth_client'
|
6
6
|
|
@@ -11,7 +11,9 @@ module Fastlane
|
|
11
11
|
extend Helper::FirebaseAppDistributionHelper
|
12
12
|
|
13
13
|
def self.run(params)
|
14
|
-
|
14
|
+
init_google_api_client(params[:debug])
|
15
|
+
client = Google::Apis::FirebaseappdistributionV1::FirebaseAppDistributionService.new
|
16
|
+
client.authorization = get_authorization(params[:service_credentials_file], params[:firebase_cli_token], params[:service_credentials_json_data], params[:debug])
|
15
17
|
|
16
18
|
if blank?(params[:emails]) && blank?(params[:file])
|
17
19
|
UI.user_error!("Must specify `emails` or `file`.")
|
@@ -78,6 +80,10 @@ module Fastlane
|
|
78
80
|
description: "Path to Google service credentials file",
|
79
81
|
optional: true,
|
80
82
|
type: String),
|
83
|
+
FastlaneCore::ConfigItem.new(key: :service_credentials_json_data,
|
84
|
+
description: "Google service account json file content",
|
85
|
+
optional: true,
|
86
|
+
type: String),
|
81
87
|
FastlaneCore::ConfigItem.new(key: :firebase_cli_token,
|
82
88
|
description: "Auth token generated using the Firebase CLI's login:ci command",
|
83
89
|
optional: true,
|
@@ -1,7 +1,6 @@
|
|
1
1
|
require 'fastlane/action'
|
2
2
|
require 'fastlane_core/ui/ui'
|
3
3
|
require 'google/apis/firebaseappdistribution_v1'
|
4
|
-
|
5
4
|
require_relative '../helper/firebase_app_distribution_helper'
|
6
5
|
require_relative '../helper/firebase_app_distribution_auth_client'
|
7
6
|
|
@@ -11,8 +10,6 @@ module Fastlane
|
|
11
10
|
extend Auth::FirebaseAppDistributionAuthClient
|
12
11
|
extend Helper::FirebaseAppDistributionHelper
|
13
12
|
|
14
|
-
FirebaseAppDistributionV1 = Google::Apis::FirebaseappdistributionV1
|
15
|
-
|
16
13
|
def self.run(params)
|
17
14
|
if blank?(params[:alias])
|
18
15
|
UI.user_error!("Must specify `alias`.")
|
@@ -22,7 +19,9 @@ module Fastlane
|
|
22
19
|
UI.user_error!("Must specify `display_name`.")
|
23
20
|
end
|
24
21
|
|
25
|
-
|
22
|
+
init_google_api_client(params[:debug])
|
23
|
+
client = Google::Apis::FirebaseappdistributionV1::FirebaseAppDistributionService.new
|
24
|
+
client.authorization = get_authorization(params[:service_credentials_file], params[:firebase_cli_token], params[:service_credentials_json_data], params[:debug])
|
26
25
|
|
27
26
|
project_number = params[:project_number]
|
28
27
|
group_alias = params[:alias]
|
@@ -31,7 +30,7 @@ module Fastlane
|
|
31
30
|
UI.message("⏳ Creating tester group '#{group_alias} (#{display_name})' in project #{project_number}...")
|
32
31
|
|
33
32
|
parent = project_name(project_number)
|
34
|
-
group =
|
33
|
+
group = Google::Apis::FirebaseappdistributionV1::GoogleFirebaseAppdistroV1Group.new(
|
35
34
|
name: group_name(project_number, group_alias),
|
36
35
|
display_name: display_name
|
37
36
|
)
|
@@ -88,6 +87,10 @@ module Fastlane
|
|
88
87
|
description: "Path to Google service credentials file",
|
89
88
|
optional: true,
|
90
89
|
type: String),
|
90
|
+
FastlaneCore::ConfigItem.new(key: :service_credentials_json_data,
|
91
|
+
description: "Google service account json file content",
|
92
|
+
optional: true,
|
93
|
+
type: String),
|
91
94
|
FastlaneCore::ConfigItem.new(key: :firebase_cli_token,
|
92
95
|
description: "Auth token generated using the Firebase CLI's login:ci command",
|
93
96
|
optional: true,
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'fastlane/action'
|
2
2
|
require 'fastlane_core/ui/ui'
|
3
|
-
|
3
|
+
require 'google/apis/firebaseappdistribution_v1'
|
4
4
|
require_relative '../helper/firebase_app_distribution_helper'
|
5
5
|
require_relative '../helper/firebase_app_distribution_auth_client'
|
6
6
|
|
@@ -11,7 +11,9 @@ module Fastlane
|
|
11
11
|
extend Helper::FirebaseAppDistributionHelper
|
12
12
|
|
13
13
|
def self.run(params)
|
14
|
-
|
14
|
+
init_google_api_client(params[:debug])
|
15
|
+
client = Google::Apis::FirebaseappdistributionV1::FirebaseAppDistributionService.new
|
16
|
+
client.authorization = get_authorization(params[:service_credentials_file], params[:firebase_cli_token], params[:service_credentials_json_data], params[:debug])
|
15
17
|
|
16
18
|
if blank?(params[:alias])
|
17
19
|
UI.user_error!("Must specify `alias`.")
|
@@ -65,6 +67,10 @@ module Fastlane
|
|
65
67
|
description: "Path to Google service credentials file",
|
66
68
|
optional: true,
|
67
69
|
type: String),
|
70
|
+
FastlaneCore::ConfigItem.new(key: :service_credentials_json_data,
|
71
|
+
description: "Google service account json file content",
|
72
|
+
optional: true,
|
73
|
+
type: String),
|
68
74
|
FastlaneCore::ConfigItem.new(key: :firebase_cli_token,
|
69
75
|
description: "Auth token generated using the Firebase CLI's login:ci command",
|
70
76
|
optional: true,
|
@@ -13,7 +13,9 @@ module Fastlane
|
|
13
13
|
extend Helper::FirebaseAppDistributionHelper
|
14
14
|
|
15
15
|
def self.run(params)
|
16
|
-
|
16
|
+
init_google_api_client(params[:debug])
|
17
|
+
client = Google::Apis::FirebaseappdistributionV1::FirebaseAppDistributionService.new
|
18
|
+
client.authorization = get_authorization(params[:service_credentials_file], params[:firebase_cli_token], params[:service_credentials_json_data], params[:debug])
|
17
19
|
|
18
20
|
UI.message("⏳ Fetching latest release for app #{params[:app]}...")
|
19
21
|
|
@@ -89,6 +91,10 @@ module Fastlane
|
|
89
91
|
description: "Path to Google service account json",
|
90
92
|
optional: true,
|
91
93
|
type: String),
|
94
|
+
FastlaneCore::ConfigItem.new(key: :service_credentials_json_data,
|
95
|
+
description: "Google service account json file content",
|
96
|
+
optional: true,
|
97
|
+
type: String),
|
92
98
|
FastlaneCore::ConfigItem.new(key: :debug,
|
93
99
|
description: "Print verbose debug output",
|
94
100
|
optional: true,
|
data/lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_get_udids.rb
CHANGED
@@ -2,9 +2,9 @@ require 'fastlane/action'
|
|
2
2
|
require 'open3'
|
3
3
|
require 'shellwords'
|
4
4
|
require 'googleauth'
|
5
|
+
require 'google/apis/firebaseappdistribution_v1alpha'
|
5
6
|
require_relative '../helper/firebase_app_distribution_helper'
|
6
7
|
require_relative '../helper/firebase_app_distribution_error_message'
|
7
|
-
require_relative '../client/firebase_app_distribution_api_client'
|
8
8
|
require_relative '../helper/firebase_app_distribution_auth_client'
|
9
9
|
|
10
10
|
module Fastlane
|
@@ -14,14 +14,23 @@ module Fastlane
|
|
14
14
|
extend Helper::FirebaseAppDistributionHelper
|
15
15
|
|
16
16
|
def self.run(params)
|
17
|
-
|
18
|
-
|
17
|
+
init_google_api_client(params[:debug])
|
18
|
+
client = Google::Apis::FirebaseappdistributionV1alpha::FirebaseAppDistributionService.new
|
19
|
+
client.authorization = get_authorization(params[:service_credentials_file], params[:firebase_cli_token], params[:service_credentials_json_data], params[:debug])
|
19
20
|
|
20
|
-
|
21
|
-
|
21
|
+
project_number = params[:project_number]
|
22
|
+
if blank?(project_number)
|
23
|
+
app_id = params[:app]
|
24
|
+
if blank?(app_id)
|
25
|
+
UI.user_error!("Must specify `project_number`.")
|
26
|
+
end
|
27
|
+
project_number = project_number_from_app_id(app_id)
|
28
|
+
end
|
29
|
+
udids = client.get_project_tester_udids(project_name(project_number)).tester_udids
|
22
30
|
|
23
|
-
if udids.empty?
|
24
|
-
|
31
|
+
if udids.to_a.empty?
|
32
|
+
File.delete(params[:output_file]) if File.exist?(params[:output_file])
|
33
|
+
UI.important("App Distribution fetched 0 tester UDIDs. Removed output file.")
|
25
34
|
else
|
26
35
|
write_udids_to_file(udids, params[:output_file])
|
27
36
|
UI.success("🎉 App Distribution tester UDIDs written to: #{params[:output_file]}")
|
@@ -32,7 +41,7 @@ module Fastlane
|
|
32
41
|
File.open(output_file, 'w') do |f|
|
33
42
|
f.write("Device ID\tDevice Name\tDevice Platform\n")
|
34
43
|
udids.each do |tester_udid|
|
35
|
-
f.write("#{tester_udid
|
44
|
+
f.write("#{tester_udid.udid}\t#{tester_udid.name}\t#{tester_udid.platform}\n")
|
36
45
|
end
|
37
46
|
end
|
38
47
|
end
|
@@ -52,10 +61,18 @@ module Fastlane
|
|
52
61
|
|
53
62
|
def self.available_options
|
54
63
|
[
|
64
|
+
FastlaneCore::ConfigItem.new(key: :project_number,
|
65
|
+
conflicting_options: [:app],
|
66
|
+
env_name: "FIREBASEAPPDISTRO_PROJECT_NUMBER",
|
67
|
+
description: "Your Firebase project number. You can find the project number in the Firebase console, on the General Settings page",
|
68
|
+
type: Integer,
|
69
|
+
optional: true),
|
55
70
|
FastlaneCore::ConfigItem.new(key: :app,
|
71
|
+
conflicting_options: [:project_number],
|
56
72
|
env_name: "FIREBASEAPPDISTRO_APP",
|
57
73
|
description: "Your app's Firebase App ID. You can find the App ID in the Firebase console, on the General Settings page",
|
58
|
-
optional:
|
74
|
+
optional: true,
|
75
|
+
deprecated: "Use project_number (FIREBASEAPPDISTRO_PROJECT_NUMBER) instead",
|
59
76
|
type: String),
|
60
77
|
FastlaneCore::ConfigItem.new(key: :output_file,
|
61
78
|
env_name: "FIREBASEAPPDISTRO_OUTPUT_FILE",
|
@@ -70,6 +87,10 @@ module Fastlane
|
|
70
87
|
description: "Path to Google service account json",
|
71
88
|
optional: true,
|
72
89
|
type: String),
|
90
|
+
FastlaneCore::ConfigItem.new(key: :service_credentials_json_data,
|
91
|
+
description: "Google service account json file content",
|
92
|
+
optional: true,
|
93
|
+
type: String),
|
73
94
|
FastlaneCore::ConfigItem.new(key: :debug,
|
74
95
|
description: "Print verbose debug output",
|
75
96
|
optional: true,
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'fastlane/action'
|
2
2
|
require 'fastlane_core/ui/ui'
|
3
|
-
|
3
|
+
require 'google/apis/firebaseappdistribution_v1'
|
4
4
|
require_relative '../helper/firebase_app_distribution_helper'
|
5
5
|
require_relative '../helper/firebase_app_distribution_auth_client'
|
6
6
|
|
@@ -11,7 +11,9 @@ module Fastlane
|
|
11
11
|
extend Helper::FirebaseAppDistributionHelper
|
12
12
|
|
13
13
|
def self.run(params)
|
14
|
-
|
14
|
+
init_google_api_client(params[:debug])
|
15
|
+
client = Google::Apis::FirebaseappdistributionV1::FirebaseAppDistributionService.new
|
16
|
+
client.authorization = get_authorization(params[:service_credentials_file], params[:firebase_cli_token], params[:service_credentials_json_data], params[:debug])
|
15
17
|
|
16
18
|
if blank?(params[:emails]) && blank?(params[:file])
|
17
19
|
UI.user_error!("Must specify `emails` or `file`.")
|
@@ -73,6 +75,10 @@ module Fastlane
|
|
73
75
|
description: "Path to Google service credentials file",
|
74
76
|
optional: true,
|
75
77
|
type: String),
|
78
|
+
FastlaneCore::ConfigItem.new(key: :service_credentials_json_data,
|
79
|
+
description: "Google service account json file content",
|
80
|
+
optional: true,
|
81
|
+
type: String),
|
76
82
|
FastlaneCore::ConfigItem.new(key: :firebase_cli_token,
|
77
83
|
description: "Auth token generated using the Firebase CLI's login:ci command",
|
78
84
|
optional: true,
|
data/lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_auth_client.rb
CHANGED
@@ -20,6 +20,7 @@ module Fastlane
|
|
20
20
|
# auth method is used, unset all other auth variables/parameters to nil/empty
|
21
21
|
#
|
22
22
|
# args
|
23
|
+
# google_service_json_data - Google service account json file content as a string
|
23
24
|
# google_service_path - Absolute path to the Google service account file
|
24
25
|
# firebase_cli_token - Refresh token
|
25
26
|
# debug - Whether to enable debug-level logging
|
@@ -28,26 +29,25 @@ module Fastlane
|
|
28
29
|
# FIREBASE_TOKEN - see firebase_cli_token
|
29
30
|
#
|
30
31
|
# Crashes if given invalid or missing credentials
|
31
|
-
def get_authorization(google_service_path, firebase_cli_token, debug = false)
|
32
|
+
def get_authorization(google_service_path, firebase_cli_token, google_service_json_data, debug = false)
|
32
33
|
if !google_service_path.nil? && !google_service_path.empty?
|
33
34
|
UI.message("🔐 Authenticating with --service_credentials_file path parameter: #{google_service_path}")
|
34
|
-
|
35
|
+
service_account_from_file(google_service_path, debug)
|
36
|
+
elsif !google_service_json_data.nil? && !google_service_json_data.empty?
|
37
|
+
UI.message("🔐 Authenticating with --service_credentials_json content parameter")
|
38
|
+
service_account_from_json(google_service_json_data, debug)
|
35
39
|
elsif !firebase_cli_token.nil? && !firebase_cli_token.empty?
|
36
40
|
UI.message("🔐 Authenticating with --firebase_cli_token parameter")
|
37
41
|
firebase_token(firebase_cli_token, debug)
|
38
42
|
elsif !ENV["FIREBASE_TOKEN"].nil? && !ENV["FIREBASE_TOKEN"].empty?
|
39
43
|
UI.message("🔐 Authenticating with FIREBASE_TOKEN environment variable")
|
40
44
|
firebase_token(ENV["FIREBASE_TOKEN"], debug)
|
41
|
-
# TODO(lkellogg): Not using Google::Auth.get_application_default yet while we are still
|
42
|
-
# using the old client for uploads. ADC also does not work for the get_udids action:
|
43
|
-
# https://cloud.google.com/docs/authentication/troubleshoot-adc#user-creds-client-based
|
44
|
-
# For now go back to just using the environment variable:
|
45
|
-
elsif !ENV["GOOGLE_APPLICATION_CREDENTIALS"].nil? && !ENV["GOOGLE_APPLICATION_CREDENTIALS"].empty?
|
46
|
-
UI.message("🔐 Authenticating with GOOGLE_APPLICATION_CREDENTIALS environment variable: #{ENV['GOOGLE_APPLICATION_CREDENTIALS']}")
|
47
|
-
service_account(ENV["GOOGLE_APPLICATION_CREDENTIALS"], debug)
|
48
45
|
elsif (refresh_token = refresh_token_from_firebase_tools)
|
49
|
-
UI.message("🔐
|
46
|
+
UI.message("🔐 Authenticating with cached Firebase CLI credentials")
|
50
47
|
firebase_token(refresh_token, debug)
|
48
|
+
elsif !application_default_creds.nil?
|
49
|
+
UI.message("🔐 Authenticating with Application Default Credentials")
|
50
|
+
application_default_creds
|
51
51
|
else
|
52
52
|
UI.user_error!(ErrorMessage::MISSING_CREDENTIALS)
|
53
53
|
nil
|
@@ -56,6 +56,12 @@ module Fastlane
|
|
56
56
|
|
57
57
|
private
|
58
58
|
|
59
|
+
def application_default_creds
|
60
|
+
Google::Auth.get_application_default([SCOPE])
|
61
|
+
rescue
|
62
|
+
nil
|
63
|
+
end
|
64
|
+
|
59
65
|
def refresh_token_from_firebase_tools
|
60
66
|
config_path = format_config_path
|
61
67
|
if File.exist?(config_path)
|
@@ -98,21 +104,32 @@ module Fastlane
|
|
98
104
|
UI.user_error!(error_message)
|
99
105
|
end
|
100
106
|
|
101
|
-
def
|
102
|
-
|
103
|
-
|
107
|
+
def service_account_from_json(google_service_json_data, debug)
|
108
|
+
get_service_account_credentials(google_service_json_data, debug)
|
109
|
+
end
|
110
|
+
|
111
|
+
def service_account_from_file(google_service_path, debug)
|
112
|
+
get_service_account_credentials(File.read(google_service_path), debug)
|
113
|
+
rescue Errno::ENOENT
|
114
|
+
UI.user_error!("#{ErrorMessage::SERVICE_CREDENTIALS_NOT_FOUND}: #{google_service_path}")
|
115
|
+
end
|
116
|
+
|
117
|
+
def get_service_account_credentials(json_data, debug)
|
118
|
+
json_file = JSON.parse(json_data)
|
119
|
+
# check if it's an external account or service account
|
120
|
+
auth = json_file["type"] == "external_account" ? Google::Auth::ExternalAccount::Credentials : Google::Auth::ServiceAccountCredentials
|
121
|
+
service_account_credentials = auth.make_creds(
|
122
|
+
json_key_io: StringIO.new(json_data),
|
104
123
|
scope: SCOPE
|
105
124
|
)
|
106
125
|
service_account_credentials.fetch_access_token!
|
107
126
|
service_account_credentials
|
108
|
-
rescue Errno::ENOENT
|
109
|
-
UI.user_error!("#{ErrorMessage::SERVICE_CREDENTIALS_NOT_FOUND}: #{google_service_path}")
|
110
127
|
rescue Signet::AuthorizationError => error
|
111
|
-
error_message = "#{ErrorMessage::SERVICE_CREDENTIALS_ERROR}:
|
128
|
+
error_message = "#{ErrorMessage::SERVICE_CREDENTIALS_ERROR}: "
|
112
129
|
if debug
|
113
130
|
error_message += "\n#{error_details(error)}"
|
114
131
|
else
|
115
|
-
error_message +=
|
132
|
+
error_message += debug_instructions.to_s
|
116
133
|
end
|
117
134
|
UI.user_error!(error_message)
|
118
135
|
end
|
data/lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_error_message.rb
CHANGED
@@ -1,46 +1,46 @@
|
|
1
1
|
module ErrorMessage
|
2
2
|
MISSING_CREDENTIALS = "Missing authentication credentials. Set up Application Default Credentials, your Firebase refresh token, or sign in with the Firebase CLI, and try again."
|
3
|
-
MISSING_APP_ID = "Missing app id. Please check that the app parameter is set and try again"
|
4
|
-
SERVICE_CREDENTIALS_NOT_FOUND = "Service credentials file does not exist. Please check the service credentials path and try again"
|
5
|
-
PARSE_SERVICE_CREDENTIALS_ERROR = "Failed to extract service account information from the service credentials file"
|
6
|
-
PARSE_FIREBASE_TOOLS_JSON_ERROR = "Encountered error parsing json file. Ensure the firebase-tools.json file is formatted correctly"
|
7
|
-
UPLOAD_RELEASE_NOTES_ERROR = "App Distribution halted because it had a problem uploading release notes"
|
8
|
-
UPLOAD_TESTERS_ERROR = "App Distribution halted because it had a problem adding testers/groups"
|
9
|
-
GET_RELEASE_TIMEOUT = "App Distribution failed to fetch release information"
|
3
|
+
MISSING_APP_ID = "Missing app id. Please check that the app parameter is set and try again."
|
4
|
+
SERVICE_CREDENTIALS_NOT_FOUND = "Service credentials file does not exist. Please check the service credentials path and try again."
|
5
|
+
PARSE_SERVICE_CREDENTIALS_ERROR = "Failed to extract service account information from the service credentials file."
|
6
|
+
PARSE_FIREBASE_TOOLS_JSON_ERROR = "Encountered error parsing json file. Ensure the firebase-tools.json file is formatted correctly."
|
7
|
+
UPLOAD_RELEASE_NOTES_ERROR = "App Distribution halted because it had a problem uploading release notes."
|
8
|
+
UPLOAD_TESTERS_ERROR = "App Distribution halted because it had a problem adding testers/groups."
|
9
|
+
GET_RELEASE_TIMEOUT = "App Distribution failed to fetch release information."
|
10
10
|
REFRESH_TOKEN_ERROR = "App Distribution could not generate credentials from the refresh token specified."
|
11
|
-
APP_NOT_ONBOARDED_ERROR = "App Distribution not onboarded"
|
11
|
+
APP_NOT_ONBOARDED_ERROR = "App Distribution not onboarded."
|
12
12
|
INVALID_APP_ID = "App Distribution could not find your app. Make sure to onboard your app by pressing the \"Get started\" button on the App Distribution page in the Firebase console: https://console.firebase.google.com/project/_/appdistribution. App ID"
|
13
13
|
INVALID_PROJECT = "App Distribution could not find your Firebase project. Make sure to onboard an app in your project by pressing the \"Get started\" button on the App Distribution page in the Firebase console: https://console.firebase.google.com/project/_/appdistribution."
|
14
14
|
INVALID_PATH = "Could not read content from"
|
15
15
|
INVALID_TESTERS = "Could not enable access for testers. Check that the tester emails are formatted correctly, the groups exist and you are using group aliases (not group names) for specifying groups."
|
16
16
|
INVALID_TESTER_GROUP = "App Distribution could not find your tester group. Make sure that it exists before trying to add testers, and that the group alias is specified correctly."
|
17
17
|
INVALID_TESTER_GROUP_NAME = "The tester group name should be 4-63 characters, and valid characters are /[a-z][0-9]-/."
|
18
|
-
INVALID_RELEASE_NOTES = "Failed to set release notes"
|
19
|
-
SERVICE_CREDENTIALS_ERROR = "App Distribution could not generate credentials from the service credentials file specified"
|
18
|
+
INVALID_RELEASE_NOTES = "Failed to set release notes."
|
19
|
+
SERVICE_CREDENTIALS_ERROR = "App Distribution could not generate credentials from the service credentials file specified."
|
20
20
|
PLAY_ACCOUNT_NOT_LINKED = "This project is not linked to a Google Play account."
|
21
21
|
APP_NOT_PUBLISHED = "This app is not published in the Google Play console."
|
22
22
|
NO_APP_WITH_GIVEN_BUNDLE_ID_IN_PLAY_ACCOUNT = "App with matching package name does not exist in Google Play."
|
23
23
|
PLAY_IAS_TERMS_NOT_ACCEPTED = "You must accept the Play Internal App Sharing (IAS) terms to upload AABs."
|
24
24
|
INVALID_EMAIL_ADDRESS = "You passed an invalid email address."
|
25
|
-
TESTER_LIMIT_VIOLATION = "Creating testers would exceed tester limit"
|
25
|
+
TESTER_LIMIT_VIOLATION = "Creating testers would exceed tester limit."
|
26
26
|
|
27
27
|
def self.aab_upload_error(aab_state)
|
28
28
|
"Failed to process the AAB: #{aab_state}"
|
29
29
|
end
|
30
30
|
|
31
31
|
def self.binary_not_found(binary_type)
|
32
|
-
"Could not find the #{binary_type}. Make sure you set the #{binary_type} path parameter to point to your #{binary_type}"
|
32
|
+
"Could not find the #{binary_type}. Make sure you set the #{binary_type} path parameter to point to your #{binary_type}."
|
33
33
|
end
|
34
34
|
|
35
35
|
def self.parse_binary_metadata_error(binary_type)
|
36
|
-
"Failed to extract #{binary_type} metadata from the #{binary_type} path"
|
36
|
+
"Failed to extract #{binary_type} metadata from the #{binary_type} path."
|
37
37
|
end
|
38
38
|
|
39
39
|
def self.upload_binary_error(binary_type)
|
40
|
-
"App Distribution halted because it had a problem uploading the #{binary_type}"
|
40
|
+
"App Distribution halted because it had a problem uploading the #{binary_type}."
|
41
41
|
end
|
42
42
|
|
43
43
|
def self.binary_processing_error(binary_type)
|
44
|
-
"App Distribution failed to process the #{binary_type}"
|
44
|
+
"App Distribution failed to process the #{binary_type}."
|
45
45
|
end
|
46
46
|
end
|
data/lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_helper.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'fastlane_core/ui/ui'
|
2
2
|
require 'cfpropertylist'
|
3
|
+
|
3
4
|
module Fastlane
|
4
5
|
UI = FastlaneCore::UI unless Fastlane.const_defined?("UI")
|
5
6
|
module Helper
|
@@ -26,10 +27,10 @@ module Fastlane
|
|
26
27
|
|
27
28
|
# Returns the array representation of a string with trimmed comma
|
28
29
|
# seperated values.
|
29
|
-
def string_to_array(string)
|
30
|
+
def string_to_array(string, delimiter = ",")
|
30
31
|
return [] if string.nil?
|
31
32
|
# Strip string and then strip individual values
|
32
|
-
string.strip.split(
|
33
|
+
string.strip.split(delimiter).map(&:strip)
|
33
34
|
end
|
34
35
|
|
35
36
|
def parse_plist(path)
|
@@ -53,8 +54,12 @@ module Fastlane
|
|
53
54
|
!blank?(value)
|
54
55
|
end
|
55
56
|
|
57
|
+
def project_number_from_app_id(app_id)
|
58
|
+
app_id.split(':')[1]
|
59
|
+
end
|
60
|
+
|
56
61
|
def app_name_from_app_id(app_id)
|
57
|
-
"#{project_name(app_id
|
62
|
+
"#{project_name(project_number_from_app_id(app_id))}/apps/#{app_id}"
|
58
63
|
end
|
59
64
|
|
60
65
|
def project_name(project_number)
|
@@ -65,7 +70,7 @@ module Fastlane
|
|
65
70
|
"#{project_name(project_number)}/groups/#{group_alias}"
|
66
71
|
end
|
67
72
|
|
68
|
-
def
|
73
|
+
def init_google_api_client(debug, timeout = nil)
|
69
74
|
if debug
|
70
75
|
UI.important("Warning: Debug logging enabled. Output may include sensitive information.")
|
71
76
|
Google::Apis.logger.level = Logger::DEBUG
|
@@ -76,10 +81,6 @@ module Fastlane
|
|
76
81
|
unless timeout.nil?
|
77
82
|
Google::Apis::ClientOptions.default.send_timeout_sec = timeout
|
78
83
|
end
|
79
|
-
|
80
|
-
client = Google::Apis::FirebaseappdistributionV1::FirebaseAppDistributionService.new
|
81
|
-
client.authorization = get_authorization(service_credentials_file, firebase_cli_token, debug)
|
82
|
-
client
|
83
84
|
end
|
84
85
|
|
85
86
|
def deep_symbolize_keys(hash)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fastlane-plugin-firebase_app_distribution
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stefan Natchev
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2024-02-08 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: google-apis-firebaseappdistribution_v1
|
@@ -26,6 +26,20 @@ dependencies:
|
|
26
26
|
- - "~>"
|
27
27
|
- !ruby/object:Gem::Version
|
28
28
|
version: 0.3.0
|
29
|
+
- !ruby/object:Gem::Dependency
|
30
|
+
name: google-apis-firebaseappdistribution_v1alpha
|
31
|
+
requirement: !ruby/object:Gem::Requirement
|
32
|
+
requirements:
|
33
|
+
- - "~>"
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: 0.2.0
|
36
|
+
type: :runtime
|
37
|
+
prerelease: false
|
38
|
+
version_requirements: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - "~>"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 0.2.0
|
29
43
|
- !ruby/object:Gem::Dependency
|
30
44
|
name: pry
|
31
45
|
requirement: !ruby/object:Gem::Requirement
|
@@ -172,7 +186,6 @@ files:
|
|
172
186
|
- lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_get_latest_release.rb
|
173
187
|
- lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_get_udids.rb
|
174
188
|
- lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_remove_testers_action.rb
|
175
|
-
- lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb
|
176
189
|
- lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_auth_client.rb
|
177
190
|
- lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_error_message.rb
|
178
191
|
- lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_helper.rb
|
@@ -196,7 +209,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
196
209
|
- !ruby/object:Gem::Version
|
197
210
|
version: '0'
|
198
211
|
requirements: []
|
199
|
-
rubygems_version: 3.
|
212
|
+
rubygems_version: 3.5.3
|
200
213
|
signing_key:
|
201
214
|
specification_version: 4
|
202
215
|
summary: Release your beta builds to Firebase App Distribution. https://firebase.google.com/docs/app-distribution
|
data/lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb
DELETED
@@ -1,97 +0,0 @@
|
|
1
|
-
require 'fastlane_core/ui/ui'
|
2
|
-
require_relative '../helper/firebase_app_distribution_helper'
|
3
|
-
|
4
|
-
module Fastlane
|
5
|
-
module Client
|
6
|
-
class FirebaseAppDistributionApiClient
|
7
|
-
include Helper::FirebaseAppDistributionHelper
|
8
|
-
|
9
|
-
BASE_URL = "https://firebaseappdistribution.googleapis.com"
|
10
|
-
MAX_POLLING_RETRIES = 60
|
11
|
-
POLLING_INTERVAL_SECONDS = 5
|
12
|
-
|
13
|
-
AUTHORIZATION = "Authorization"
|
14
|
-
CONTENT_TYPE = "Content-Type"
|
15
|
-
APPLICATION_OCTET_STREAM = "application/octet-stream"
|
16
|
-
CLIENT_VERSION = "X-Client-Version"
|
17
|
-
|
18
|
-
def initialize(auth_token, debug = false)
|
19
|
-
@auth_token = auth_token
|
20
|
-
@debug = debug
|
21
|
-
end
|
22
|
-
|
23
|
-
# Uploads the app binary to the Firebase API
|
24
|
-
#
|
25
|
-
# args
|
26
|
-
# app_name - Firebase App resource name
|
27
|
-
# binary_path - Absolute path to your app's aab/apk/ipa file
|
28
|
-
# platform - 'android' or 'ios'
|
29
|
-
# timeout - The amount of seconds before the upload will timeout, if not completed
|
30
|
-
#
|
31
|
-
# Returns the long-running operation name.
|
32
|
-
#
|
33
|
-
# Throws a user_error if the binary file does not exist
|
34
|
-
def upload_binary(app_name, binary_path, platform, timeout)
|
35
|
-
response = connection.post(binary_upload_url(app_name), read_binary(binary_path)) do |request|
|
36
|
-
request.options.timeout = timeout # seconds
|
37
|
-
request.headers[AUTHORIZATION] = "Bearer " + @auth_token
|
38
|
-
request.headers[CONTENT_TYPE] = APPLICATION_OCTET_STREAM
|
39
|
-
request.headers[CLIENT_VERSION] = client_version_header_value
|
40
|
-
request.headers["X-Goog-Upload-File-Name"] = File.basename(binary_path)
|
41
|
-
request.headers["X-Goog-Upload-Protocol"] = "raw"
|
42
|
-
end
|
43
|
-
|
44
|
-
response.body[:name] || ''
|
45
|
-
rescue Errno::ENOENT # Raised when binary_path file does not exist
|
46
|
-
binary_type = binary_type_from_path(binary_path)
|
47
|
-
UI.user_error!("#{ErrorMessage.binary_not_found(binary_type)}: #{binary_path}")
|
48
|
-
end
|
49
|
-
|
50
|
-
# Get tester UDIDs
|
51
|
-
#
|
52
|
-
# args
|
53
|
-
# app_name - Firebase App resource name
|
54
|
-
#
|
55
|
-
# Returns a list of hashes containing tester device info
|
56
|
-
def get_udids(app_id)
|
57
|
-
begin
|
58
|
-
response = connection.get(get_udids_url(app_id)) do |request|
|
59
|
-
request.headers[AUTHORIZATION] = "Bearer " + @auth_token
|
60
|
-
request.headers[CLIENT_VERSION] = client_version_header_value
|
61
|
-
end
|
62
|
-
rescue Faraday::ResourceNotFound
|
63
|
-
UI.user_error!("#{ErrorMessage::INVALID_APP_ID}: #{app_id}")
|
64
|
-
end
|
65
|
-
response.body[:testerUdids] || []
|
66
|
-
end
|
67
|
-
|
68
|
-
private
|
69
|
-
|
70
|
-
def client_version_header_value
|
71
|
-
"fastlane/#{Fastlane::FirebaseAppDistribution::VERSION}"
|
72
|
-
end
|
73
|
-
|
74
|
-
def binary_upload_url(app_name)
|
75
|
-
"/upload/v1/#{app_name}/releases:upload"
|
76
|
-
end
|
77
|
-
|
78
|
-
def get_udids_url(app_id)
|
79
|
-
"/v1alpha/apps/#{app_id}/testers:getTesterUdids"
|
80
|
-
end
|
81
|
-
|
82
|
-
def connection
|
83
|
-
@connection ||= Faraday.new(url: BASE_URL) do |conn|
|
84
|
-
conn.response(:json, parser_options: { symbolize_names: true })
|
85
|
-
conn.response(:raise_error) # raise_error middleware will run before the json middleware
|
86
|
-
conn.response(:logger, nil, { headers: false, bodies: { response: true }, log_level: :debug }) if @debug
|
87
|
-
conn.adapter(Faraday.default_adapter)
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
def read_binary(path)
|
92
|
-
# File must be read in binary mode to work on Windows
|
93
|
-
File.open(path, 'rb').read
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|