Skip to content

Commit

Permalink
Merge pull request #336 from firebase/lk/revert-uploads
Browse files Browse the repository at this point in the history
Use old API client for uploads and bump to 0.7.2.pre.1
  • Loading branch information
lfkellogg authored Aug 3, 2023
2 parents 65ca804 + ef69959 commit 267f503
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,29 @@ def self.run(params)
params[:debug],
timeout)

# If binary is an AAB, get the AAB info for this app, which includes the integration state
# and certificate data
# If binary is an AAB, get the AAB info for this app, which includes the integration state and certificate data
if binary_type == :AAB
aab_info = get_aab_info(client, app_name)
validate_aab_setup!(aab_info)
end

binary_type = binary_type_from_path(binary_path)
UI.message("⌛ Uploading the #{binary_type}.")
operation = upload_binary(app_name, binary_path, client, timeout)
release = poll_upload_release_operation(client, operation, binary_type)

# For some reason calling the client.upload_medium returns nil when
# it should return a long running operation object
# (https://github.com/googleapis/google-api-ruby-client/blob/main/generated/google-apis-firebaseappdistribution_v1/lib/google/apis/firebaseappdistribution_v1/service.rb#L79).
# We could use client.http, but is much slower
# (https://github.com/firebase/fastlane-plugin-firebase_app_distribution/issues/330),
# so we still use the old client for now.
# TODO(kbolay) Prefer client.upload_medium, assuming it is sufficiently fast
fad_api_client = Client::FirebaseAppDistributionApiClient.new(client.authorization.access_token, params[:debug])
operation_name = fad_api_client.upload_binary(app_name,
binary_path,
platform.to_s,
get_upload_timeout(params))

release = poll_upload_release_operation(client, operation_name, binary_type)

if binary_type == :AAB && aab_info && !aab_certs_included?(aab_info.test_certificate)
updated_aab_info = get_aab_info(client, app_name)
Expand Down Expand Up @@ -210,8 +222,8 @@ def self.release_notes(params)
release_notes_param || Actions.lane_context[SharedValues::FL_CHANGELOG]
end

def self.poll_upload_release_operation(client, operation, binary_type)
operation = client.get_project_app_release_operation(operation.name)
def self.poll_upload_release_operation(client, operation_name, binary_type)
operation = client.get_project_app_release_operation(operation_name)
MAX_POLLING_RETRIES.times do
if operation.done && operation.response && operation.response['release']
release = extract_release(operation)
Expand Down Expand Up @@ -245,30 +257,6 @@ def self.poll_upload_release_operation(client, operation, binary_type)
extract_release(operation)
end

def self.upload_binary(app_name, binary_path, client, timeout)
options = Google::Apis::RequestOptions.new
options.max_elapsed_time = timeout # includes retries (default = no retries)
options.header = {
'Content-Type' => 'application/octet-stream',
'X-Goog-Upload-File-Name' => File.basename(binary_path),
'X-Goog-Upload-Protocol' => 'raw'
}

# For some reason calling the client.upload_medium returns nil when
# it should return a long running operation object, so we make a
# standard http call instead and convert it to a long running object
# https://github.com/googleapis/google-api-ruby-client/blob/main/generated/google-apis-firebaseappdistribution_v1/lib/google/apis/firebaseappdistribution_v1/service.rb#L79
# TODO(kbolay) Prefer client.upload_medium
response = client.http(
:post,
"https://firebaseappdistribution.googleapis.com/upload/v1/#{app_name}/releases:upload",
body: File.open(binary_path, 'rb').read,
options: options
)

Google::Apis::FirebaseappdistributionV1::GoogleLongrunningOperation.from_json(response)
end

def self.extract_release(operation)
Google::Apis::FirebaseappdistributionV1::GoogleFirebaseAppdistroV1Release.from_json(operation.response['release'].to_json)
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,46 @@ class FirebaseAppDistributionApiClient
include Helper::FirebaseAppDistributionHelper

BASE_URL = "https://firebaseappdistribution.googleapis.com"
MAX_POLLING_RETRIES = 60
POLLING_INTERVAL_SECONDS = 5

AUTHORIZATION = "Authorization"
CONTENT_TYPE = "Content-Type"
APPLICATION_OCTET_STREAM = "application/octet-stream"
CLIENT_VERSION = "X-Client-Version"

def initialize(auth_token, debug = false)
@auth_token = auth_token
@debug = debug
end

# Uploads the app binary to the Firebase API
#
# args
# app_name - Firebase App resource name
# binary_path - Absolute path to your app's aab/apk/ipa file
# platform - 'android' or 'ios'
# timeout - The amount of seconds before the upload will timeout, if not completed
#
# Returns the long-running operation name.
#
# Throws a user_error if the binary file does not exist
def upload_binary(app_name, binary_path, platform, timeout)
response = connection.post(binary_upload_url(app_name), read_binary(binary_path)) do |request|
request.options.timeout = timeout # seconds
request.headers[AUTHORIZATION] = "Bearer " + @auth_token
request.headers[CONTENT_TYPE] = APPLICATION_OCTET_STREAM
request.headers[CLIENT_VERSION] = client_version_header_value
request.headers["X-Goog-Upload-File-Name"] = File.basename(binary_path)
request.headers["X-Goog-Upload-Protocol"] = "raw"
end

response.body[:name] || ''
rescue Errno::ENOENT # Raised when binary_path file does not exist
binary_type = binary_type_from_path(binary_path)
UI.user_error!("#{ErrorMessage.binary_not_found(binary_type)}: #{binary_path}")
end

# Get tester UDIDs
#
# args
Expand All @@ -40,6 +71,10 @@ def client_version_header_value
"fastlane/#{Fastlane::FirebaseAppDistribution::VERSION}"
end

def binary_upload_url(app_name)
"/upload/v1/#{app_name}/releases:upload"
end

def get_udids_url(app_id)
"/v1alpha/apps/#{app_id}/testers:getTesterUdids"
end
Expand All @@ -52,6 +87,11 @@ def connection
conn.adapter(Faraday.default_adapter)
end
end

def read_binary(path)
# File must be read in binary mode to work on Windows
File.open(path, 'rb').read
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,13 @@ def get_authorization(google_service_path, firebase_cli_token, debug = false)
elsif !ENV["FIREBASE_TOKEN"].nil? && !ENV["FIREBASE_TOKEN"].empty?
UI.message("🔐 Authenticating with FIREBASE_TOKEN environment variable")
firebase_token(ENV["FIREBASE_TOKEN"], debug)
elsif !application_default_creds.nil?
UI.message("🔐 Authenticating with Application Default Credentials")
application_default_creds
# TODO(lkellogg): Not using Google::Auth.get_application_default yet while we are still
# using the old client for uploads. ADC also does not work for the get_udids action:
# https://cloud.google.com/docs/authentication/troubleshoot-adc#user-creds-client-based
# For now go back to just using the environment variable:
elsif !ENV["GOOGLE_APPLICATION_CREDENTIALS"].nil? && !ENV["GOOGLE_APPLICATION_CREDENTIALS"].empty?
UI.message("🔐 Authenticating with GOOGLE_APPLICATION_CREDENTIALS environment variable: #{ENV['GOOGLE_APPLICATION_CREDENTIALS']}")
service_account(ENV["GOOGLE_APPLICATION_CREDENTIALS"], debug)
elsif (refresh_token = refresh_token_from_firebase_tools)
UI.message("🔐 No authentication method found. Using cached Firebase CLI credentials.")
firebase_token(refresh_token, debug)
Expand All @@ -52,12 +56,6 @@ def get_authorization(google_service_path, firebase_cli_token, debug = false)

private

def application_default_creds
Google::Auth.get_application_default([SCOPE])
rescue
nil
end

def refresh_token_from_firebase_tools
config_path = format_config_path
if File.exist?(config_path)
Expand Down
2 changes: 1 addition & 1 deletion lib/fastlane/plugin/firebase_app_distribution/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module Fastlane
module FirebaseAppDistribution
VERSION = "0.7.1"
VERSION = "0.7.2.pre.1"
end
end
12 changes: 6 additions & 6 deletions spec/firebase_app_distribution_action_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -318,9 +318,9 @@ def stub_get_aab_info(integration_state = 'INTEGRATED')

it 'crashes if it exceeds polling threshold' do
stub_const('Fastlane::Actions::FirebaseAppDistributionAction::MAX_POLLING_RETRIES', 0)
allow_any_instance_of(Google::Apis::FirebaseappdistributionV1::FirebaseAppDistributionService)
.to receive(:http)
.and_return({ name: 'operation-name' }.to_json)
allow_any_instance_of(Fastlane::Client::FirebaseAppDistributionApiClient)
.to receive(:upload_binary)
.and_return('operation-name')
allow_any_instance_of(Google::Apis::FirebaseappdistributionV1::FirebaseAppDistributionService)
.to receive(:get_project_app_release_operation)
.with('operation-name')
Expand All @@ -340,9 +340,9 @@ def stub_get_aab_info(integration_state = 'INTEGRATED')
let(:release) { { name: "release-name", displayVersion: 'display-version' } }

before do
allow_any_instance_of(Google::Apis::FirebaseappdistributionV1::FirebaseAppDistributionService)
.to receive(:http)
.and_return({ name: 'operation-name', result: release }.to_json)
allow_any_instance_of(Fastlane::Client::FirebaseAppDistributionApiClient)
.to receive(:upload_binary)
.and_return('operation-name')
allow_any_instance_of(Google::Apis::FirebaseappdistributionV1::FirebaseAppDistributionService)
.to receive(:get_project_app_release_operation)
.and_return(Google::Apis::FirebaseappdistributionV1::GoogleLongrunningOperation.new(
Expand Down
10 changes: 4 additions & 6 deletions spec/firebase_app_distribution_auth_client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@
let(:fake_binary_contents) { double("Contents") }
let(:firebase_auth) { Signet::OAuth2::Client }
let(:service_auth) { Google::Auth::ServiceAccountCredentials }
let(:application_default_auth) { Google::Auth }
let(:fake_firebase_tools_contents) { "{\"tokens\": {\"refresh_token\": \"refresh_token\"} }" }
let(:fake_firebase_tools_contents_no_tokens_field) { "{}" }
let(:fake_firebase_tools_contents_no_refresh_field) { "{\"tokens\": \"empty\"}" }
let(:fake_firebase_tools_contents_invalid_json) { "\"tokens\": \"empty\"}" }
let(:fake_service_creds) { double("service_account_creds") }
let(:fake_oauth_client) { double("oauth_client") }
let(:fake_application_default_creds) { double("application_default_creds") }
let(:payload) { { "access_token" => "service_fake_auth_token" } }
let(:fake_error_response) { double("error_response") }

Expand Down Expand Up @@ -59,9 +57,11 @@
end

it 'auths with service credentials environment variable' do
allow(application_default_auth).to receive(:get_application_default).and_return(fake_application_default_creds)
allow(ENV).to receive(:[])
.with("GOOGLE_APPLICATION_CREDENTIALS")
.and_return("google_service_path")
expect(auth_client.get_authorization(empty_val, empty_val))
.to eq(fake_application_default_creds)
.to eq(fake_service_creds)
end

it 'auths with firebase token parameter' do
Expand Down Expand Up @@ -121,8 +121,6 @@
end

describe 'when using cached firebase tools json file' do
before { allow(application_default_auth).to receive(:get_application_default).and_return(nil) }

it 'authenticates' do
allow(File).to receive(:read)
.and_return(fake_firebase_tools_contents)
Expand Down

0 comments on commit 267f503

Please sign in to comment.