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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 31b34bdb303bcc029291767ec3396329550ed6ac8f038774b37d78fb5121f1dc
4
- data.tar.gz: 220323bd4dab4e6cd20fca8a863aa184d4b88fdf261549ecb115d8ef4aaf0b6b
3
+ metadata.gz: 1ed146d48ff09a46b608e01e483f207ce62a2809d9a8ded675c56d0b22802ba7
4
+ data.tar.gz: 18b893dc6b1a79a339fb7aeb8c432c13bd419d04df78dc4c5606dbfd24db64a6
5
5
  SHA512:
6
- metadata.gz: 7256d3eb6e7ac59dd8bffbd3ce3d1fda8b98d1284c6576ad556e1f43d46d7adc0b86a59fc5a30577438a525fea8f46b76027b28278a2c3a025d98b3c6e8636e7
7
- data.tar.gz: a4469ffccd6a38a78dfaf3729433ab5961ade3fc251f97c791123f76632c8142f25544afd6ece4c961310d45d0277be0212782ababbc8418eceb08c4d568dec3
6
+ metadata.gz: afa135119953bc3ee405aa868e1f5012b0e1dc9ac1240b02a895f437125b142b5e58a9930e4dc137fcfac92e3711db9ac364d07c98f33e61a6de652e878bdf46
7
+ data.tar.gz: c263365d7d45466c43486fb78548c7b0191b3f502e4e207f83b4674180badb457cb35e0f14e927f3a7c82841b6544ff5d44e0e05f99837f6ae45331dda9dbd92
@@ -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
- MAX_POLLING_RETRIES = 60
23
- POLLING_INTERVAL_SECONDS = 5
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
- # ideally the timeout should only apply to the binary upload
40
- client = init_client(params[:service_credentials_file],
41
- params[:firebase_cli_token],
42
- params[:debug],
43
- timeout)
44
-
45
- # If binary is an AAB, get the AAB info for this app, which includes the integration state and certificate data
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(" Uploading the #{binary_type}.")
53
-
54
- # For some reason calling the client.upload_medium returns nil when
55
- # it should return a long running operation object
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, operation_name, binary_type)
226
- operation = client.get_project_app_release_operation(operation_name)
227
- MAX_POLLING_RETRIES.times do
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
- result = operation.response['result']
231
- if result == 'RELEASE_UPDATED'
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
- break
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
- sleep(POLLING_INTERVAL_SECONDS)
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
- # Generic
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: "The absolute path of the firebase cli command",
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: "The group aliases used for distribution, separated by commas",
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: "The group aliases used for distribution, separated by commas",
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: "Pass email addresses of testers, separated by commas",
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: "Pass email addresses of testers, separated by commas",
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: "Release notes file for this build",
516
+ description: "Path to file containing release notes for this build",
377
517
  optional: true,
378
518
  type: String),
379
- FastlaneCore::ConfigItem.new(key: :firebase_cli_token,
380
- description: "Auth token generated using the Firebase CLI's login:ci command",
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: :debug,
384
- description: "Print verbose debug output",
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
- is_string: false),
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: :upload_timeout,
393
- description: "The amount of seconds before the upload will timeout, if not completed",
572
+ FastlaneCore::ConfigItem.new(key: :service_credentials_json_data,
573
+ description: "Google service account json file content",
394
574
  optional: true,
395
- default_value: DEFAULT_UPLOAD_TIMEOUT_SECONDS,
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
- client = init_client(params[:service_credentials_file], params[:firebase_cli_token], params[:debug])
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
- client = init_client(params[:service_credentials_file], params[:firebase_cli_token], params[:debug])
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 = FirebaseAppDistributionV1::GoogleFirebaseAppdistroV1Group.new(
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
- client = init_client(params[:service_credentials_file], params[:firebase_cli_token], params[:debug])
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
- client = init_client(params[:service_credentials_file], params[:firebase_cli_token], params[:debug])
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,
@@ -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
- client = init_client(params[:service_credentials_file], params[:firebase_cli_token], params[:debug])
18
- fad_api_client = Client::FirebaseAppDistributionApiClient.new(client.authorization.access_token, params[:debug])
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
- app_id = params[:app]
21
- udids = fad_api_client.get_udids(app_id)
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
- UI.important("App Distribution fetched 0 tester UDIDs. Nothing written to output file.")
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[:udid]}\t#{tester_udid[:name]}\t#{tester_udid[:platform]}\n")
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: false,
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
- client = init_client(params[:service_credentials_file], params[:firebase_cli_token], params[:debug])
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,
@@ -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
- service_account(google_service_path, debug)
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("🔐 No authentication method found. Using cached Firebase CLI credentials.")
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 service_account(google_service_path, debug)
102
- service_account_credentials = Google::Auth::ServiceAccountCredentials.make_creds(
103
- json_key_io: File.open(google_service_path),
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}: \"#{google_service_path}\""
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 += ". #{debug_instructions}"
132
+ error_message += debug_instructions.to_s
116
133
  end
117
134
  UI.user_error!(error_message)
118
135
  end
@@ -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
@@ -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(",").map(&:strip)
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.split(':')[1])}/apps/#{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 init_client(service_credentials_file, firebase_cli_token, debug, timeout = nil)
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)
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
2
  module FirebaseAppDistribution
3
- VERSION = "0.7.3"
3
+ VERSION = "0.9.0"
4
4
  end
5
5
  end
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.7.3
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: 2023-09-07 00:00:00.000000000 Z
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.4.10
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
@@ -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