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

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
- 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