fastlane-plugin-firebase_app_distribution 0.6.0 → 0.7.0.pre.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (17) hide show
  1. checksums.yaml +4 -4
  2. data/lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_action.rb +150 -40
  3. data/lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_add_testers_action.rb +52 -9
  4. data/lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_create_group_action.rb +25 -4
  5. data/lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_delete_group_action.rb +11 -3
  6. data/lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_get_latest_release.rb +39 -6
  7. data/lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_get_udids.rb +2 -2
  8. data/lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_remove_testers_action.rb +57 -9
  9. data/lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb +1 -396
  10. data/lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_auth_client.rb +24 -19
  11. data/lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_error_message.rb +2 -3
  12. data/lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_helper.rb +31 -2
  13. data/lib/fastlane/plugin/firebase_app_distribution/version.rb +1 -1
  14. metadata +32 -7
  15. data/lib/fastlane/plugin/firebase_app_distribution/client/aab_info.rb +0 -42
  16. data/lib/fastlane/plugin/firebase_app_distribution/client/error_response.rb +0 -16
  17. data/lib/fastlane/plugin/firebase_app_distribution/helper/upload_status_response.rb +0 -77
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c8c5fed8eb93d2d6c62dee224fb08c366646a91b0ddd38d74689e5c34b0f355b
4
- data.tar.gz: 03ee7ef5569e510ae63370cd860ec6f118f6ce5d8771c2f961d9194609232709
3
+ metadata.gz: 0de4fec6f4206e3577c8e2d6e247d64336020336f0db820c732b7b08a3473848
4
+ data.tar.gz: dd9d4f3bb86846b3115c3311dbc413e837134cd8d0fecc1ba845328b0fa62420
5
5
  SHA512:
6
- metadata.gz: e0b67fbe3ac855aec3dd70be48726d44bcae8eb021a70984ff5274431b0e093c6849ad9ddf64f6186698ffa5701950acc53c602cb48583d520738a1bfa75f6b5
7
- data.tar.gz: ec59af9949e4d9086004c63561b2e74a3bf09b9a270b02a12e62d66a322198124c8019724ae7cd33f2f5097615dd9c068f416e3efa5bc48fe4b77e11133b6946
6
+ metadata.gz: cb67a198cbf50af6cf4eec6cae102b99ae2e3c64752a726e33812bbb761b6da0372bae5f3e905413f5a949a72896a9901ba3d0429c92a77d0dbdf1a849628d52
7
+ data.tar.gz: b2e7b60eb0e5831a3de41340c22f452577fa675cce46c9dd40eeabcc0ba5a5614f915202cedcab362783b3d71ed153e672b350813beb953d8f53c22103f71e82
@@ -2,10 +2,8 @@ require 'fastlane/action'
2
2
  require 'open3'
3
3
  require 'shellwords'
4
4
  require 'googleauth'
5
- require_relative '../helper/upload_status_response'
6
5
  require_relative '../helper/firebase_app_distribution_helper'
7
6
  require_relative '../helper/firebase_app_distribution_error_message'
8
- require_relative '../client/firebase_app_distribution_api_client'
9
7
  require_relative '../helper/firebase_app_distribution_auth_client'
10
8
 
11
9
  ## How should we document the usage of release notes?
@@ -15,19 +13,18 @@ module Fastlane
15
13
  FIREBASE_APP_DISTRO_RELEASE ||= :FIREBASE_APP_DISTRO_RELEASE
16
14
  end
17
15
 
16
+ # rubocop:disable Metrics/ClassLength
18
17
  class FirebaseAppDistributionAction < Action
19
18
  extend Auth::FirebaseAppDistributionAuthClient
20
19
  extend Helper::FirebaseAppDistributionHelper
21
20
 
22
21
  DEFAULT_UPLOAD_TIMEOUT_SECONDS = 300
22
+ MAX_POLLING_RETRIES = 60
23
+ POLLING_INTERVAL_SECONDS = 5
23
24
 
24
25
  def self.run(params)
25
26
  params.values # to validate all inputs before looking for the ipa/apk/aab
26
27
 
27
- if params[:debug]
28
- UI.important("Warning: Debug logging enabled. Output may include sensitive information.")
29
- end
30
-
31
28
  app_id = app_id_from_params(params)
32
29
  app_name = app_name_from_app_id(app_id)
33
30
  platform = lane_platform || platform_from_app_id(app_id)
@@ -37,33 +34,31 @@ module Fastlane
37
34
  UI.user_error!("Couldn't find binary at path #{binary_path}") unless File.exist?(binary_path)
38
35
  binary_type = binary_type_from_path(binary_path)
39
36
 
40
- auth_token = fetch_auth_token(
41
- params[:service_credentials_file], params[:firebase_cli_token], params[:debug]
42
- )
43
- fad_api_client = Client::FirebaseAppDistributionApiClient.new(auth_token, params[:debug])
37
+ client = init_client(params[:service_credentials_file], params[:firebase_cli_token], params[:debug])
44
38
 
45
39
  # If binary is an AAB, get the AAB info for this app, which includes the integration state and certificate data
46
40
  if binary_type == :AAB
47
- aab_info = fad_api_client.get_aab_info(app_name)
41
+ aab_info = get_aab_info(client, app_name)
48
42
  validate_aab_setup!(aab_info)
49
43
  end
50
44
 
51
- upload_timeout = get_upload_timeout(params)
45
+ binary_type = binary_type_from_path(binary_path)
46
+ UI.message("⌛ Uploading the #{binary_type}.")
52
47
 
53
- upload_status_response = fad_api_client.upload(app_name, binary_path, platform.to_s, upload_timeout)
54
- release_name = upload_status_response.release_name
55
- release = upload_status_response.release
48
+ timeout = get_upload_timeout(params)
49
+ operation = upload_binary(app_name, binary_path, client, timeout)
50
+ release = poll_upload_release_operation(client, operation, binary_type)
56
51
 
57
- if binary_type == :AAB && aab_info && !aab_info.certs_provided?
58
- updated_aab_info = fad_api_client.get_aab_info(app_name)
59
- if updated_aab_info.certs_provided?
52
+ if binary_type == :AAB && aab_info && !aab_certs_included?(aab_info.test_certificate)
53
+ updated_aab_info = get_aab_info(client, app_name)
54
+ if aab_certs_included?(updated_aab_info.test_certificate)
60
55
  UI.message("After you upload an AAB for the first time, App Distribution " \
61
56
  "generates a new test certificate. All AAB uploads are re-signed with this test " \
62
57
  "certificate. Use the certificate fingerprints below to register your app " \
63
58
  "signing key with API providers, such as Google Sign-In and Google Maps.\n" \
64
- "MD-1 certificate fingerprint: #{updated_aab_info.md5_certificate_hash}\n" \
65
- "SHA-1 certificate fingerprint: #{updated_aab_info.sha1_certificate_hash}\n" \
66
- "SHA-256 certificate fingerprint: #{updated_aab_info.sha256_certificate_hash}")
59
+ "MD-1 certificate fingerprint: #{updated_aab_info.test_certificate.hash_md5}\n" \
60
+ "SHA-1 certificate fingerprint: #{updated_aab_info.test_certificate.hash_sha1}\n" \
61
+ "SHA-256 certificate fingerprint: #{updated_aab_info.test_certificate.hash_sha256}")
67
62
  end
68
63
  end
69
64
 
@@ -71,30 +66,35 @@ module Fastlane
71
66
  if release_notes.nil? || release_notes.empty?
72
67
  UI.message("⏩ No release notes passed in. Skipping this step.")
73
68
  else
74
- release = fad_api_client.update_release_notes(release_name, release_notes)
69
+ release.release_notes = Google::Apis::FirebaseappdistributionV1::GoogleFirebaseAppdistroV1ReleaseNotes.new(
70
+ text: release_notes
71
+ )
72
+ release = update_release(client, release)
75
73
  end
76
74
 
77
75
  testers = get_value_from_value_or_file(params[:testers], params[:testers_file])
78
76
  groups = get_value_from_value_or_file(params[:groups], params[:groups_file])
79
77
  emails = string_to_array(testers)
80
78
  group_aliases = string_to_array(groups)
81
- fad_api_client.distribute(release_name, emails, group_aliases)
82
- UI.success("🎉 App Distribution upload finished successfully. Setting Actions.lane_context[SharedValues::FIREBASE_APP_DISTRO_RELEASE] to the uploaded release.")
83
-
84
- if upload_status_response.firebase_console_uri
85
- UI.message("🔗 View this release in the Firebase console: #{upload_status_response.firebase_console_uri}")
79
+ if present?(emails) || present?(group_aliases)
80
+ request = Google::Apis::FirebaseappdistributionV1::GoogleFirebaseAppdistroV1DistributeReleaseRequest.new(
81
+ tester_emails: emails,
82
+ group_aliases: group_aliases
83
+ )
84
+ distribute_release(client, release, request)
85
+ else
86
+ UI.message("⏩ No testers or groups passed in. Skipping this step.")
86
87
  end
87
88
 
88
- if upload_status_response.testing_uri
89
- UI.message("🔗 Share this release with testers who have access: #{upload_status_response.testing_uri}")
90
- end
89
+ UI.success("🎉 App Distribution upload finished successfully. Setting Actions.lane_context[SharedValues::FIREBASE_APP_DISTRO_RELEASE] to the uploaded release.")
91
90
 
92
- if upload_status_response.binary_download_uri
93
- UI.message("🔗 Download the release binary (link expires in 1 hour): #{upload_status_response.binary_download_uri}")
94
- end
91
+ UI.message("🔗 View this release in the Firebase console: #{release.firebase_console_uri}") if release.firebase_console_uri
92
+ UI.message("🔗 Share this release with testers who have access: #{release.testing_uri}") if release.testing_uri
93
+ UI.message("🔗 Download the release binary (link expires in 1 hour): #{release.binary_download_uri}") if release.binary_download_uri
95
94
 
96
- Actions.lane_context[SharedValues::FIREBASE_APP_DISTRO_RELEASE] = release
97
- release
95
+ release_hash = deep_symbolize_keys(JSON.parse(release.to_json))
96
+ Actions.lane_context[SharedValues::FIREBASE_APP_DISTRO_RELEASE] = release_hash
97
+ release_hash
98
98
  end
99
99
 
100
100
  def self.description
@@ -174,15 +174,15 @@ module Fastlane
174
174
  end
175
175
 
176
176
  def self.validate_aab_setup!(aab_info)
177
- if aab_info && aab_info.integration_state != AabInfo::AabState::INTEGRATED && aab_info.integration_state != AabInfo::AabState::UNAVAILABLE
177
+ if aab_info && aab_info.integration_state != 'INTEGRATED' && aab_info.integration_state != 'AAB_STATE_UNAVAILABLE'
178
178
  case aab_info.integration_state
179
- when AabInfo::AabState::PLAY_ACCOUNT_NOT_LINKED
179
+ when 'PLAY_ACCOUNT_NOT_LINKED'
180
180
  UI.user_error!(ErrorMessage::PLAY_ACCOUNT_NOT_LINKED)
181
- when AabInfo::AabState::APP_NOT_PUBLISHED
181
+ when 'APP_NOT_PUBLISHED'
182
182
  UI.user_error!(ErrorMessage::APP_NOT_PUBLISHED)
183
- when AabInfo::AabState::NO_APP_WITH_GIVEN_BUNDLE_ID_IN_PLAY_ACCOUNT
183
+ when 'NO_APP_WITH_GIVEN_BUNDLE_ID_IN_PLAY_ACCOUNT'
184
184
  UI.user_error!(ErrorMessage::NO_APP_WITH_GIVEN_BUNDLE_ID_IN_PLAY_ACCOUNT)
185
- when AabInfo::AabState::PLAY_IAS_TERMS_NOT_ACCEPTED
185
+ when 'PLAY_IAS_TERMS_NOT_ACCEPTED'
186
186
  UI.user_error!(ErrorMessage::PLAY_IAS_TERMS_NOT_ACCEPTED)
187
187
  else
188
188
  UI.user_error!(ErrorMessage.aab_upload_error(aab_info.integration_state))
@@ -190,12 +190,121 @@ module Fastlane
190
190
  end
191
191
  end
192
192
 
193
+ def self.aab_certs_included?(test_certificate)
194
+ present?(test_certificate.hash_md5) && present?(test_certificate.hash_sha1) &&
195
+ present?(test_certificate.hash_sha256)
196
+ end
197
+
198
+ def self.aab_info_name(app_name)
199
+ "#{app_name}/aabInfo"
200
+ end
201
+
193
202
  def self.release_notes(params)
194
203
  release_notes_param =
195
204
  get_value_from_value_or_file(params[:release_notes], params[:release_notes_file])
196
205
  release_notes_param || Actions.lane_context[SharedValues::FL_CHANGELOG]
197
206
  end
198
207
 
208
+ def self.poll_upload_release_operation(client, operation, binary_type)
209
+ operation = client.get_project_app_release_operation(operation.name)
210
+ MAX_POLLING_RETRIES.times do
211
+ if operation.done && operation.response && operation.response['release']
212
+ release = extract_release(operation)
213
+ result = operation.response['result']
214
+ if result == 'RELEASE_UPDATED'
215
+ UI.success("✅ Uploaded #{binary_type} successfully; updated provisioning profile of existing release #{release_version(release)}.")
216
+ break
217
+ elsif result == 'RELEASE_UNMODIFIED'
218
+ UI.success("✅ The same #{binary_type} was found in release #{release_version(release)} with no changes, skipping.")
219
+ break
220
+ else
221
+ UI.success("✅ Uploaded #{binary_type} successfully and created release #{release_version(release)}.")
222
+ end
223
+ break
224
+ elsif !operation.done
225
+ sleep(POLLING_INTERVAL_SECONDS)
226
+ operation = client.get_project_app_release_operation(operation.name)
227
+ else
228
+ if operation.error && operation.error.message
229
+ UI.user_error!("#{ErrorMessage.upload_binary_error(binary_type)}: #{operation.error.message}")
230
+ else
231
+ UI.user_error!(ErrorMessage.upload_binary_error(binary_type))
232
+ end
233
+ end
234
+ end
235
+ extract_release(operation)
236
+ end
237
+
238
+ def self.upload_binary(app_name, binary_path, client, timeout)
239
+ options = Google::Apis::RequestOptions.new
240
+ options.max_elapsed_time = timeout
241
+ options.header = {
242
+ 'Content-Type' => 'application/octet-stream',
243
+ 'X-Goog-Upload-File-Name' => File.basename(binary_path),
244
+ 'X-Goog-Upload-Protocol' => 'raw'
245
+ }
246
+ # For some reason calling the client.upload_medium returns nil when
247
+ # it should return a long running operation object, so we make a
248
+ # standard http call instead and convert it to a long running object
249
+ # https://github.com/googleapis/google-api-ruby-client/blob/main/generated/google-apis-firebaseappdistribution_v1/lib/google/apis/firebaseappdistribution_v1/service.rb#L79
250
+ # TODO(kbolay) Prefer client.upload_medium
251
+ response = client.http(
252
+ :post,
253
+ "https://firebaseappdistribution.googleapis.com/upload/v1/#{app_name}/releases:upload",
254
+ body: File.open(binary_path, 'rb').read,
255
+ options: options
256
+ )
257
+
258
+ Google::Apis::FirebaseappdistributionV1::GoogleLongrunningOperation.from_json(response)
259
+ end
260
+
261
+ def self.extract_release(operation)
262
+ Google::Apis::FirebaseappdistributionV1::GoogleFirebaseAppdistroV1Release.from_json(operation.response['release'].to_json)
263
+ end
264
+
265
+ def self.release_version(release)
266
+ if release.display_version && release.build_version
267
+ "#{release.display_version} (#{release.build_version})"
268
+ elsif release.display_version
269
+ release.display_version
270
+ else
271
+ release.build_version
272
+ end
273
+ end
274
+
275
+ def self.get_aab_info(client, app_name)
276
+ client.get_project_app_aab_info(aab_info_name(app_name))
277
+ rescue Google::Apis::Error => err
278
+ case err.status_code.to_i
279
+ when 404
280
+ UI.user_error!(ErrorMessage::INVALID_APP_ID)
281
+ else
282
+ UI.crash!(err)
283
+ end
284
+ end
285
+
286
+ def self.update_release(client, release)
287
+ client.patch_project_app_release(release.name, release)
288
+ rescue Google::Apis::Error => err
289
+ case err.status_code.to_i
290
+ when 400
291
+ UI.user_error!("#{ErrorMessage::INVALID_RELEASE_NOTES}: #{err.body}")
292
+ else
293
+ UI.crash!(err)
294
+ end
295
+ end
296
+
297
+ def self.distribute_release(client, release, request)
298
+ client.distribute_project_app_release(release.name, request)
299
+ rescue Google::Apis::Error => err
300
+ case err.status_code.to_i
301
+ when 400
302
+ UI.user_error!("#{ErrorMessage::INVALID_TESTERS}\nEmails: #{request.tester_emails} \nGroup Aliases: #{request.group_aliases}")
303
+ else
304
+ UI.crash!(err)
305
+ end
306
+ end
307
+
199
308
  def self.available_options
200
309
  [
201
310
  # iOS Specific
@@ -314,5 +423,6 @@ module Fastlane
314
423
  ]
315
424
  end
316
425
  end
426
+ # rubocop:enable Metrics/ClassLength
317
427
  end
318
428
  end
@@ -11,14 +11,15 @@ module Fastlane
11
11
  extend Helper::FirebaseAppDistributionHelper
12
12
 
13
13
  def self.run(params)
14
- auth_token = fetch_auth_token(params[:service_credentials_file], params[:firebase_cli_token])
15
- fad_api_client = Client::FirebaseAppDistributionApiClient.new(auth_token, params[:debug])
14
+ client = init_client(params[:service_credentials_file], params[:firebase_cli_token], params[:debug])
16
15
 
17
16
  if blank?(params[:emails]) && blank?(params[:file])
18
17
  UI.user_error!("Must specify `emails` or `file`.")
19
18
  end
20
19
 
21
20
  emails = string_to_array(get_value_from_value_or_file(params[:emails], params[:file]))
21
+ project_number = params[:project_number]
22
+ group_alias = params[:group_alias]
22
23
 
23
24
  UI.user_error!("Must pass at least one email") if blank?(emails)
24
25
 
@@ -26,13 +27,10 @@ module Fastlane
26
27
  UI.user_error!("A maximum of 1000 testers can be added at a time.")
27
28
  end
28
29
 
29
- UI.message("⏳ Adding #{emails.count} testers to project #{params[:project_number]}...")
30
-
31
- fad_api_client.add_testers(params[:project_number], emails)
32
-
33
- unless blank?(params[:group_alias])
34
- UI.message("⏳ Adding testers to group #{params[:group_alias]}...")
35
- fad_api_client.add_testers_to_group(params[:project_number], params[:group_alias], emails)
30
+ if present?(group_alias)
31
+ add_testers_to_group(client, project_number, emails, group_alias)
32
+ else
33
+ add_testers_to_project(client, emails, project_number)
36
34
  end
37
35
 
38
36
  # The add_testers response lists all the testers from the request
@@ -95,6 +93,51 @@ module Fastlane
95
93
  def self.is_supported?(platform)
96
94
  true
97
95
  end
96
+
97
+ def self.add_testers_to_project(client, emails, project_number)
98
+ UI.message("⏳ Adding #{emails.count} testers to project #{project_number}...")
99
+ request = Google::Apis::FirebaseappdistributionV1::GoogleFirebaseAppdistroV1BatchAddTestersRequest.new(
100
+ emails: emails
101
+ )
102
+
103
+ begin
104
+ client.batch_project_tester_add(project_name(project_number), request)
105
+ rescue Google::Apis::Error => err
106
+ case err.status_code.to_i
107
+ when 400
108
+ UI.user_error!(ErrorMessage::INVALID_EMAIL_ADDRESS)
109
+ when 404
110
+ UI.user_error!(ErrorMessage::INVALID_PROJECT)
111
+ when 429
112
+ UI.user_error!(ErrorMessage::TESTER_LIMIT_VIOLATION)
113
+ else
114
+ UI.crash!(err)
115
+ end
116
+ end
117
+ end
118
+
119
+ def self.add_testers_to_group(client, project_number, emails, group_alias)
120
+ UI.message("⏳ Adding testers to group #{group_alias}...")
121
+ request = Google::Apis::FirebaseappdistributionV1::GoogleFirebaseAppdistroV1BatchJoinGroupRequest.new(
122
+ emails: emails,
123
+ create_missing_testers: true
124
+ )
125
+
126
+ begin
127
+ client.batch_project_group_join(group_name(project_number, group_alias), request)
128
+ rescue Google::Apis::Error => err
129
+ case err.status_code.to_i
130
+ when 400
131
+ UI.user_error!(ErrorMessage::INVALID_EMAIL_ADDRESS)
132
+ when 404
133
+ UI.user_error!(ErrorMessage::INVALID_TESTER_GROUP)
134
+ when 429
135
+ UI.user_error!(ErrorMessage::TESTER_LIMIT_VIOLATION)
136
+ else
137
+ UI.crash!(err)
138
+ end
139
+ end
140
+ end
98
141
  end
99
142
  end
100
143
  end
@@ -1,5 +1,6 @@
1
1
  require 'fastlane/action'
2
2
  require 'fastlane_core/ui/ui'
3
+ require 'google/apis/firebaseappdistribution_v1'
3
4
 
4
5
  require_relative '../helper/firebase_app_distribution_helper'
5
6
  require_relative '../helper/firebase_app_distribution_auth_client'
@@ -10,10 +11,9 @@ module Fastlane
10
11
  extend Auth::FirebaseAppDistributionAuthClient
11
12
  extend Helper::FirebaseAppDistributionHelper
12
13
 
13
- def self.run(params)
14
- auth_token = fetch_auth_token(params[:service_credentials_file], params[:firebase_cli_token])
15
- fad_api_client = Client::FirebaseAppDistributionApiClient.new(auth_token, params[:debug])
14
+ FirebaseAppDistributionV1 = Google::Apis::FirebaseappdistributionV1
16
15
 
16
+ def self.run(params)
17
17
  if blank?(params[:alias])
18
18
  UI.user_error!("Must specify `alias`.")
19
19
  end
@@ -22,13 +22,34 @@ module Fastlane
22
22
  UI.user_error!("Must specify `display_name`.")
23
23
  end
24
24
 
25
+ client = init_client(params[:service_credentials_file], params[:firebase_cli_token], params[:debug])
26
+
25
27
  project_number = params[:project_number]
26
28
  group_alias = params[:alias]
27
29
  display_name = params[:display_name]
28
30
 
29
31
  UI.message("⏳ Creating tester group '#{group_alias} (#{display_name})' in project #{project_number}...")
30
32
 
31
- fad_api_client.create_group(project_number, group_alias, display_name)
33
+ parent = project_name(project_number)
34
+ group = FirebaseAppDistributionV1::GoogleFirebaseAppdistroV1Group.new(
35
+ name: group_name(project_number, group_alias),
36
+ display_name: display_name
37
+ )
38
+
39
+ begin
40
+ client.create_project_group(parent, group, group_id: group_alias)
41
+ rescue Google::Apis::Error => err
42
+ case err.status_code.to_i
43
+ when 400
44
+ UI.user_error!(ErrorMessage::INVALID_TESTER_GROUP_NAME)
45
+ when 404
46
+ UI.user_error!(ErrorMessage::INVALID_PROJECT)
47
+ when 409
48
+ UI.important("Tester group #{group_alias} already exists.")
49
+ else
50
+ UI.crash!(err)
51
+ end
52
+ end
32
53
 
33
54
  UI.success("✅ Group created successfully.")
34
55
  end
@@ -11,8 +11,7 @@ module Fastlane
11
11
  extend Helper::FirebaseAppDistributionHelper
12
12
 
13
13
  def self.run(params)
14
- auth_token = fetch_auth_token(params[:service_credentials_file], params[:firebase_cli_token])
15
- fad_api_client = Client::FirebaseAppDistributionApiClient.new(auth_token, params[:debug])
14
+ client = init_client(params[:service_credentials_file], params[:firebase_cli_token], params[:debug])
16
15
 
17
16
  if blank?(params[:alias])
18
17
  UI.user_error!("Must specify `alias`.")
@@ -23,7 +22,16 @@ module Fastlane
23
22
 
24
23
  UI.message("⏳ Deleting tester group '#{group_alias}' in project #{project_number}...")
25
24
 
26
- fad_api_client.delete_group(project_number, group_alias)
25
+ begin
26
+ client.delete_project_group(group_name(project_number, group_alias))
27
+ rescue Google::Apis::Error => err
28
+ case err.status_code.to_i
29
+ when 404
30
+ UI.user_error!(ErrorMessage::INVALID_TESTER_GROUP)
31
+ else
32
+ UI.crash!(err)
33
+ end
34
+ end
27
35
 
28
36
  UI.success("✅ Group deleted successfully.")
29
37
  end
@@ -1,5 +1,5 @@
1
1
  require 'fastlane/action'
2
- require_relative '../client/firebase_app_distribution_api_client'
2
+ require 'google/apis/firebaseappdistribution_v1'
3
3
  require_relative '../helper/firebase_app_distribution_auth_client'
4
4
  require_relative '../helper/firebase_app_distribution_helper'
5
5
 
@@ -13,23 +13,53 @@ module Fastlane
13
13
  extend Helper::FirebaseAppDistributionHelper
14
14
 
15
15
  def self.run(params)
16
- auth_token = fetch_auth_token(params[:service_credentials_file], params[:firebase_cli_token])
17
- fad_api_client = Client::FirebaseAppDistributionApiClient.new(auth_token, params[:debug])
16
+ client = init_client(params[:service_credentials_file], params[:firebase_cli_token], params[:debug])
18
17
 
19
18
  UI.message("⏳ Fetching latest release for app #{params[:app]}...")
20
19
 
21
- releases = fad_api_client.list_releases(app_name_from_app_id(params[:app]), 1)[:releases] || []
22
- if releases.empty?
20
+ parent = app_name_from_app_id(params[:app])
21
+
22
+ begin
23
+ releases = client.list_project_app_releases(parent, page_size: 1).releases
24
+ rescue Google::Apis::Error => err
25
+ if err.status_code.to_i == 404
26
+ UI.user_error!("#{ErrorMessage::INVALID_APP_ID}: #{params[:app]}")
27
+ else
28
+ UI.crash!(err)
29
+ end
30
+ end
31
+
32
+ if releases.nil? || releases.empty?
23
33
  latest_release = nil
24
34
  UI.important("No releases for app #{params[:app]} found in App Distribution. Returning nil and setting Actions.lane_context[SharedValues::FIREBASE_APP_DISTRO_LATEST_RELEASE].")
25
35
  else
26
- latest_release = releases[0]
36
+ # latest_release = append_json_style_fields(response.releases[0].to_h)
37
+ latest_release = map_release_hash(releases[0])
27
38
  UI.success("✅ Latest release fetched successfully. Returning release and setting Actions.lane_context[SharedValues::FIREBASE_APP_DISTRO_LATEST_RELEASE].")
28
39
  end
29
40
  Actions.lane_context[SharedValues::FIREBASE_APP_DISTRO_LATEST_RELEASE] = latest_release
30
41
  return latest_release
31
42
  end
32
43
 
44
+ def self.map_release_hash(release)
45
+ {
46
+ name: release.name,
47
+ releaseNotes: map_release_notes_hash(release.release_notes),
48
+ displayVersion: release.display_version,
49
+ buildVersion: release.build_version,
50
+ binaryDownloadUri: release.binary_download_uri,
51
+ firebaseConsoleUri: release.firebase_console_uri,
52
+ testingUri: release.testing_uri,
53
+ createTime: release.create_time
54
+ }
55
+ end
56
+
57
+ def self.map_release_notes_hash(release_notes)
58
+ return nil if release_notes.nil?
59
+
60
+ { text: release_notes.text }
61
+ end
62
+
33
63
  #####################################################
34
64
  # @!group Documentation
35
65
  #####################################################
@@ -106,6 +136,9 @@ module Fastlane
106
136
  },
107
137
  displayVersion: "1.2.3",
108
138
  buildVersion: "10",
139
+ binaryDownloadUri: "<URI>",
140
+ firebaseConsoleUri: "<URI>",
141
+ testingUri: "<URI>",
109
142
  createTime: "2021-10-06T15:01:23Z"
110
143
  }
111
144
  end
@@ -14,8 +14,8 @@ module Fastlane
14
14
  extend Helper::FirebaseAppDistributionHelper
15
15
 
16
16
  def self.run(params)
17
- auth_token = fetch_auth_token(params[:service_credentials_file], params[:firebase_cli_token])
18
- fad_api_client = Client::FirebaseAppDistributionApiClient.new(auth_token, params[:debug])
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])
19
19
 
20
20
  app_id = params[:app]
21
21
  udids = fad_api_client.get_udids(app_id)
@@ -11,14 +11,15 @@ module Fastlane
11
11
  extend Helper::FirebaseAppDistributionHelper
12
12
 
13
13
  def self.run(params)
14
- auth_token = fetch_auth_token(params[:service_credentials_file], params[:firebase_cli_token])
15
- fad_api_client = Client::FirebaseAppDistributionApiClient.new(auth_token, params[:debug])
14
+ client = init_client(params[:service_credentials_file], params[:firebase_cli_token], params[:debug])
16
15
 
17
16
  if blank?(params[:emails]) && blank?(params[:file])
18
17
  UI.user_error!("Must specify `emails` or `file`.")
19
18
  end
20
19
 
21
20
  emails = string_to_array(get_value_from_value_or_file(params[:emails], params[:file]))
21
+ project_number = params[:project_number]
22
+ group_alias = params[:group_alias]
22
23
 
23
24
  UI.user_error!("Must pass at least one email") if blank?(emails)
24
25
 
@@ -26,11 +27,11 @@ module Fastlane
26
27
  UI.user_error!("A maximum of 1000 testers can be removed at a time.")
27
28
  end
28
29
 
29
- UI.message("⏳ Removing #{emails.count} testers from project #{params[:project_number]}...")
30
-
31
- count = fad_api_client.remove_testers(params[:project_number], emails)
32
-
33
- UI.success("✅ #{count} tester(s) removed successfully.")
30
+ if present?(group_alias)
31
+ remove_testers_from_group(client, project_number, group_alias, emails)
32
+ else
33
+ remove_testers_from_project(client, project_number, emails)
34
+ end
34
35
  end
35
36
 
36
37
  def self.description
@@ -55,14 +56,19 @@ module Fastlane
55
56
  optional: false),
56
57
  FastlaneCore::ConfigItem.new(key: :emails,
57
58
  env_name: "FIREBASEAPPDISTRO_REMOVE_TESTERS_EMAILS",
58
- description: "Comma separated list of tester emails to be deleted. A maximum of 1000 testers can be deleted at a time",
59
+ description: "Comma separated list of tester emails to be deleted (or removed from a group if a group alias is specified). A maximum of 1000 testers can be deleted/removed at a time",
59
60
  optional: true,
60
61
  type: String),
61
62
  FastlaneCore::ConfigItem.new(key: :file,
62
63
  env_name: "FIREBASEAPPDISTRO_REMOVE_TESTERS_FILE",
63
- description: "Path to a file containing a comma separated list of tester emails to be deleted. A maximum of 1000 testers can be deleted at a time",
64
+ description: "Path to a file containing a comma separated list of tester emails to be deleted (or removed from a group if a group alias is specified). A maximum of 1000 testers can be deleted/removed at a time",
64
65
  optional: true,
65
66
  type: String),
67
+ FastlaneCore::ConfigItem.new(key: :group_alias,
68
+ env_name: "FIREBASEAPPDISTRO_REMOVE_TESTERS_GROUP_ALIAS",
69
+ description: "Alias of the group to remove the specified testers from. Testers will not be deleted from the project",
70
+ optional: true,
71
+ type: String),
66
72
  FastlaneCore::ConfigItem.new(key: :service_credentials_file,
67
73
  description: "Path to Google service credentials file",
68
74
  optional: true,
@@ -83,6 +89,48 @@ module Fastlane
83
89
  def self.is_supported?(platform)
84
90
  true
85
91
  end
92
+
93
+ def self.remove_testers_from_project(client, project_number, emails)
94
+ UI.message("⏳ Removing #{emails.count} testers from project #{project_number}...")
95
+ request = Google::Apis::FirebaseappdistributionV1::GoogleFirebaseAppdistroV1BatchRemoveTestersRequest.new(
96
+ emails: emails
97
+ )
98
+
99
+ begin
100
+ response = client.batch_project_tester_remove(project_name(project_number), request)
101
+ rescue Google::Apis::Error => err
102
+ case err.status_code.to_i
103
+ when 404
104
+ UI.user_error!(ErrorMessage::INVALID_PROJECT)
105
+ else
106
+ UI.crash!(err)
107
+ end
108
+ end
109
+
110
+ UI.success("✅ #{response.emails.count} tester(s) removed successfully.")
111
+ end
112
+
113
+ def self.remove_testers_from_group(client, project_number, group_alias, emails)
114
+ UI.message("⏳ Removing #{emails.count} testers from group #group_alias}...")
115
+ request = Google::Apis::FirebaseappdistributionV1::GoogleFirebaseAppdistroV1BatchLeaveGroupRequest.new(
116
+ emails: emails
117
+ )
118
+
119
+ begin
120
+ client.batch_project_group_leave(group_name(project_number, group_alias), request)
121
+ rescue Google::Apis::Error => err
122
+ case err.status_code.to_i
123
+ when 400
124
+ UI.user_error!(ErrorMessage::INVALID_EMAIL_ADDRESS)
125
+ when 404
126
+ UI.user_error!(ErrorMessage::INVALID_TESTER_GROUP)
127
+ else
128
+ UI.crash!(err)
129
+ end
130
+ end
131
+
132
+ UI.success("✅ Tester(s) removed successfully.")
133
+ end
86
134
  end
87
135
  end
88
136
  end