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

Sign up to get free protection for your applications and to get access to all the features.
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