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

Google Play release promotion #345

Merged
merged 15 commits into from
Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
Version 0.44.0
-------------

Additions and changes from [pull request #345](https://github.com/codemagic-ci-cd/cli-tools/pull/345).

**Features**
- Add a new action `google-play tracks promote-release` to promote a release from one Google Play release track to another.

**Development**
- Define a new common argument type `bounded_number` for CLI usage that can be used to load floats and integers from CLI inputs within specified ranges.
- Add a new client method `update_track` to update release track in Google Play API client `codemagic.google_play.api_client.GooglePlayDeveloperAPIClient`.
-
**Documentation**
- Update documentation for action group `google-play tracks`.
- Add documentation for action `google-play tracks promote-release`.

Version 0.43.0
-------------

Expand Down
2 changes: 1 addition & 1 deletion docs/google-play/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ google-play [-h] [--log-stream STREAM] [--no-color] [--version] [-s] [-v]
##### `--credentials=GCLOUD_SERVICE_ACCOUNT_CREDENTIALS`


Gcloud service account credentials with `JSON` key type to access Google Play Developer API. If not given, the value will be checked from the environment variable `GCLOUD_SERVICE_ACCOUNT_CREDENTIALS`. Alternatively to entering CREDENTIALS in plaintext, it may also be specified using the `@env:` prefix followed by an environment variable name, or the `@file:` prefix followed by a path to the file containing the value. Example: `@env:<variable>` uses the value in the environment variable named `<variable>`, and `@file:<file_path>` uses the value from the file at `<file_path>`.
Gcloud service account credentials with the `JSON` key type to access Google Play Developer API. If not given, the value will be checked from the environment variable `GCLOUD_SERVICE_ACCOUNT_CREDENTIALS`. Alternatively to entering CREDENTIALS in plaintext, it may also be specified using the `@env:` prefix followed by an environment variable name, or the `@file:` prefix followed by a path to the file containing the value. Example: `@env:<variable>` uses the value in the environment variable named `<variable>`, and `@file:<file_path>` uses the value from the file at `<file_path>`.
### Common options

##### `-h, --help`
Expand Down
2 changes: 1 addition & 1 deletion docs/google-play/get-latest-build-number.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Get the build number from the specified track(s). If not specified, the highest
##### `--credentials=GCLOUD_SERVICE_ACCOUNT_CREDENTIALS`


Gcloud service account credentials with `JSON` key type to access Google Play Developer API. If not given, the value will be checked from the environment variable `GCLOUD_SERVICE_ACCOUNT_CREDENTIALS`. Alternatively to entering CREDENTIALS in plaintext, it may also be specified using the `@env:` prefix followed by an environment variable name, or the `@file:` prefix followed by a path to the file containing the value. Example: `@env:<variable>` uses the value in the environment variable named `<variable>`, and `@file:<file_path>` uses the value from the file at `<file_path>`.
Gcloud service account credentials with the `JSON` key type to access Google Play Developer API. If not given, the value will be checked from the environment variable `GCLOUD_SERVICE_ACCOUNT_CREDENTIALS`. Alternatively to entering CREDENTIALS in plaintext, it may also be specified using the `@env:` prefix followed by an environment variable name, or the `@file:` prefix followed by a path to the file containing the value. Example: `@env:<variable>` uses the value in the environment variable named `<variable>`, and `@file:<file_path>` uses the value from the file at `<file_path>`.
### Common options

##### `-h, --help`
Expand Down
3 changes: 2 additions & 1 deletion docs/google-play/tracks.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ google-play tracks [-h] [--log-stream STREAM] [--no-color] [--version] [-s] [-v]
##### `--credentials=GCLOUD_SERVICE_ACCOUNT_CREDENTIALS`


Gcloud service account credentials with `JSON` key type to access Google Play Developer API. If not given, the value will be checked from the environment variable `GCLOUD_SERVICE_ACCOUNT_CREDENTIALS`. Alternatively to entering CREDENTIALS in plaintext, it may also be specified using the `@env:` prefix followed by an environment variable name, or the `@file:` prefix followed by a path to the file containing the value. Example: `@env:<variable>` uses the value in the environment variable named `<variable>`, and `@file:<file_path>` uses the value from the file at `<file_path>`.
Gcloud service account credentials with the `JSON` key type to access Google Play Developer API. If not given, the value will be checked from the environment variable `GCLOUD_SERVICE_ACCOUNT_CREDENTIALS`. Alternatively to entering CREDENTIALS in plaintext, it may also be specified using the `@env:` prefix followed by an environment variable name, or the `@file:` prefix followed by a path to the file containing the value. Example: `@env:<variable>` uses the value in the environment variable named `<variable>`, and `@file:<file_path>` uses the value from the file at `<file_path>`.
### Common options

##### `-h, --help`
Expand Down Expand Up @@ -48,3 +48,4 @@ Enable verbose logging for commands
| :--- | :--- |
|[`get`](tracks/get.md)|Get information about specified track from Google Play Developer API|
|[`list`](tracks/list.md)|Get information about specified track from Google Play Developer API|
|[`promote-release`](tracks/promote-release.md)|Promote releases from source track to target track. If filters for source track release are not specified, then the latest release will be promoted|
2 changes: 1 addition & 1 deletion docs/google-play/tracks/get.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Whether to show the request response in JSON format
##### `--credentials=GCLOUD_SERVICE_ACCOUNT_CREDENTIALS`


Gcloud service account credentials with `JSON` key type to access Google Play Developer API. If not given, the value will be checked from the environment variable `GCLOUD_SERVICE_ACCOUNT_CREDENTIALS`. Alternatively to entering CREDENTIALS in plaintext, it may also be specified using the `@env:` prefix followed by an environment variable name, or the `@file:` prefix followed by a path to the file containing the value. Example: `@env:<variable>` uses the value in the environment variable named `<variable>`, and `@file:<file_path>` uses the value from the file at `<file_path>`.
Gcloud service account credentials with the `JSON` key type to access Google Play Developer API. If not given, the value will be checked from the environment variable `GCLOUD_SERVICE_ACCOUNT_CREDENTIALS`. Alternatively to entering CREDENTIALS in plaintext, it may also be specified using the `@env:` prefix followed by an environment variable name, or the `@file:` prefix followed by a path to the file containing the value. Example: `@env:<variable>` uses the value in the environment variable named `<variable>`, and `@file:<file_path>` uses the value from the file at `<file_path>`.
### Common options

##### `-h, --help`
Expand Down
2 changes: 1 addition & 1 deletion docs/google-play/tracks/list.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Whether to show the request response in JSON format
##### `--credentials=GCLOUD_SERVICE_ACCOUNT_CREDENTIALS`


Gcloud service account credentials with `JSON` key type to access Google Play Developer API. If not given, the value will be checked from the environment variable `GCLOUD_SERVICE_ACCOUNT_CREDENTIALS`. Alternatively to entering CREDENTIALS in plaintext, it may also be specified using the `@env:` prefix followed by an environment variable name, or the `@file:` prefix followed by a path to the file containing the value. Example: `@env:<variable>` uses the value in the environment variable named `<variable>`, and `@file:<file_path>` uses the value from the file at `<file_path>`.
Gcloud service account credentials with the `JSON` key type to access Google Play Developer API. If not given, the value will be checked from the environment variable `GCLOUD_SERVICE_ACCOUNT_CREDENTIALS`. Alternatively to entering CREDENTIALS in plaintext, it may also be specified using the `@env:` prefix followed by an environment variable name, or the `@file:` prefix followed by a path to the file containing the value. Example: `@env:<variable>` uses the value in the environment variable named `<variable>`, and `@file:<file_path>` uses the value from the file at `<file_path>`.
### Common options

##### `-h, --help`
Expand Down
87 changes: 87 additions & 0 deletions docs/google-play/tracks/promote-release.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@

promote-release
===============


**Promote releases from source track to target track. If filters for source track release are not specified, then the latest release will be promoted**
### Usage
```bash
google-play tracks promote-release [-h] [--log-stream STREAM] [--no-color] [--version] [-s] [-v]
[--credentials GCLOUD_SERVICE_ACCOUNT_CREDENTIALS]
[--release-status PROMOTED_STATUS]
[--user-fraction PROMOTED_USER_FRACTION]
[--version-code-filter PROMOTE_VERSION_CODE]
[--release-status-filter PROMOTE_STATUS]
[--json]
--package-name PACKAGE_NAME
--source-track SOURCE_TRACK_NAME
--target-track TARGET_TRACK_NAME
```
### Required arguments for action `promote-release`

##### `--package-name, -p=PACKAGE_NAME`


Package name of the app in Google Play Console. For example `com.example.app`
##### `--source-track=SOURCE_TRACK_NAME`


Name of the track from where releases are promoted. For example `internal`
##### `--target-track=TARGET_TRACK_NAME`


Name of the track to which releases are promoted. For example `alpha`
### Optional arguments for action `promote-release`

##### `--release-status=statusUnspecified | draft | inProgress | halted | completed`


Status of the promoted release in the target track. Default:&nbsp;`completed`
##### `--user-fraction=PROMOTED_USER_FRACTION`


Fraction of users who are eligible for a staged promoted release in the target track. Number from interval `0 < fraction < 1`. Can only be set when status is `inProgress` or `halted`
##### `--version-code-filter=PROMOTE_VERSION_CODE`


Promote only a source track release that contains the specified version code
##### `--release-status-filter=statusUnspecified | draft | inProgress | halted | completed`


Promote only a source track release with the specified status
##### `--json, -j`


Whether to show the request response in JSON format
### Optional arguments for command `google-play`

##### `--credentials=GCLOUD_SERVICE_ACCOUNT_CREDENTIALS`


Gcloud service account credentials with the `JSON` key type to access Google Play Developer API. If not given, the value will be checked from the environment variable `GCLOUD_SERVICE_ACCOUNT_CREDENTIALS`. Alternatively to entering CREDENTIALS in plaintext, it may also be specified using the `@env:` prefix followed by an environment variable name, or the `@file:` prefix followed by a path to the file containing the value. Example: `@env:<variable>` uses the value in the environment variable named `<variable>`, and `@file:<file_path>` uses the value from the file at `<file_path>`.
### Common options

##### `-h, --help`


show this help message and exit
priitlatt marked this conversation as resolved.
Show resolved Hide resolved
##### `--log-stream=stderr | stdout`


Log output stream. Default `stderr`
##### `--no-color`


Do not use ANSI colors to format terminal output
##### `--version`


Show tool version and exit
##### `-s, --silent`


Disable log output for commands
##### `-v, --verbose`


Enable verbose logging for commands
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.43.0"
version = "0.44.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.43.0.dev"
__version__ = "0.44.0.dev"
__url__ = "https://github.com/codemagic-ci-cd/cli-tools"
__licence__ = "GNU General Public License v3.0"
30 changes: 30 additions & 0 deletions src/codemagic/cli/argument/common_argument_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
import json
import pathlib
from datetime import datetime
from typing import Callable
from typing import Dict
from typing import Type
from typing import TypeVar

N = TypeVar("N", int, float)


class CommonArgumentTypes:
Expand Down Expand Up @@ -74,3 +79,28 @@ def iso_8601_datetime(iso_8601_timestamp: str) -> datetime:
except ValueError:
continue
raise argparse.ArgumentTypeError(f'"{iso_8601_timestamp}" is not a valid ISO 8601 timestamp')

@staticmethod
def bounded_number(
number_type: Type[N],
lower_limit: N,
upper_limit: N,
inclusive: bool,
) -> Callable[[str], N]:
def _resolve_number(number_as_string: str):
try:
n = number_type(number_as_string)
except ValueError:
type_description = "floating point number" if number_type is float else "integer"
raise argparse.ArgumentTypeError(f"Value {number_as_string} is not a valid {type_description}")

if inclusive and (lower_limit > n or n > upper_limit):
error = f"Value {n} is out of allowed bounds, {lower_limit} <= value <= {upper_limit}"
raise argparse.ArgumentTypeError(error)
if not inclusive and (lower_limit >= n or n >= upper_limit):
error = f"Value {n} is out of allowed bounds, {lower_limit} < value < {upper_limit}"
raise argparse.ArgumentTypeError(error)

return n

return _resolve_number
34 changes: 34 additions & 0 deletions src/codemagic/google_play/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from .api_error import GetResourceError
from .api_error import GooglePlayDeveloperAPIClientError
from .api_error import ListResourcesError
from .api_error import UpdateResourceError
from .resources import Edit
from .resources import Track

Expand Down Expand Up @@ -100,6 +101,9 @@ def delete_edit(self, edit: Union[str, Edit], package_name: str) -> None:
delete_request.execute()
self._logger.debug(f"Deleted edit {edit_id} for package {package_name!r}")
except (errors.Error, errors.HttpError) as e:
if isinstance(e, errors.HttpError) and "edit has already been successfully committed" in e.error_details:
# This can be ignored as the commit has already been exhausted
return
raise EditError("delete", package_name, e) from e

@contextlib.contextmanager
Expand Down Expand Up @@ -161,3 +165,33 @@ def _list_tracks(self, package_name: str, edit_id: str) -> List[Track]:
raise ListResourcesError("tracks", package_name, e) from e
else:
return [Track(**track) for track in tracks_response["tracks"]]

def update_track(
self,
package_name: str,
track: Track,
) -> Track:
with self.use_app_edit(package_name) as edit:
track_update = track.dict()
self._logger.debug(
f"Update track {track.track!r} for package {package_name} using edit {edit.id} with {track_update!r}",
)
track_request = self.edits_service.tracks().update(
packageName=package_name,
editId=edit.id,
track=track.track,
body=track_update,
)
commit_request = self.edits_service.commit(
packageName=package_name,
editId=edit.id,
)
try:
track_response = track_request.execute()
commit_response = commit_request.execute()
except (errors.Error, errors.HttpError) as e:
raise UpdateResourceError("track", package_name, e) from e

self._logger.debug(f"Track {track.track!r} update response for package {package_name!r}: {track_response}")
self._logger.debug(f"Track {track.track!r} commit response for package {package_name!r}: {commit_response}")
return Track(**track_response)
8 changes: 8 additions & 0 deletions src/codemagic/google_play/api_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,11 @@ def _get_message(self, resource_description: str) -> str:
f'Failed to list {resource_description} from Google Play for package "{self.package_name}". '
f"{self._get_reason()}"
)


class UpdateResourceError(_RequestError):
def _get_message(self, resource_description: str) -> str:
return (
f'Failed to update {resource_description} in Google Play for package "{self.package_name}". '
f"{self._get_reason()}"
)
2 changes: 2 additions & 0 deletions src/codemagic/google_play/resources/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from .edit import Edit
from .enums import ReleaseStatus
from .resource import Resource
from .track import Release
from .track import Track
4 changes: 2 additions & 2 deletions src/codemagic/google_play/resources/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@ def _format_attribute_value(cls, value: Any, tabs_count: int = 0) -> str:
return str(value)

def __str__(self) -> str:
return "".join(
return "\n".join(
[
f"\n{self._format_attribute_name(k)}: {self._format_attribute_value(v)}"
f"{self._format_attribute_name(k)}: {self._format_attribute_value(v)}"
for k, v in self.__dict__.items()
if v is not None
],
Expand Down
4 changes: 3 additions & 1 deletion src/codemagic/google_play/resources/track.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ class Track(Resource):

def __post_init__(self):
if isinstance(self.releases, list):
self.releases = [Release(**release) for release in self.releases]
self.releases = [
release if isinstance(release, Release) else Release(**release) for release in self.releases
]

def get_max_version_code(self) -> int:
error_prefix = f'Failed to get version code from "{self.track}" track'
Expand Down
Loading
Loading