Skip to content

Commit

Permalink
Add option to configure timeout for finding build from App Store Conn…
Browse files Browse the repository at this point in the history
…ect (#355)
  • Loading branch information
priitlatt authored Oct 3, 2023
1 parent ade5933 commit d7d5db1
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 46 deletions.
7 changes: 5 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
Version UNRELEASED
Version 0.46.0
-------------

**Features**
- Add new option `--max-find-build-wait` to action `app-store-connect publish` to configure maximum waiting time to discover uploaded build from App Store Connect before failing publishing. Defaults to 10 minutes. [PR #355](https://github.com/codemagic-ci-cd/cli-tools/pull/355)

**Docs**
- Add option `--max-find-build-wait` documentation for action `app-store-connect publish`. [PR #355](https://github.com/codemagic-ci-cd/cli-tools/pull/355)
- Update option `--cancel-previous-submissions` documentation for actions `app-store-connect builds submit-to-app-store` and `app-store-connect publish`. [PR #353](https://github.com/codemagic-ci-cd/cli-tools/pull/353)


Version 0.45.3
-------------

Expand Down
5 changes: 5 additions & 0 deletions docs/app-store-connect/publish.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ app-store-connect publish [-h] [--log-stream STREAM] [--no-color] [--version] [-
[--enable-package-validation]
[--skip-package-validation]
[--skip-package-upload]
[--max-find-build-wait MAX_BUILD_FIND_WAIT]
[--max-build-processing-wait MAX_BUILD_PROCESSING_WAIT]
[--beta-build-localizations BETA_BUILD_LOCALIZATIONS]
[--testflight]
Expand Down Expand Up @@ -74,6 +75,10 @@ Validate package before uploading it to App Store Connect. Use this switch to en


Skip package upload before doing any other TestFlight or App Store related actions. Using this switch will opt out from running `altool --upload-app` as part of publishing action. Use this option in case your application package is already uploaded to App Store. If not given, the value will be checked from the environment variable `APP_STORE_CONNECT_SKIP_PACKAGE_UPLOAD`.
##### `--max-find-build-wait=MAX_BUILD_FIND_WAIT`


The maximum amount of minutes to wait for the freshly uploaded build to become discoverable in App Store Connect. Works in conjunction with TestFlight beta review submission or App Store review submission and operations that depend on either one of those. If the build does not become available within the specified timeframe, further submission will be terminated. If not given, the value will be checked from the environment variable `APP_STORE_CONNECT_MAX_FIND_BUILD_WAIT`. [Default: 10]
##### `--max-build-processing-wait, -w=MAX_BUILD_PROCESSING_WAIT`


Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "codemagic-cli-tools"
version = "0.45.3"
version = "0.46.0"
description = "CLI tools used in Codemagic builds"
readme = "README.md"
authors = [
Expand Down
2 changes: 1 addition & 1 deletion src/codemagic/__version__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
__title__ = "codemagic-cli-tools"
__description__ = "CLI tools used in Codemagic builds"
__version__ = "0.45.3.dev"
__version__ = "0.46.0.dev"
__url__ = "https://github.com/codemagic-ci-cd/cli-tools"
__licence__ = "GNU General Public License v3.0"
95 changes: 53 additions & 42 deletions src/codemagic/tools/_app_store_connect/actions/publish_action.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import dataclasses
import itertools
import pathlib
import time
from abc import ABCMeta
Expand Down Expand Up @@ -86,6 +87,7 @@ class SubmitToAppStoreOptions:
PublishArgument.APPLE_ID,
PublishArgument.APP_SPECIFIC_PASSWORD,
*ArgumentGroups.PACKAGE_UPLOAD_ARGUMENTS,
PublishArgument.MAX_BUILD_FIND_WAIT,
PublishArgument.MAX_BUILD_PROCESSING_WAIT,
*PublishArgument.with_custom_argument_group(
"add localized What's new (what to test) information to uploaded build",
Expand Down Expand Up @@ -286,6 +288,7 @@ def publish(
altool_retries_count: Optional[Types.AltoolRetriesCount] = None,
altool_retry_wait: Optional[Types.AltoolRetryWait] = None,
altool_verbose_logging: Optional[bool] = None,
max_find_build_wait: Union[Types.MaxFindBuildWait, int] = PublishArgument.MAX_BUILD_FIND_WAIT.get_default(),
**app_store_connect_submit_options,
) -> None:
"""
Expand Down Expand Up @@ -318,6 +321,7 @@ def publish(
)
self._process_application_after_upload(
application_package,
Types.MaxFindBuildWait.resolve_value(max_find_build_wait),
*self._get_app_store_connect_submit_options(
application_package,
submit_to_testflight,
Expand Down Expand Up @@ -369,6 +373,7 @@ def _publish_application_package(
def _process_application_after_upload(
self,
application_package: Union[Ipa, MacOsPackage],
max_find_build_wait: int,
testflight_options: Optional[SubmitToTestFlightOptions],
app_store_options: Optional[SubmitToAppStoreOptions],
beta_test_info_options: Optional[AddBetaTestInfoOptions],
Expand All @@ -378,7 +383,7 @@ def _process_application_after_upload(
return # Nothing to do with the application...

app = self._get_uploaded_build_application(application_package)
build = self._get_uploaded_build(app, application_package)
build = self._get_uploaded_build(app, application_package, max_find_build_wait)

if beta_test_info_options:
self.add_beta_test_info(build.id, **beta_test_info_options.__dict__)
Expand Down Expand Up @@ -408,8 +413,8 @@ def _find_build(
self,
app_id: ResourceId,
application_package: Union[Ipa, MacOsPackage],
retries: int = 20,
retry_wait_seconds: int = 30,
max_find_build_minutes: int,
retry_wait_seconds: int,
) -> Build:
"""
Find corresponding build for the uploaded ipa or macOS package.
Expand All @@ -423,44 +428,43 @@ def _find_build(
pre_release_version_version=application_package.version,
pre_release_version_platform=self._get_application_package_platform(application_package),
)
try:
found_builds = self.api_client.builds.list(
builds_filter,
ordering=self.api_client.builds.Ordering.UPLOADED_DATE,
reverse=True,
)
except AppStoreConnectApiError as api_error:
raise AppStoreConnectError(str(api_error))

if found_builds:
return found_builds[0]

# Matching build was not found.
if retries > 0:
# There are retries left, wait a bit and try again.
self.logger.info(
(
"Build has finished uploading but is processing on App Store Connect side. Could not find the "
"build matching the uploaded version yet. Waiting %d seconds to try again, %d attempts remaining."
),
retry_wait_seconds,
retries,
)
time.sleep(retry_wait_seconds)
return self._find_build(
app_id,
application_package,
retries=retries - 1,
retry_wait_seconds=retry_wait_seconds,
)
else:
# There are no more retries left, give up.
raise IOError(
"The build was successfully uploaded to App Store Connect but processing the corresponding artifact "
f'"{application_package.path}" by Apple took longer than expected. Further actions like updating the '
"What to test information or submitting the build to beta review could not be performed at the moment "
"but can be completed manually in TestFlight once the build has finished processing.",
)

not_found_message = "Could not find the build matching the uploaded version"
default_retry_message = f"{not_found_message}, waiting {retry_wait_seconds} seconds to try again."
first_retry_message = (
f"Build has finished uploading but is not available in App Store Connect yet. "
f"{default_retry_message} Timeout in {max_find_build_minutes} minutes."
)

find_started_at = time.time()
for attempt in itertools.count():
try:
found_builds = self.api_client.builds.list(
builds_filter,
ordering=self.api_client.builds.Ordering.UPLOADED_DATE,
reverse=True,
)
except AppStoreConnectApiError as api_error:
raise AppStoreConnectError(str(api_error))

if found_builds:
return found_builds[0]
elif int(time.time() - find_started_at) <= max_find_build_minutes * 60:
self.logger.info(first_retry_message if attempt == 0 else default_retry_message)
time.sleep(retry_wait_seconds)
else:
self.logger.info(f"{not_found_message}. Timeout reached, stop trying.\n")
break # No more time left, give up.

error_message = (
"The build was successfully uploaded to App Store Connect but processing the corresponding artifact "
f'"{application_package.path}" by Apple took longer than expected. Further actions like updating the '
'"What to test information" or submitting the build to beta review could not be performed at the moment '
"but can be completed manually in TestFlight once the build has finished processing.\n"
f"You can configure maximum timeout using {PublishArgument.MAX_BUILD_FIND_WAIT.flag} "
f"command line option, or {Types.MaxFindBuildWait.environment_variable_key} environment variable."
)
raise IOError(error_message)

def _get_uploaded_build_application(self, application_package: Union[Ipa, MacOsPackage]) -> App:
bundle_id = application_package.bundle_identifier
Expand All @@ -481,9 +485,16 @@ def _get_uploaded_build(
self,
app: App,
application_package: Union[Ipa, MacOsPackage],
max_find_build_minutes: int,
retry_wait_seconds: int = 30,
) -> Build:
self.logger.info(Colors.BLUE("\nFind uploaded build"))
build = self._find_build(app.id, application_package)
build = self._find_build(
app.id,
application_package,
max_find_build_minutes,
retry_wait_seconds,
)
self.logger.info(Colors.GREEN("\nUploaded build is"))
self.printer.print_resource(build, True)
return build
Expand Down
25 changes: 25 additions & 0 deletions src/codemagic/tools/_app_store_connect/arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,15 @@ class AltoolVerboseLogging(cli.TypedCliArgument[bool]):
argument_type = bool
environment_variable_key = "APP_STORE_CONNECT_ALTOOL_VERBOSE_LOGGING"

class MaxFindBuildWait(cli.TypedCliArgument[int]):
argument_type = int
environment_variable_key = "APP_STORE_CONNECT_MAX_FIND_BUILD_WAIT"
default_value = 10

@classmethod
def _is_valid(cls, value: int) -> bool:
return value > 0

class MaxBuildProcessingWait(cli.TypedCliArgument[int]):
argument_type = int
environment_variable_key = "APP_STORE_CONNECT_MAX_BUILD_PROCESSING_WAIT"
Expand Down Expand Up @@ -899,6 +908,22 @@ class PublishArgument(cli.Argument):
"required": False,
},
)
MAX_BUILD_FIND_WAIT = cli.ArgumentProperties(
key="max_find_build_wait",
flags=("--max-find-build-wait",),
type=Types.MaxFindBuildWait,
description=(
"The maximum amount of minutes to wait for the freshly uploaded build "
"to become discoverable in App Store Connect. Works in conjunction with "
"TestFlight beta review submission or App Store review submission and "
"operations that depend on either one of those. If the build does not "
"become available within the specified timeframe, further submission "
"will be terminated."
),
argparse_kwargs={
"required": False,
},
)
MAX_BUILD_PROCESSING_WAIT = cli.ArgumentProperties(
key="max_build_processing_wait",
flags=("--max-build-processing-wait", "-w"),
Expand Down

0 comments on commit d7d5db1

Please sign in to comment.