Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[deliver] increase chances of success when creating a new app version even when Apple servers are degraded #21742

Merged
merged 9 commits into from
Feb 9, 2024
2 changes: 1 addition & 1 deletion deliver/lib/deliver/generate_summary.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module Deliver
class GenerateSummary
def run(options)
screenshots = UploadScreenshots.new.collect_screenshots(options)
UploadMetadata.new.load_from_filesystem(options)
UploadMetadata.new(options).load_from_filesystem
HtmlGenerator.new.render(options, screenshots, '.')
end
end
Expand Down
9 changes: 9 additions & 0 deletions deliver/lib/deliver/options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,15 @@ def self.available_options
description: "Rejects the previously submitted build if it's in a state where it's possible",
type: Boolean,
default_value: false),
FastlaneCore::ConfigItem.new(key: :version_check_wait_retry_limit,
env_name: "DELIVER_VERSION_CHECK_WAIT_RETRY_LIMIT",
description: "After submitting a new version, App Store Connect takes some time to recognize the new version and we must wait until it's available before attempting to upload metadata for it. There is a mechanism that will check if it's available and retry with an exponential backoff if it's not available yet. " \
"This option specifies how many times we should retry before giving up. Setting this to a value below 5 is not recommended and will likely cause failures. Increase this parameter when Apple servers seem to be degraded or slow",
type: Integer,
default_value: 7,
verify_block: proc do |value|
UI.user_error!("'#{value}' needs to be greater than 0") if value <= 0
end),

# release
FastlaneCore::ConfigItem.new(key: :automatic_release,
Expand Down
8 changes: 4 additions & 4 deletions deliver/lib/deliver/runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -135,21 +135,21 @@ def verify_version

# Upload all metadata, screenshots, pricing information, etc. to App Store Connect
def upload_metadata
upload_metadata = UploadMetadata.new
upload_metadata = UploadMetadata.new(options)
upload_screenshots = UploadScreenshots.new

# First, collect all the things for the HTML Report
screenshots = upload_screenshots.collect_screenshots(options)
upload_metadata.load_from_filesystem(options)
upload_metadata.load_from_filesystem

# Assign "default" values to all languages
upload_metadata.assign_defaults(options)
upload_metadata.assign_defaults

# Validate
validate_html(screenshots)

# Commit
upload_metadata.upload(options)
upload_metadata.upload

if options[:sync_screenshots]
sync_screenshots = SyncScreenshots.new(app: Deliver.cache[:app], platform: Spaceship::ConnectAPI::Platform.map(options[:platform]))
Expand Down
71 changes: 43 additions & 28 deletions deliver/lib/deliver/upload_metadata.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,19 +78,25 @@ class UploadMetadata

require_relative 'loader'

attr_accessor :options

def initialize(options)
self.options = options
end

# Make sure to call `load_from_filesystem` before calling upload
def upload(options)
def upload
return if options[:skip_metadata]

app = Deliver.cache[:app]

platform = Spaceship::ConnectAPI::Platform.map(options[:platform])

enabled_languages = detect_languages(options)
enabled_languages = detect_languages

app_store_version_localizations = verify_available_version_languages!(options, app, enabled_languages) unless options[:edit_live]
app_store_version_localizations = verify_available_version_languages!(app, enabled_languages) unless options[:edit_live]
app_info = fetch_edit_app_info(app)
app_info_localizations = verify_available_info_languages!(options, app, app_info, enabled_languages) unless options[:edit_live] || !updating_localized_app_info?(options, app, app_info)
app_info_localizations = verify_available_info_languages!(app, app_info, enabled_languages) unless options[:edit_live] || !updating_localized_app_info?(app, app_info)

if options[:edit_live]
# not all values are editable when using live_version
Expand Down Expand Up @@ -342,9 +348,9 @@ def upload(options)
end
end

set_review_information(version, options)
set_review_attachment_file(version, options)
set_app_rating(app_info, options)
review_information(version)
review_attachment_file(version)
app_rating(app_info)
end

# rubocop:enable Metrics/PerceivedComplexity
Expand All @@ -360,12 +366,12 @@ def convert_ms_to_iso8601(time_in_ms)
end

# If the user is using the 'default' language, then assign values where they are needed
def assign_defaults(options)
def assign_defaults
# Normalizes languages keys from symbols to strings
normalize_language_keys(options)
normalize_language_keys

# Build a complete list of the required languages
enabled_languages = detect_languages(options)
enabled_languages = detect_languages

# Get all languages used in existing settings
(LOCALISED_VERSION_VALUES.keys + LOCALISED_APP_VALUES.keys).each do |key|
Expand Down Expand Up @@ -402,7 +408,7 @@ def assign_defaults(options)
end
end

def detect_languages(options)
def detect_languages
# Build a complete list of the required languages
enabled_languages = options[:languages] || []

Expand All @@ -427,40 +433,49 @@ def detect_languages(options)
.uniq
end

def fetch_edit_app_store_version(app, platform, wait_time: 10)
retry_if_nil("Cannot find edit app store version", wait_time: wait_time) do
def fetch_edit_app_store_version(app, platform)
retry_if_nil("Cannot find edit app store version") do
app.get_edit_app_store_version(platform: platform)
end
end

def fetch_edit_app_info(app, wait_time: 10)
retry_if_nil("Cannot find edit app info", wait_time: wait_time) do
def fetch_edit_app_info(app)
retry_if_nil("Cannot find edit app info") do
app.fetch_edit_app_info
end
end

def fetch_live_app_info(app, wait_time: 10)
retry_if_nil("Cannot find live app info", wait_time: wait_time) do
def fetch_live_app_info(app)
retry_if_nil("Cannot find live app info") do
app.fetch_live_app_info
end
end

def retry_if_nil(message, tries: 5, wait_time: 10)
# Retries a block of code if the return value is nil, with an exponential backoff.
def retry_if_nil(message)
tries = options[:version_check_wait_retry_limit]
wait_time = 10
loop do
tries -= 1

value = yield
return value if value

UI.message("#{message}... Retrying after #{wait_time} seconds (remaining: #{tries})")
sleep(wait_time)
# Calculate sleep time to be the lesser of the exponential backoff or 5 minutes.
# This prevents problems with CI's console output timeouts (of usually 10 minutes), and also
# speeds up the retry time for the user, as waiting longer than 5 minutes is a too long wait for a retry.
sleep_time = [wait_time * 2, 5 * 60].min
UI.message("#{message}... Retrying after #{sleep_time} seconds (remaining: #{tries})")
Kernel.sleep(sleep_time)

return nil if tries.zero?

wait_time *= 2 # Double the wait time for the next iteration
end
end

# Checking if the metadata to update includes localised App Info
def updating_localized_app_info?(options, app, app_info)
def updating_localized_app_info?(app, app_info)
app_info ||= fetch_live_app_info(app)
unless app_info
UI.important("Can't find edit or live App info. Skipping upload.")
Expand Down Expand Up @@ -499,7 +514,7 @@ def updating_localized_app_info?(options, app, app_info)
end

# Finding languages to enable
def verify_available_info_languages!(options, app, app_info, languages)
def verify_available_info_languages!(app, app_info, languages)
unless app_info
UI.user_error!("Cannot update languages - could not find an editable 'App Info'. Verify that your app is in one of the editable states in App Store Connect")
return
Expand Down Expand Up @@ -531,7 +546,7 @@ def verify_available_info_languages!(options, app, app_info, languages)
end

# Finding languages to enable
def verify_available_version_languages!(options, app, languages)
def verify_available_version_languages!(app, languages)
platform = Spaceship::ConnectAPI::Platform.map(options[:platform])
version = fetch_edit_app_store_version(app, platform)

Expand Down Expand Up @@ -566,7 +581,7 @@ def verify_available_version_languages!(options, app, languages)
end

# Loads the metadata files and stores them into the options object
def load_from_filesystem(options)
def load_from_filesystem
return if options[:skip_metadata]

# Load localised data
Expand Down Expand Up @@ -623,7 +638,7 @@ def load_from_filesystem(options)
private

# Normalizes languages keys from symbols to strings
def normalize_language_keys(options)
def normalize_language_keys
(LOCALISED_VERSION_VALUES.keys + LOCALISED_APP_VALUES.keys).each do |key|
current = options[key]
next unless current && current.kind_of?(Hash)
Expand All @@ -636,7 +651,7 @@ def normalize_language_keys(options)
options
end

def set_review_information(version, options)
def review_information(version)
info = options[:app_review_information]
return if info.nil? || info.empty?

Expand Down Expand Up @@ -669,7 +684,7 @@ def set_review_information(version, options)
end
end

def set_review_attachment_file(version, options)
def review_attachment_file(version)
app_store_review_detail = version.fetch_app_store_review_detail
app_store_review_attachments = app_store_review_detail.app_store_review_attachments || []

Expand All @@ -687,7 +702,7 @@ def set_review_attachment_file(version, options)
end
end

def set_app_rating(app_info, options)
def app_rating(app_info)
return unless options[:app_rating_config_path]

require 'json'
Expand Down