diff --git a/CHANGELOG.md b/CHANGELOG.md index 4789abe..81c5a64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [0.10.0] - TBD +### Changed +- Configuration: `taskcluster_scope_prefix` now becomes `taskcluster_scope_prefixes` and takes a JSON array. + + ## [0.9.0] - 2018-11-23 ### Changed diff --git a/examples/config.example.json b/examples/config.example.json index 5ed0c0e..338d569 100644 --- a/examples/config.example.json +++ b/examples/config.example.json @@ -25,7 +25,7 @@ "release": "release" }, - "taskcluster_scope_prefix": "project:releng:googleplay:", + "taskcluster_scope_prefixes": ["project:releng:googleplay:"], "verbose": true } diff --git a/pushapkscript/task.py b/pushapkscript/task.py index c355279..b155395 100644 --- a/pushapkscript/task.py +++ b/pushapkscript/task.py @@ -8,15 +8,49 @@ def extract_android_product_from_scopes(context): - prefix = context.config['taskcluster_scope_prefix'] + prefixes = _get_scope_prefixes(context) + scopes = _extract_scopes_from_unique_prefix( + scopes=context.task['scopes'], + prefixes=prefixes + ) scope = get_single_item_from_sequence( - context.task['scopes'], - condition=lambda scope: scope.startswith(prefix), + scopes, + condition=lambda _: True, # scopes must just contain 1 single item ErrorClass=TaskVerificationError, - no_item_error_message='No valid scope found. Task must have a scope that starts with "{}"'.format(prefix), - too_many_item_error_message='More than one valid scope given', + no_item_error_message='No scope starting with any of these prefixes {} found'.format(prefixes), + too_many_item_error_message='More than one scope found', ) - android_product = scope[len(prefix):] + android_product = scope.split(':')[-1] return android_product + + +def _get_scope_prefixes(context): + prefixes = context.config['taskcluster_scope_prefixes'] + return [ + prefix if prefix.endswith(':') else '{}:'.format(prefix) + for prefix in prefixes + ] + + +def _extract_scopes_from_unique_prefix(scopes, prefixes): + scopes = [ + scope + for scope in scopes + for prefix in prefixes + if scope.startswith(prefix) + ] + _check_scopes_exist_and_all_have_the_same_prefix(scopes, prefixes) + return scopes + + +def _check_scopes_exist_and_all_have_the_same_prefix(scopes, prefixes): + for prefix in prefixes: + if all(scope.startswith(prefix) for scope in scopes): + break + else: + raise TaskVerificationError( + 'Scopes must exist and all have the same prefix. ' + 'Given scopes: {}. Allowed prefixes: {}'.format(scopes, prefixes) + ) diff --git a/pushapkscript/test/integration/test_integration_script.py b/pushapkscript/test/integration/test_integration_script.py index 104b977..e0d7572 100644 --- a/pushapkscript/test/integration/test_integration_script.py +++ b/pushapkscript/test/integration/test_integration_script.py @@ -57,7 +57,7 @@ def _generate_json(self): "certificate": "/dummy/path/to/certificate.p12" }} }}, - "taskcluster_scope_prefix": "project:releng:googleplay:" + "taskcluster_scope_prefixes": ["project:releng:googleplay:"] }}'''.format( work_dir=self.work_dir, test_data_dir=self.test_data_dir, project_data_dir=project_data_dir, keystore_path=self.keystore_manager.keystore_path, diff --git a/pushapkscript/test/test_googleplay.py b/pushapkscript/test/test_googleplay.py index a164284..86e17af 100644 --- a/pushapkscript/test/test_googleplay.py +++ b/pushapkscript/test/test_googleplay.py @@ -30,7 +30,7 @@ def setUp(self): 'certificate': '/path/to/dummy_non_p12_file', }, }, - 'taskcluster_scope_prefix': 'project:releng:googleplay:', + 'taskcluster_scope_prefixes': ['project:releng:googleplay:'], } self.context.task = { 'scopes': ['project:releng:googleplay:release'], diff --git a/pushapkscript/test/test_jarsigner.py b/pushapkscript/test/test_jarsigner.py index 8fe57e9..b570c4b 100644 --- a/pushapkscript/test/test_jarsigner.py +++ b/pushapkscript/test/test_jarsigner.py @@ -20,7 +20,7 @@ def setUp(self): 'release': 'release_alias', 'dep': 'dep_alias', }, - 'taskcluster_scope_prefix': 'project:releng:googleplay:', + 'taskcluster_scope_prefixes': ['project:releng:googleplay:'], } self.context.task = { 'scopes': ['project:releng:googleplay:aurora'], @@ -29,7 +29,7 @@ def setUp(self): self.minimal_context = MagicMock() self.minimal_context.config = { 'jarsigner_key_store': '/path/to/keystore', - 'taskcluster_scope_prefix': 'project:releng:googleplay:', + 'taskcluster_scope_prefixes': ['project:releng:googleplay:'], } self.minimal_context.task = { 'scopes': ['project:releng:googleplay:aurora'], diff --git a/pushapkscript/test/test_manifest.py b/pushapkscript/test/test_manifest.py index dfd96fb..5ece5fa 100644 --- a/pushapkscript/test/test_manifest.py +++ b/pushapkscript/test/test_manifest.py @@ -16,7 +16,7 @@ def test_verify(monkeypatch, does_apk_have_expected_digest, raises): monkeypatch.setattr(manifest, '_does_apk_have_expected_digest', lambda _, __: does_apk_have_expected_digest) context = MagicMock() - context.config = {'taskcluster_scope_prefix': 'project:releng:googleplay:'} + context.config = {'taskcluster_scope_prefixes': ['project:releng:googleplay:']} context.task = { 'scopes': ['project:releng:googleplay:aurora'], } diff --git a/pushapkscript/test/test_task.py b/pushapkscript/test/test_task.py index dd927be..9f1f9c0 100644 --- a/pushapkscript/test/test_task.py +++ b/pushapkscript/test/test_task.py @@ -103,37 +103,69 @@ def test_validate_real_life_tasks(context, task): validate_task_schema(context) -@pytest.mark.parametrize('prefix, task, expected', (( - 'project:releng:googleplay:', - {'scopes': ['project:releng:googleplay:aurora']}, +@pytest.mark.parametrize('prefixes, scopes, raises, expected', (( + ['project:releng:googleplay:'], + ['project:releng:googleplay:aurora'], + False, 'aurora', ), ( - 'project:releng:googleplay:', - {'scopes': ['project:releng:googleplay:beta']}, + ['project:releng:googleplay:'], + ['project:releng:googleplay:beta'], + False, 'beta', ), ( - 'project:releng:googleplay:', - {'scopes': ['project:releng:googleplay:release']}, + ['project:releng:googleplay:'], + ['project:releng:googleplay:release'], + False, 'release', ), ( - 'project:mobile:focus:googleplay:product:', - {'scopes': ['project:mobile:focus:googleplay:product:focus']}, + ['project:mobile:focus:googleplay:product:'], + ['project:mobile:focus:googleplay:product:focus'], + False, 'focus', +), ( + ['project:mobile:focus:googleplay:product:'], + [], + True, + None, +), ( + [], + ['project:mobile:focus:googleplay:product:focus'], + True, + None, +), ( + ['project:releng:googleplay:'], + ['project:releng:googleplay:beta', 'project:releng:googleplay:release'], + True, + None, +), ( + ['project:releng:googleplay:', 'project:mobile:focus:releng:googleplay'], + ['project:releng:googleplay:beta', 'project:mobile:focus:releng:googleplay:product:focus'], + True, + None, +), ( + ['project:mobile:reference_browser:releng:googleplay:'], + [ + 'project:mobile:reference_browser:releng:googleplay:product:reference_browser', + 'project:mobile:reference_browser:releng:googleplay:product:reference_browser_dep', + ], + True, + None, +), ( + ['project:mobile:focus:releng:googleplay', 'project:mobile:reference_browser:releng:googleplay:'], + [ + 'project:mobile:focus:releng:googleplay:product:focus', + 'project:mobile:reference_browser:releng:googleplay:product:reference_browser', + ], + True, + None, ))) -def test_extract_supported_android_products(context, prefix, task, expected): - context.task = task - context.config = { - 'taskcluster_scope_prefix': prefix, - } - assert extract_android_product_from_scopes(context) == expected - +def test_extract_supported_android_products(context, prefixes, scopes, raises, expected): + context.task = {'scopes': scopes} + context.config = {'taskcluster_scope_prefixes': prefixes} -def test_extract_android_product_from_scopes_fails_when_too_many_products_are_given(context): - context.task = { - 'scopes': ['project:releng:googleplay:beta', 'project:releng:googleplay:release'] - } - context.config = { - 'taskcluster_scope_prefix': 'project:releng:googleplay:', - } - with pytest.raises(TaskVerificationError): - extract_android_product_from_scopes(context) + if raises: + with pytest.raises(TaskVerificationError): + extract_android_product_from_scopes(context) + else: + assert extract_android_product_from_scopes(context) == expected