diff --git a/src/mozilla_taskgraph/__init__.py b/src/mozilla_taskgraph/__init__.py index cc477e4..1ec45be 100644 --- a/src/mozilla_taskgraph/__init__.py +++ b/src/mozilla_taskgraph/__init__.py @@ -5,6 +5,16 @@ from importlib import import_module from taskgraph.config import validate_graph_config +from taskgraph.util import schema + +# Schemas for YAML files should use dashed identifiers by default. If there are +# components of the schema for which there is a good reason to use another format, +# exceptions can be added here. +schema.EXCEPTED_SCHEMA_IDENTIFIERS.extend( + [ + "bitrise", + ] +) def register(graph_config): diff --git a/src/mozilla_taskgraph/worker_types.py b/src/mozilla_taskgraph/worker_types.py index 0b3dc18..7749a9b 100644 --- a/src/mozilla_taskgraph/worker_types.py +++ b/src/mozilla_taskgraph/worker_types.py @@ -1,5 +1,108 @@ from taskgraph.transforms.task import payload_builder -from voluptuous import Required +from voluptuous import Extra, Optional, Required + + +@payload_builder( + "scriptworker-bitrise", + schema={ + Required("bitrise"): { + Required( + "app", description="Name of Bitrise App to schedule workflows on." + ): str, + Required( + "workflows", + description="List of workflows to trigger on specified app.", + ): [str], + Optional( + "build_params", + description="Parameters describing the build context to pass " + "onto Bitrise. All keys are optional but specific workflows " + "may depend on particular keys being set.", + ): { + Optional( + "branch", + description="The branch running the build. For pull " + "requests, this should be the head branch.", + ): str, + Optional( + "branch_dest", + description="The destination branch where the branch " + "running the build will merge into. Only valid for pull " + "requests.", + ): str, + Optional( + "branch_dest_repo_owner", + description="The repository owning the destination branch. " + "Only valid for pull requests.", + ): str, + Optional( + "branch_repo_owner", description="The repository owning the branch." + ): str, + Optional( + "commit_hash", + description="The hash of the commit running the build.", + ): str, + Optional( + "commit_message", + description="The commit message of the commit running the build.", + ): str, + Optional( + "environments", + description="Environment variables to pass into the build.", + ): dict, + Optional( + "pull_request_author", + description="The author of the pull request running the build.", + ): str, + Optional( + "pull_request_id", + description="The id of the pull request running the build.", + ): int, + Optional( + "skip_git_status_report", + description="Whether Bitrise should send a status report to " + "Github (default False).", + ): bool, + Optional( + "tag", description="The tag of the commit running the build." + ): str, + }, + }, + Extra: object, + }, +) +def build_bitrise_payload(config, task, task_def): + bitrise = task["worker"]["bitrise"] + build_params = task_def["payload"] = bitrise.get("build_params") or {} + task_def["tags"]["worker-implementation"] = "scriptworker" + + scope_prefix = config.graph_config["scriptworker"]["scope-prefix"] + scopes = task_def.setdefault("scopes", []) + scopes.append(f"{scope_prefix}:bitrise:app:{bitrise['app']}") + scopes.extend( + [f"{scope_prefix}:bitrise:workflow:{wf}" for wf in bitrise["workflows"]] + ) + + # Set some build_params implicitly from Taskcluster params. + build_params.setdefault("commit_hash", config.params["head_rev"]) + build_params.setdefault("branch_repo_owner", config.params["head_repository"]) + + if config.params["head_ref"]: + build_params.setdefault("branch", config.params["head_ref"]) + + if config.params["head_tag"]: + build_params.setdefault("tag", config.params["head_tag"]) + + if config.params["tasks_for"] == "github-pull-request": + build_params.setdefault("pull_request_author", config.params["owner"]) + + if config.params["base_ref"]: + build_params.setdefault("branch_dest", config.params["base_ref"]) + + if config.params["base_repository"]: + build_params.setdefault( + "branch_dest_repo_owner", config.params["base_repository"] + ) @payload_builder( diff --git a/test/conftest.py b/test/conftest.py index a89690e..a8b670a 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -74,13 +74,15 @@ def file_url(self, path, pretty=False): def parameters(): return FakeParameters( { - "base_repository": "http://hg.example.com", + "base_ref": "123456", + "base_repository": "http://example.com/base/repo", "build_date": 0, "build_number": 1, "enable_always_target": True, - "head_repository": "http://hg.example.com", + "head_repository": "http://example.com/head/repo", "head_rev": "abcdef", "head_ref": "default", + "head_tag": "", "level": "1", "moz_build_date": 0, "next_version": "1.0.1", diff --git a/test/test_worker_types.py b/test/test_worker_types.py index 28fda33..993d75a 100644 --- a/test/test_worker_types.py +++ b/test/test_worker_types.py @@ -1,6 +1,106 @@ +import inspect +from pprint import pprint + +import pytest +from taskgraph.transforms.task import payload_builders +from taskgraph.util.schema import validate_schema + from mozilla_taskgraph import worker_types +@pytest.mark.parametrize( + "worker,extra_params,expected", + ( + pytest.param({}, {}, Exception, id="missing bitrise"), + pytest.param({"bitrise": {"workflows": []}}, {}, Exception, id="missing app"), + pytest.param( + {"bitrise": {"app": "foo"}}, {}, Exception, id="missing workflows" + ), + pytest.param( + {"bitrise": {"app": "some-app", "workflows": ["bar", "baz"]}}, + {}, + { + "payload": { + "branch": "default", + "branch_repo_owner": "http://example.com/head/repo", + "commit_hash": "abcdef", + }, + "scopes": [ + "foo:bitrise:app:some-app", + "foo:bitrise:workflow:bar", + "foo:bitrise:workflow:baz", + ], + "tags": {"worker-implementation": "scriptworker"}, + }, + id="default params", + ), + pytest.param( + {"bitrise": {"app": "some-app", "workflows": ["bar"]}}, + {"tasks_for": "github-pull-request"}, + { + "payload": { + "branch": "default", + "branch_dest": "123456", + "branch_dest_repo_owner": "http://example.com/base/repo", + "branch_repo_owner": "http://example.com/head/repo", + "commit_hash": "abcdef", + "pull_request_author": "some-owner", + }, + "scopes": ["foo:bitrise:app:some-app", "foo:bitrise:workflow:bar"], + "tags": {"worker-implementation": "scriptworker"}, + }, + id="pull request", + ), + pytest.param( + {"bitrise": {"app": "some-app", "workflows": ["bar"]}}, + { + "base_ref": "", + "base_repository": "", + "head_ref": "", + "head_tag": "some-tag", + "tasks_for": "github-pull-request", + }, + { + "payload": { + "branch_repo_owner": "http://example.com/head/repo", + "commit_hash": "abcdef", + "pull_request_author": "some-owner", + "tag": "some-tag", + }, + "scopes": ["foo:bitrise:app:some-app", "foo:bitrise:workflow:bar"], + "tags": {"worker-implementation": "scriptworker"}, + }, + id="opposite params", # this test helps hit other half of if statements + ), + ), +) +def test_build_bitrise_payload( + make_graph_config, make_transform_config, parameters, worker, extra_params, expected +): + schema = payload_builders["scriptworker-bitrise"].schema + + graph_config = make_graph_config( + extra_config={"scriptworker": {"scope-prefix": "foo"}} + ) + parameters.update(extra_params) + config = make_transform_config(params=parameters, graph_cfg=graph_config) + + worker.setdefault("implementation", "scriptworker-bitrise") + task = {"worker": worker} + task_def = {"tags": {}} + + if inspect.isclass(expected) and issubclass(expected, Exception): + with pytest.raises(expected): + validate_schema(schema, worker, "schema error") + worker_types.build_bitrise_payload(config, task, task_def) + else: + validate_schema(schema, worker, "schema error") + worker_types.build_bitrise_payload(config, task, task_def) + print("Dumping result:") + pprint(task_def, indent=2) + assert task_def == expected + + def test_build_shipit_payload(): task = {"worker": {"release-name": "foo"}} task_def = {"tags": {}}