fastlane-plugin-firebase_app_distribution 0.2.0.pre.1 → 0.2.0.pre.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: ab7379d2d8feb6112f778c3b4a64e2f2181a099a
4
- data.tar.gz: bdb617b84fb387a5486faee45d23be3043a9d9af
2
+ SHA256:
3
+ metadata.gz: 7387fdbb5db28a63ea91ef6e4feb290f4cdb71b93e8c7132595098f94f1ffc16
4
+ data.tar.gz: d24c551ec1c81f90ca38307aa75f7c3884982fdc58de2911f54b5971056a4a99
5
5
  SHA512:
6
- metadata.gz: 69f808e806793eb81839ac45241445c8764884f35c92c25066c86121c5ecb7ebf981371ed347fceadbfb0161df03ac6613fbd3f2aebe42a935783ca55d239df5
7
- data.tar.gz: 83c298cdfd08b945957f10f9194142423418b89ee5dc17a2718b1506013e79fbba2f94ad11e1b7c0b92a874c3e69731eba327e66f393bb61b99fae0bf423d5b5
6
+ metadata.gz: fe83dbc6eaca609c5471732598f004775ff2139d9d24487bb5021d821a909dfce61f955166c3ed1e115186909c7b455a0578237923845a5015d5ac776c57fe5c
7
+ data.tar.gz: 440ac81fb3b05743c511d22f2096c099a636cfc6df06a8d4a8703f9dff8d727c4841f6218e4503cec22d765d2379dc5bbca4ae76bc2b98dcb79007d2b45533ce
@@ -21,9 +21,8 @@ module Fastlane
21
21
 
22
22
  def self.run(params)
23
23
  params.values # to validate all inputs before looking for the ipa/apk
24
- auth_token = fetch_auth_token(params[:service_credentials_file])
25
- fad_api_client = Client::FirebaseAppDistributionApiClient.new(auth_token)
26
- platform = Actions.lane_context[Actions::SharedValues::PLATFORM_NAME]
24
+ auth_token = fetch_auth_token(params[:service_credentials_file], params[:firebase_cli_token])
25
+ fad_api_client = Client::FirebaseAppDistributionApiClient.new(auth_token, platform)
27
26
  binary_path = params[:ipa_path] || params[:apk_path]
28
27
 
29
28
  if params[:app] # Set app_id if it is specified as a parameter
@@ -38,7 +37,7 @@ module Fastlane
38
37
  if app_id.nil?
39
38
  UI.crash!(ErrorMessage::MISSING_APP_ID)
40
39
  end
41
- release_id = fad_api_client.upload(app_id, binary_path)
40
+ release_id = fad_api_client.upload(app_id, binary_path, platform.to_s)
42
41
  if release_id.nil?
43
42
  return
44
43
  end
@@ -67,9 +66,11 @@ module Fastlane
67
66
  "Release your beta builds with Firebase App Distribution"
68
67
  end
69
68
 
70
- def self.available_options
71
- platform = Actions.lane_context[Actions::SharedValues::PLATFORM_NAME]
69
+ def self.platform
70
+ @platform ||= Actions.lane_context[Actions::SharedValues::PLATFORM_NAME]
71
+ end
72
72
 
73
+ def self.available_options
73
74
  if platform == :ios || platform.nil?
74
75
  ipa_path_default = Dir["*.ipa"].sort_by { |x| File.mtime(x) }.last
75
76
  end
@@ -9,8 +9,13 @@ module Fastlane
9
9
  MAX_POLLING_RETRIES = 60
10
10
  POLLING_INTERVAL_SECONDS = 2
11
11
 
12
- def initialize(auth_token)
12
+ def initialize(auth_token, platform)
13
13
  @auth_token = auth_token
14
+ if platform == :ios || platform.nil?
15
+ @binary_type = "IPA"
16
+ else
17
+ @binary_type = "APK"
18
+ end
14
19
  end
15
20
 
16
21
  # Enables tester access to the specified app release. Skips this
@@ -38,6 +43,7 @@ module Fastlane
38
43
  rescue Faraday::ClientError
39
44
  UI.user_error!("#{ErrorMessage::INVALID_TESTERS} \nEmails: #{emails} \nGroups: #{group_ids}")
40
45
  end
46
+ UI.success("Added testers/groups successfully.")
41
47
  end
42
48
 
43
49
  # Posts notes for the specified app release. Skips this
@@ -71,7 +77,7 @@ module Fastlane
71
77
  begin
72
78
  binary_hash = Digest::SHA256.hexdigest(File.open(binary_path).read)
73
79
  rescue Errno::ENOENT
74
- UI.crash!("#{ErrorMessage::APK_NOT_FOUND}: #{binary_path}")
80
+ UI.crash!("#{ErrorMessage.binary_not_found(@binary_type)}: #{binary_path}")
75
81
  end
76
82
 
77
83
  begin
@@ -88,14 +94,17 @@ module Fastlane
88
94
  return upload_token_format(response.body[:appId], response.body[:projectNumber], binary_hash)
89
95
  end
90
96
 
91
- def upload_binary(app_id, binary_path)
97
+ def upload_binary(app_id, binary_path, platform)
92
98
  connection.post(binary_upload_url(app_id), File.open(binary_path).read) do |request|
93
99
  request.headers["Authorization"] = "Bearer " + @auth_token
100
+ request.headers["X-APP-DISTRO-API-CLIENT-ID"] = "fastlane"
101
+ request.headers["X-APP-DISTRO-API-CLIENT-TYPE"] = platform
102
+ request.headers["X-APP-DISTRO-API-CLIENT-VERSION"] = Fastlane::FirebaseAppDistribution::VERSION
94
103
  end
95
104
  rescue Faraday::ResourceNotFound
96
105
  UI.crash!("#{ErrorMessage::INVALID_APP_ID}: #{app_id}")
97
106
  rescue Errno::ENOENT
98
- UI.crash!("#{ErrorMessage::APK_NOT_FOUND}: #{binary_path}")
107
+ UI.crash!("#{ErrorMessage.binary_not_found(@binary_type)}: #{binary_path}")
99
108
  end
100
109
 
101
110
  # Uploads the binary file if it has not already been uploaded
@@ -107,28 +116,36 @@ module Fastlane
107
116
  #
108
117
  # Returns the release_id on a successful release, otherwise returns nil.
109
118
  #
110
- # Throws an error if the number of polling retries exceeds MAX_POLLING_RETRIES
111
- def upload(app_id, binary_path)
119
+ # Throws a UI error if the number of polling retries exceeds MAX_POLLING_RETRIES
120
+ # Crashes if not able to upload the binary
121
+ def upload(app_id, binary_path, platform)
112
122
  upload_token = get_upload_token(app_id, binary_path)
113
123
  upload_status_response = get_upload_status(app_id, upload_token)
114
- if upload_status_response.success?
115
- UI.success("This APK/IPA has been uploaded before. Skipping upload step.")
124
+ if upload_status_response.success? || upload_status_response.already_uploaded?
125
+ UI.success("This #{@binary_type} has been uploaded before. Skipping upload step.")
116
126
  else
117
- UI.message("This APK has not been uploaded before.")
127
+ UI.message("This #{@binary_type} has not been uploaded before")
128
+ UI.message("Uploading the #{@binary_type}.")
129
+ unless upload_status_response.in_progress?
130
+ upload_binary(app_id, binary_path, platform)
131
+ end
118
132
  MAX_POLLING_RETRIES.times do
119
- if upload_status_response.success?
120
- UI.success("Uploaded APK/IPA Successfully!")
133
+ upload_status_response = get_upload_status(app_id, upload_token)
134
+ if upload_status_response.success? || upload_status_response.already_uploaded?
135
+ UI.success("Uploaded #{@binary_type} Successfully!")
121
136
  break
122
137
  elsif upload_status_response.in_progress?
123
138
  sleep(POLLING_INTERVAL_SECONDS)
124
139
  else
125
- UI.message("Uploading the APK/IPA.")
126
- upload_binary(app_id, binary_path)
140
+ if !upload_status_response.message.nil?
141
+ UI.user_error!("#{ErrorMessage.upload_binary_error(@binary_type)}: #{upload_status_response.message}")
142
+ else
143
+ UI.user_error!(ErrorMessage.upload_binary_error(@binary_type))
144
+ end
127
145
  end
128
- upload_status_response = get_upload_status(app_id, upload_token)
129
146
  end
130
147
  unless upload_status_response.success?
131
- UI.error("It took longer than expected to process your APK/IPA, please try again.")
148
+ UI.error("It took longer than expected to process your #{@binary_type}, please try again.")
132
149
  return nil
133
150
  end
134
151
  end
@@ -5,33 +5,74 @@ module Fastlane
5
5
  module FirebaseAppDistributionAuthClient
6
6
  TOKEN_CREDENTIAL_URI = "https://oauth2.googleapis.com/token"
7
7
 
8
- def fetch_auth_token(google_service_path)
8
+ # Returns the auth token for any of the auth methods (Firebase CLI token,
9
+ # Google service account, firebase-tools). To ensure that a specific
10
+ # auth method is used, unset all other auth variables/parameters to nil/empty
11
+ #
12
+ # args
13
+ # google_service_path - Absolute path to the Google service account file
14
+ # firebase_cli_token - Firebase CLI refresh token from login action or
15
+ # CI environment
16
+ #
17
+ # env variables
18
+ # GOOGLE_APPLICATION_CREDENTIALS - see google_service_path
19
+ # FIREBASE_TOKEN - see firebase_cli_token
20
+ #
21
+ # Crashes if given invalid or missing credentials
22
+ def fetch_auth_token(google_service_path, firebase_cli_token)
9
23
  if !google_service_path.nil? && !google_service_path.empty?
10
- service_account(google_service_path)
11
- elsif ENV["FIREBASE_TOKEN"]
12
- firebase_token
13
- elsif ENV["GOOGLE_APPLICATION_CREDENTIALS"]
14
- service_account(ENV["GOOGLE_APPLICATION_CREDENTIALS"])
24
+ UI.message("Authenticating with --service_credentials_file path parameter: #{google_service_path}")
25
+ token = service_account(google_service_path)
26
+ elsif !firebase_cli_token.nil? && !firebase_cli_token.empty?
27
+ UI.message("Authenticating with --firebase_cli_token parameter")
28
+ token = firebase_token(firebase_cli_token)
29
+ elsif !ENV["FIREBASE_TOKEN"].nil? && !ENV["FIREBASE_TOKEN"].empty?
30
+ UI.message("Authenticating with FIREBASE_TOKEN environment variable")
31
+ token = firebase_token(ENV["FIREBASE_TOKEN"])
32
+ elsif !ENV["GOOGLE_APPLICATION_CREDENTIALS"].nil? && !ENV["GOOGLE_APPLICATION_CREDENTIALS"].empty?
33
+ UI.message("Authenticating with GOOGLE_APPLICATION_CREDENTIALS environment variable: #{ENV['GOOGLE_APPLICATION_CREDENTIALS']}")
34
+ token = service_account(ENV["GOOGLE_APPLICATION_CREDENTIALS"])
35
+ elsif (refresh_token = refresh_token_from_firebase_tools)
36
+ UI.message("No authentication method specified. Using cached Firebase CLI credentials.")
37
+ token = firebase_token(refresh_token)
15
38
  else
16
- UI.crash!(ErrorMessage::MISSING_CREDENTIALS)
39
+ UI.user_error!(ErrorMessage::MISSING_CREDENTIALS)
17
40
  end
41
+ UI.success("Successfully authenticated!")
42
+ token
18
43
  end
19
44
 
20
45
  private
21
46
 
22
- def firebase_token
23
- begin
24
- client = Signet::OAuth2::Client.new(
25
- token_credential_uri: TOKEN_CREDENTIAL_URI,
26
- client_id: Fastlane::Actions::FirebaseAppDistributionLoginAction::CLIENT_ID,
27
- client_secret: Fastlane::Actions::FirebaseAppDistributionLoginAction::CLIENT_SECRET,
28
- refresh_token: ENV["FIREBASE_TOKEN"]
29
- )
30
- rescue Signet::AuthorizationError
31
- UI.crash!(ErrorMessage::REFRESH_TOKEN_ERROR)
47
+ def refresh_token_from_firebase_tools
48
+ if ENV["XDG_CONFIG_HOME"].nil? || ENV["XDG_CONFIG_HOME"].empty?
49
+ config_path = File.expand_path(".config/configstore/firebase-tools.json", "~")
50
+ else
51
+ config_path = File.expand_path("configstore/firebase-tools.json", ENV["XDG_CONFIG_HOME"])
52
+ end
53
+
54
+ if File.exist?(config_path)
55
+ begin
56
+ refresh_token = JSON.parse(File.read(config_path))['tokens']['refresh_token']
57
+ refresh_token unless refresh_token.nil? || refresh_token.empty?
58
+ # TODO: Catch parser errors, improve error handling here
59
+ # Returns nil when there is an empty "tokens" field in the firebase-tools json
60
+ rescue NoMethodError
61
+ end
32
62
  end
63
+ end
64
+
65
+ def firebase_token(refresh_token)
66
+ client = Signet::OAuth2::Client.new(
67
+ token_credential_uri: TOKEN_CREDENTIAL_URI,
68
+ client_id: Fastlane::Actions::FirebaseAppDistributionLoginAction::CLIENT_ID,
69
+ client_secret: Fastlane::Actions::FirebaseAppDistributionLoginAction::CLIENT_SECRET,
70
+ refresh_token: refresh_token
71
+ )
33
72
  client.fetch_access_token!
34
73
  client.access_token
74
+ rescue Signet::AuthorizationError
75
+ UI.user_error!(ErrorMessage::REFRESH_TOKEN_ERROR)
35
76
  end
36
77
 
37
78
  def service_account(google_service_path)
@@ -41,7 +82,9 @@ module Fastlane
41
82
  )
42
83
  service_account_credentials.fetch_access_token!["access_token"]
43
84
  rescue Errno::ENOENT
44
- UI.crash!("#{ErrorMessage::SERVICE_CREDENTIALS_NOT_FOUND}: #{google_service_path}")
85
+ UI.user_error!("#{ErrorMessage::SERVICE_CREDENTIALS_NOT_FOUND}: #{google_service_path}")
86
+ rescue Signet::AuthorizationError
87
+ UI.user_error!("#{ErrorMessage::SERVICE_CREDENTIALS_ERROR}: #{google_service_path}")
45
88
  end
46
89
  end
47
90
  end
@@ -1,16 +1,12 @@
1
1
  module ErrorMessage
2
- MISSING_CREDENTIALS = "Missing credentials. Please check that a refresh token was set or service credentials were passed in and try again"
3
- APK_NOT_FOUND = "Could not find the APK/IPA. Make sure you set the apk_path parameter to point to your APK/IPA"
2
+ MISSING_CREDENTIALS = "Missing authentication credentials. Check that your Firebase refresh token is set or that your service account file path is correct and try again."
4
3
  MISSING_APP_ID = "Missing app id. Please check that it was passed in and try again"
5
4
  SERVICE_CREDENTIALS_NOT_FOUND = "Service credentials file does not exist. Please check the service credentials path and try again"
6
5
  PARSE_SERVICE_CREDENTIALS_ERROR = "Failed to extract service account information from the service credentials file"
7
- PARSE_APK_METADATA_ERROR = "Failed to extract APK/IPA metadata from the APK/IPA path"
8
6
  UPLOAD_RELEASE_NOTES_ERROR = "App Distribution halted because it had a problem uploading release notes"
9
7
  UPLOAD_TESTERS_ERROR = "App Distribution halted because it had a problem adding testers/groups"
10
- UPLOAD_APK_ERROR = "App Distribution halted because it had a problem uploading the APK/IPA"
11
- APK_PROCESSING_ERROR = "App Distribution failed to process the APK/IPA"
12
8
  GET_RELEASE_TIMEOUT = "App Distribution failed to fetch release information"
13
- REFRESH_TOKEN_ERROR = "Could not generate credentials from the refresh token specified"
9
+ REFRESH_TOKEN_ERROR = "Could not generate credentials from the refresh token specified."
14
10
  GET_APP_ERROR = "App Distribution failed to fetch app information"
15
11
  APP_NOT_ONBOARDED_ERROR = "App Distribution not onboarded"
16
12
  GET_APP_NO_CONTACT_EMAIL_ERROR = "App Distribution could not find a contact email associated with this app. Contact Email"
@@ -18,4 +14,21 @@ module ErrorMessage
18
14
  INVALID_PATH = "Could not read content from"
19
15
  INVALID_TESTERS = "Could not enable access for testers. Ensure that the groups exist and the tester emails are formatted correctly"
20
16
  INVALID_RELEASE_ID = "App distribution failed to fetch release with id"
17
+ SERVICE_CREDENTIALS_ERROR = "Could not generate credentials from the service credentials file specified. Service Account Path"
18
+
19
+ def self.binary_not_found(binary_type)
20
+ "Could not find the #{binary_type}. Make sure you set the #{binary_type} path parameter to point to your #{binary_type}"
21
+ end
22
+
23
+ def self.parse_binary_metadata_error(binary_type)
24
+ "Failed to extract #{binary_type} metadata from the #{binary_type} path"
25
+ end
26
+
27
+ def self.upload_binary_error(binary_type)
28
+ "App Distribution halted because it had a problem uploading the #{binary_type}"
29
+ end
30
+
31
+ def self.binary_processing_error(binary_type)
32
+ "App Distribution failed to process the #{binary_type}"
33
+ end
21
34
  end
@@ -1,4 +1,5 @@
1
1
  require 'fastlane_core/ui/ui'
2
+ require 'cfpropertylist'
2
3
  module Fastlane
3
4
  UI = FastlaneCore::UI unless Fastlane.const_defined?("UI")
4
5
  module Helper
@@ -23,6 +24,10 @@ module Fastlane
23
24
  return string_array
24
25
  end
25
26
 
27
+ def parse_plist(path)
28
+ CFPropertyList.native_types(CFPropertyList::List.new(file: path).value)
29
+ end
30
+
26
31
  def get_ios_app_id_from_archive(path)
27
32
  app_path = parse_plist("#{path}/Info.plist")["ApplicationProperties"]["ApplicationPath"]
28
33
  UI.shell_error!("can't extract application path from Info.plist at #{path}") if app_path.empty?
@@ -12,7 +12,15 @@ class UploadStatusResponse
12
12
  end
13
13
 
14
14
  def in_progress?
15
- status == "IN_PROGRESS"
15
+ status == 'IN_PROGRESS'
16
+ end
17
+
18
+ def error?
19
+ status == 'ERROR'
20
+ end
21
+
22
+ def already_uploaded?
23
+ status == 'ALREADY_UPLOADED'
16
24
  end
17
25
 
18
26
  def release_hash
@@ -22,4 +30,8 @@ class UploadStatusResponse
22
30
  def release_id
23
31
  release_hash ? release_hash[:id] : nil
24
32
  end
33
+
34
+ def message
35
+ @response_json_hash[:message]
36
+ end
25
37
  end
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
2
  module FirebaseAppDistribution
3
- VERSION = "0.2.0.pre.1"
3
+ VERSION = "0.2.0.pre.2"
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.2.0.pre.1
4
+ version: 0.2.0.pre.2
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: 2020-07-28 00:00:00.000000000 Z
13
+ date: 2020-08-06 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: pry
@@ -177,8 +177,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
177
177
  - !ruby/object:Gem::Version
178
178
  version: 1.3.1
179
179
  requirements: []
180
- rubyforge_project:
181
- rubygems_version: 2.2.5
180
+ rubygems_version: 3.1.4
182
181
  signing_key:
183
182
  specification_version: 4
184
183
  summary: Release your beta builds to Firebase App Distribution. https://firebase.google.com/docs/app-distribution