Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
6 changes: 6 additions & 0 deletions pushapkscript/docker.d/init_worker.sh
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ case $COT_PRODUCT in
export GOOGLE_CREDENTIALS_FOCUS_DEP_PATH=$CONFIG_DIR/fake_cert.json
export SGS_SERVICE_ACCOUNT_ID_DEP="0123456"
export SGS_ACCESS_TOKEN_DEP="dummy"
echo '{"key_id": "dummy", "sub_account": "dummy", "private_key": "dummy"}' > $CONFIG_DIR/huawei_dep.json
export HUAWEI_CREDENTIALS_DEP_PATH=$CONFIG_DIR/huawei_dep.json

import_cert fenix $CERT_DIR/fenix_dep.pem
import_cert focus $CERT_DIR/focus_dep.pem
Expand All @@ -85,6 +87,7 @@ case $COT_PRODUCT in
test_var_set 'GOOGLE_SERVICE_ACCOUNT_FENIX_RELEASE'
test_var_set 'SGS_SERVICE_ACCOUNT_ID'
test_var_set 'SGS_ACCESS_TOKEN'
test_var_set 'HUAWEI_SERVICE_ACCOUNT'

export GOOGLE_CREDENTIALS_FOCUS_PATH=$CONFIG_DIR/focus.json
export GOOGLE_CREDENTIALS_FENIX_NIGHTLY_PATH=$CONFIG_DIR/fenix_nightly.json
Expand All @@ -96,6 +99,9 @@ case $COT_PRODUCT in
echo $GOOGLE_SERVICE_ACCOUNT_FENIX_BETA | base64 -d > $GOOGLE_CREDENTIALS_FENIX_BETA_PATH
echo $GOOGLE_SERVICE_ACCOUNT_FENIX_RELEASE | base64 -d > $GOOGLE_CREDENTIALS_FENIX_RELEASE_PATH

export HUAWEI_CREDENTIALS_PATH=$CONFIG_DIR/huawei.json
echo $HUAWEI_SERVICE_ACCOUNT | base64 -d > $HUAWEI_CREDENTIALS_PATH

import_cert fenix-nightly $CERT_DIR/fenix_nightly.pem
import_cert fenix-beta $CERT_DIR/fenix_beta.pem
import_cert fenix-release $CERT_DIR/fenix_release.pem
Expand Down
4 changes: 4 additions & 0 deletions pushapkscript/docker.d/worker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ products:
samsung:
service_account_id: { "$eval": "SGS_SERVICE_ACCOUNT_ID" }
access_token: { "$eval": "SGS_ACCESS_TOKEN" }
huawei:
credentials_file: { "$eval": "HUAWEI_CREDENTIALS_PATH" }
- product_names: ["focus-android" ]
digest_algorithm: 'SHA1'
skip_check_ordered_version_codes: true
Expand Down Expand Up @@ -113,6 +115,8 @@ products:
samsung:
service_account_id: { "$eval": "SGS_SERVICE_ACCOUNT_ID_DEP" }
access_token: { "$eval": "SGS_ACCESS_TOKEN_DEP" }
huawei:
credentials_file: { "$eval": "HUAWEI_CREDENTIALS_DEP_PATH" }
- product_names: ["focus-android" ]
digest_algorithm: "SHA1"
skip_check_ordered_version_codes: true
Expand Down
7 changes: 5 additions & 2 deletions pushapkscript/examples/config.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,11 @@
"credentials_file": "/fenix-production.json"
},
"samsung": {
"sgs_service_account_id": "0123456",
"sgs_access_token": "abcdef"
"service_account_id": "0123456",
"access_token": "abcdef"
},
"huawei": {
"credentials_file": "/huawei.json"
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion pushapkscript/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ classifiers = [
"Programming Language :: Python :: 3.11",
]
dependencies = [
"mozapkpublisher",
"mozapkpublisher>=12.0.0",
"scriptworker",
]

Expand Down
12 changes: 12 additions & 0 deletions pushapkscript/src/pushapkscript/data/config_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,18 @@
"type": "string"
}
}
},
"huawei": {
"type": "object",
"required": [
"credentials_file"
],
"additionalProperties": false,
"properties": {
"credentials_file": {
"type": "string"
}
}
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion pushapkscript/src/pushapkscript/publish.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ async def publish(product_config, publish_config, apk_files, contact_server):
skip_checks_fennec=bool(product_config.get("skip_checks_fennec")),
sgs_service_account_id=publish_config.get("sgs_service_account_id"),
sgs_access_token=publish_config.get("sgs_access_token"),
# Note that this only has an effect on SGS submissions, not google play.
huawei_credentials=publish_config.get("huawei_credentials"),
# Note that this only has an effect on SGS and Huawei submissions, not google play.
submit=publish_config.get("submit", False),
)

Expand Down
20 changes: 16 additions & 4 deletions pushapkscript/src/pushapkscript/publish_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

log = logging.getLogger(__name__)

# Non-Google stores produce the same publish-config shape and differ only in which
# credential keys they expose. Maps target_store -> {output_key: store_config_key}.
_NON_GOOGLE_STORE_CREDENTIALS = {
"samsung": {
"sgs_service_account_id": "service_account_id",
"sgs_access_token": "access_token",
},
"huawei": {
"huawei_credentials": "credentials_file",
},
}


def _should_do_dry_run(task):
# Don't commit anything by default. Committed APKs can't be unpublished,
Expand Down Expand Up @@ -70,19 +82,19 @@ def _get_channel_publish_config(product_config, task):
store_config = publish_config[target_store]
rollout_percentage = task.get("rollout_percentage")

if target_store == "samsung":
if target_store in _NON_GOOGLE_STORE_CREDENTIALS:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be a case where verbosity is preferable; adding a mostly duplicated branch for huawei would probably read better (after all, it's what we already do between google and samsung?).

Not a blocker, just a mild suggestion.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are planning on adding more stores, I'll keep a mental note on this; it's likely we'll need a refactor since other stores might have different config models.

if task.get("google_play_track"):
raise ValueError("`google_play_track` is not allowed on the task if the target store is samsung")
raise ValueError(f"`google_play_track` is not allowed on the task if the target store is {target_store}")

credentials = {output_key: store_config[config_key] for output_key, config_key in _NON_GOOGLE_STORE_CREDENTIALS[target_store].items()}
return {
"target_store": target_store,
"dry_run": _should_do_dry_run(task),
"certificate_alias": publish_config.get("certificate_alias"),
"package_names": publish_config["package_names"],
"rollout_percentage": rollout_percentage,
"sgs_service_account_id": store_config["service_account_id"],
"sgs_access_token": store_config["access_token"],
"submit": task.get("submit", False),
**credentials,
}

google_track = task.get("google_play_track", store_config["default_track"])
Expand Down
36 changes: 26 additions & 10 deletions pushapkscript/src/pushapkscript/script.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@

log = logging.getLogger(__name__)

# Human readable names for the stores mozapkpublisher can target, used in the warnings
# logged before a publication.
STORE_NAMES = {
"google": "Google Play",
"samsung": "the Samsung Galaxy Store",
"huawei": "the Huawei AppGallery",
}


async def async_main(context):
android_product = task.extract_android_product_from_scopes(context)
Expand Down Expand Up @@ -82,17 +90,25 @@ def _get_product_config(context, android_product):


def _log_warning_forewords(contact_server, dry_run, target_store):
if contact_server:
if target_store == "google":
if not dry_run:
log.warning(
"You will publish APKs to Google Play. This action is irreversible,\
if no error is detected either by this script or by Google Play."
)
else:
log.warning("APKs will be submitted, but no change will not be committed.")
store_name = STORE_NAMES.get(target_store, target_store)
if contact_server and not dry_run:
log.warning(
"You will publish APKs to {}. This action is irreversible, if no error is detected either by this script or by {}.".format(store_name, store_name)
)
elif target_store == "google":
# Google Play uploads inside an edit transaction it then declines to commit, and
# mocks the API outright when this instance may not contact the server.
if contact_server:
log.warning("APKs will be submitted to {}, but no change will be committed.".format(store_name))
else:
log.warning("This pushapk instance is not allowed to talk to {}. *All* requests will be mocked.".format(store_name))
else:
log.warning("This pushapk instance is not allowed to talk to Google Play. *All* requests will be mocked.")
# The other stores have no transaction to leave uncommitted, so mozapkpublisher
# skips the upload rather than performing or mocking it.
if contact_server:
log.warning("Nothing will be uploaded to {}, since this is a dry run.".format(store_name))
else:
log.warning("Nothing will be uploaded to {}, since this pushapk instance is not allowed to talk to it.".format(store_name))


def get_default_config():
Expand Down
55 changes: 48 additions & 7 deletions pushapkscript/tests/integration/test_integration_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,9 @@ def generate_fenix_config(self):
"service_account_id": "123",
"access_token": "456",
},
"huawei": {
"credentials_file": "huawei.json",
},
},
},
}
Expand Down Expand Up @@ -261,7 +264,7 @@ def write_task_file(self, task):
with open(task_file, "w") as f:
json.dump(task, f)

@unittest.mock.patch("pushapkscript.publish.push_apk")
@unittest.mock.patch("pushapkscript.publish.push_apk", autospec=True)
def test_main_fennec_style(self, push_apk):
task_generator = TaskGenerator()
self.write_task_file(task_generator.generate_task("aurora"))
Expand All @@ -287,10 +290,11 @@ def test_main_fennec_style(self, push_apk):
skip_checks_fennec=False,
sgs_service_account_id=None,
sgs_access_token=None,
huawei_credentials=None,
submit=False,
)

@unittest.mock.patch("pushapkscript.publish.push_apk")
@unittest.mock.patch("pushapkscript.publish.push_apk", autospec=True)
def test_main_focus_style(self, push_apk):
task_generator = TaskGenerator()
self.write_task_file(task_generator.generate_task("focus", "production"))
Expand All @@ -316,10 +320,11 @@ def test_main_focus_style(self, push_apk):
skip_checks_fennec=True,
sgs_service_account_id=None,
sgs_access_token=None,
huawei_credentials=None,
submit=False,
)

@unittest.mock.patch("pushapkscript.publish.push_apk")
@unittest.mock.patch("pushapkscript.publish.push_apk", autospec=True)
def test_main_fenix_style(self, push_apk):
task_generator = TaskGenerator()
self.write_task_file(task_generator.generate_task("fenix", "nightly"))
Expand All @@ -345,10 +350,11 @@ def test_main_fenix_style(self, push_apk):
skip_checks_fennec=True,
sgs_service_account_id=None,
sgs_access_token=None,
huawei_credentials=None,
submit=False,
)

@unittest.mock.patch("pushapkscript.publish.push_apk")
@unittest.mock.patch("pushapkscript.publish.push_apk", autospec=True)
def test_main_downloads_verifies_signature_and_gives_the_right_config_to_mozapkpublisher(self, push_apk):
task_generator = TaskGenerator()
self.write_task_file(task_generator.generate_task("aurora"))
Expand All @@ -374,10 +380,11 @@ def test_main_downloads_verifies_signature_and_gives_the_right_config_to_mozapkp
skip_checks_fennec=False,
sgs_service_account_id=None,
sgs_access_token=None,
huawei_credentials=None,
submit=False,
)

@unittest.mock.patch("pushapkscript.publish.push_apk")
@unittest.mock.patch("pushapkscript.publish.push_apk", autospec=True)
def test_main_allows_rollout_percentage(self, push_apk):
task_generator = TaskGenerator(rollout_percentage=25)
self.write_task_file(task_generator.generate_task("aurora"))
Expand All @@ -403,10 +410,11 @@ def test_main_allows_rollout_percentage(self, push_apk):
skip_checks_fennec=False,
sgs_service_account_id=None,
sgs_access_token=None,
huawei_credentials=None,
submit=False,
)

@unittest.mock.patch("pushapkscript.publish.push_apk")
@unittest.mock.patch("pushapkscript.publish.push_apk", autospec=True)
def test_main_allows_commit_transaction(self, push_apk):
task_generator = TaskGenerator(should_commit_transaction=True)

Expand All @@ -433,10 +441,11 @@ def test_main_allows_commit_transaction(self, push_apk):
skip_checks_fennec=False,
sgs_service_account_id=None,
sgs_access_token=None,
huawei_credentials=None,
submit=False,
)

@unittest.mock.patch("pushapkscript.publish.push_apk")
@unittest.mock.patch("pushapkscript.publish.push_apk", autospec=True)
def test_main_with_samsung_store(self, push_apk):
task_generator = TaskGenerator(should_commit_transaction=True, store="samsung")

Expand All @@ -463,6 +472,38 @@ def test_main_with_samsung_store(self, push_apk):
skip_checks_fennec=True,
sgs_service_account_id="123",
sgs_access_token="456",
huawei_credentials=None,
submit=False,
)

@unittest.mock.patch("pushapkscript.publish.push_apk", autospec=True)
def test_main_with_huawei_store(self, push_apk):
task_generator = TaskGenerator(should_commit_transaction=True, store="huawei")

self.write_task_file(task_generator.generate_task("fenix", channel="release"))

self._prepare_apks(task_generator, "fenix-production")
main(config_path=self.config_generator.generate_fenix_config())

push_apk.assert_called_with(
apks=[
MockFile("{}/work/cot/{}/public/build/target.apk".format(self.test_temp_dir, task_generator.arm_task_id)),
MockFile("{}/work/cot/{}/public/build/target.apk".format(self.test_temp_dir, task_generator.x86_task_id)),
],
secret=None,
track=None,
expected_package_names=["org.mozilla.fenix"],
store="huawei",
rollout_percentage=None,
dry_run=False,
contact_server=True,
skip_check_multiple_locales=True,
skip_check_ordered_version_codes=False,
skip_check_same_locales=True,
skip_checks_fennec=True,
sgs_service_account_id=None,
sgs_access_token=None,
huawei_credentials="huawei.json",
submit=False,
)

Expand Down
2 changes: 2 additions & 0 deletions pushapkscript/tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ def test_firefox_fake_prod():
"GOOGLE_CREDENTIALS_FOCUS_DEP_PATH": "focus",
"SGS_SERVICE_ACCOUNT_ID_DEP": "123456",
"SGS_ACCESS_TOKEN_DEP": "abcdef",
"HUAWEI_CREDENTIALS_DEP_PATH": "huawei",
}
_validate_config(context)

Expand All @@ -81,5 +82,6 @@ def test_firefox_prod():
"GOOGLE_CREDENTIALS_FOCUS_PATH": "focus",
"SGS_SERVICE_ACCOUNT_ID": "123456",
"SGS_ACCESS_TOKEN": "abcdef",
"HUAWEI_CREDENTIALS_PATH": "huawei",
}
_validate_config(context)
Loading