diff --git a/shipitscript/src/shipitscript/data/config_schema.json b/shipitscript/src/shipitscript/data/config_schema.json index 66da0bff2..d6d416669 100644 --- a/shipitscript/src/shipitscript/data/config_schema.json +++ b/shipitscript/src/shipitscript/data/config_schema.json @@ -3,6 +3,7 @@ "type": "object", "required": [ "mark_as_shipped_schema_file", + "mark_as_merged_schema_file", "taskcluster_scope_prefix", "shipit_instance" ], @@ -10,6 +11,9 @@ "mark_as_shipped_schema_file": { "type": "string" }, + "mark_as_merged_schema_file": { + "type": "string" + }, "taskcluster_scope_prefix": { "type": "string" }, diff --git a/shipitscript/src/shipitscript/data/mark_as_merged_task_schema.json b/shipitscript/src/shipitscript/data/mark_as_merged_task_schema.json new file mode 100644 index 000000000..6ba514bcf --- /dev/null +++ b/shipitscript/src/shipitscript/data/mark_as_merged_task_schema.json @@ -0,0 +1,33 @@ +{ + "title": "Taskcluster shipit mark-as-merged task schema", + "type": "object", + "properties": { + "dependencies": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string" + } + }, + "scopes": { + "type": "array", + "minItems": 2, + "uniqueItems": true, + "items": { + "type": "string" + } + }, + "payload": { + "type": "object", + "properties": { + "automation_id": { + "type": "integer" + } + }, + "required": ["automation_id"], + "additionalProperties": false + } + }, + "required": ["dependencies", "scopes", "payload"] +} diff --git a/shipitscript/src/shipitscript/script.py b/shipitscript/src/shipitscript/script.py index 47c8c86a4..8c57e46a2 100644 --- a/shipitscript/src/shipitscript/script.py +++ b/shipitscript/src/shipitscript/script.py @@ -35,6 +35,14 @@ def mark_as_shipped_action(context): ship_actions.mark_as_shipped_v2(context.ship_it_instance_config, release_name) +def mark_as_merged_action(context): + """Action to mark a merge automation as complete""" + automation_id = context.task["payload"]["automation_id"] + + log.info("Marking the merge automation as complete ...") + ship_actions.mark_as_merged(context.ship_it_instance_config, automation_id) + + def create_new_release_action(context): """Determine if there is a shippable release and create it if so in Shipit""" payload = context.task["payload"] @@ -109,6 +117,7 @@ def update_product_channel_version_action(context): # ACTION_MAP {{{1 ACTION_MAP = { "mark-as-shipped": mark_as_shipped_action, + "mark-as-merged": mark_as_merged_action, "create-new-release": create_new_release_action, "update-product-channel-version": update_product_channel_version_action, } @@ -123,6 +132,7 @@ def get_default_config(): "work_dir": os.path.join(parent_dir, "work_dir"), "verbose": False, "mark_as_shipped_schema_file": os.path.join(data_dir, "mark_as_shipped_task_schema.json"), + "mark_as_merged_schema_file": os.path.join(data_dir, "mark_as_merged_task_schema.json"), "create_new_release_schema_file": os.path.join(data_dir, "create_new_release_task_schema.json"), } diff --git a/shipitscript/src/shipitscript/ship_actions.py b/shipitscript/src/shipitscript/ship_actions.py index 6c69dcfec..56645c5df 100644 --- a/shipitscript/src/shipitscript/ship_actions.py +++ b/shipitscript/src/shipitscript/ship_actions.py @@ -104,6 +104,13 @@ def mark_as_shipped_v2(shipit_config, release_name): check_release_has_values_v2(release_api, release_name, headers, status="shipped") +def mark_as_merged(shipit_config, automation_id): + release_api, headers = get_shipit_api_instance(shipit_config) + + log.info("Marking merge automation as complete...") + release_api.complete_merge_automation(automation_id, headers=headers) + + def get_product_channel_version(shipit_config, product, channel): release_api, headers = get_shipit_api_instance(shipit_config) log.info(f"Getting the current version of {product} {channel}...") diff --git a/shipitscript/src/shipitscript/shipitapi.py b/shipitscript/src/shipitscript/shipitapi.py index 92891d05c..f84acc5c8 100644 --- a/shipitscript/src/shipitscript/shipitapi.py +++ b/shipitscript/src/shipitscript/shipitapi.py @@ -180,3 +180,12 @@ def update_product_channel_version(self, product, channel, version, headers=None except Exception: log.error(f"Caught error while getting version for {product} {channel}!", exc_info=True) raise + + def complete_merge_automation(self, automation_id, headers={}): + """Method to map over the PATCH /merge-automation/{automation_id} API in shipit + + Parameters: + * automation_id + """ + resp = self._request(api_endpoint=f"/merge-automation/{automation_id}", method="PATCH", data="", headers=headers).content + return resp diff --git a/shipitscript/src/shipitscript/task.py b/shipitscript/src/shipitscript/task.py index 4fef743a3..7a7ff9999 100644 --- a/shipitscript/src/shipitscript/task.py +++ b/shipitscript/src/shipitscript/task.py @@ -9,6 +9,7 @@ # SCHEMA_MAP {{{1 SCHEMA_MAP = { "mark-as-shipped": "mark_as_shipped_schema_file", + "mark-as-merged": "mark_as_merged_schema_file", "create-new-release": "create_new_release_schema_file", "update-product-channel-version": "update_product_channel_version_schema_file", } diff --git a/shipitscript/tests/conftest.py b/shipitscript/tests/conftest.py index 02afd35ba..320296819 100644 --- a/shipitscript/tests/conftest.py +++ b/shipitscript/tests/conftest.py @@ -9,7 +9,10 @@ @pytest.fixture def context(): context = Context() - context.config = {"mark_as_shipped_schema_file": os.path.join(os.path.dirname(shipitscript.__file__), "data", "mark_as_shipped_task_schema.json")} + context.config = { + "mark_as_shipped_schema_file": os.path.join(os.path.dirname(shipitscript.__file__), "data", "mark_as_shipped_task_schema.json"), + "mark_as_merged_schema_file": os.path.join(os.path.dirname(shipitscript.__file__), "data", "mark_as_merged_task_schema.json"), + } context.config["shipit_instance"] = { "scope": "project:releng:ship-it:server:dev", "api_root_v2": "http://some-ship-it.url/v2", diff --git a/shipitscript/tests/test_script.py b/shipitscript/tests/test_script.py index 64e92d563..2eca00ed8 100644 --- a/shipitscript/tests/test_script.py +++ b/shipitscript/tests/test_script.py @@ -30,6 +30,28 @@ async def test_mark_as_shipped(context, monkeypatch, scopes): ) +@pytest.mark.parametrize("scopes", (["project:releng:ship-it:action:mark-as-merged", "project:releng:ship-it:server:dev"],)) +@pytest.mark.asyncio +async def test_mark_as_merged(context, monkeypatch, scopes): + context.task["scopes"] = scopes + context.task["payload"] = {"automation_id": 123} + + mark_as_merged_mock = MagicMock() + monkeypatch.setattr(ship_actions, "mark_as_merged", mark_as_merged_mock) + + await script.async_main(context) + mark_as_merged_mock.assert_called_with( + { + "scope": scopes[-1], + "api_root_v2": "http://some-ship-it.url/v2", + "timeout_in_seconds": 1, + "taskcluster_client_id": "some-id", + "taskcluster_access_token": "some-token", + }, + 123, + ) + + @pytest.mark.parametrize( "task,raises", ( @@ -79,6 +101,7 @@ def test_get_default_config(): "work_dir": os.path.join(parent_dir, "work_dir"), "verbose": False, "mark_as_shipped_schema_file": os.path.join(data_dir, "mark_as_shipped_task_schema.json"), + "mark_as_merged_schema_file": os.path.join(data_dir, "mark_as_merged_task_schema.json"), "create_new_release_schema_file": os.path.join(data_dir, "create_new_release_task_schema.json"), } diff --git a/shipitscript/tests/test_ship_actions.py b/shipitscript/tests/test_ship_actions.py index 16a0c9c2a..81011d233 100644 --- a/shipitscript/tests/test_ship_actions.py +++ b/shipitscript/tests/test_ship_actions.py @@ -28,3 +28,23 @@ def test_mark_as_shipped_v2(monkeypatch, timeout, expected_timeout): release_instance_mock.update_status.assert_called_with( "Firefox-59.0b1-build1", status="shipped", headers={"X-Forwarded-Proto": "https", "X-Forwarded-Port": "80"} ) + + +@pytest.mark.parametrize("timeout, expected_timeout", ((1, 1), ("10", 10), (None, 60))) +def test_mark_as_merged(monkeypatch, timeout, expected_timeout): + ReleaseClassMock = MagicMock() + release_instance_mock = MagicMock() + ReleaseClassMock.side_effect = lambda *args, **kwargs: release_instance_mock + monkeypatch.setattr(shipitscript.ship_actions, "Release_V2", ReleaseClassMock) + + ship_it_instance_config = {"taskcluster_client_id": "some-id", "taskcluster_access_token": "some-token", "api_root_v2": "http://some.ship-it.tld/api/root"} + if timeout is not None: + ship_it_instance_config["timeout_in_seconds"] = timeout + automation_id = 123 + + shipitscript.ship_actions.mark_as_merged(ship_it_instance_config, automation_id) + + ReleaseClassMock.assert_called_with( + taskcluster_client_id="some-id", taskcluster_access_token="some-token", api_root="http://some.ship-it.tld/api/root", timeout=expected_timeout + ) + release_instance_mock.complete_merge_automation.assert_called_with(123, headers={"X-Forwarded-Proto": "https", "X-Forwarded-Port": "80"}) diff --git a/shipitscript/tests/test_shipitapi.py b/shipitscript/tests/test_shipitapi.py index e11885f9a..1d372dbbd 100644 --- a/shipitscript/tests/test_shipitapi.py +++ b/shipitscript/tests/test_shipitapi.py @@ -66,6 +66,17 @@ def __init__(self): # make sure we don't modify the passed headers dictionary in the methods assert headers == {"X-Test": "yes"} + # test that complete_merge_automation calls the right URL + headers = {"X-Test": "yes"} + ret = release.complete_merge_automation(123, headers=headers) + ret_json = json.loads(ret) + assert ret_json["test"] is True + correct_url = "https://www.apiroot.com/merge-automation/123" + release.session.request.assert_called_with(data="", headers=mock.ANY, method="PATCH", timeout=mock.ANY, verify=mock.ANY, url=correct_url) + api_call_count += 1 + assert release.session.request.call_count == api_call_count + assert headers == {"X-Test": "yes"} + # test that exception raised if error, and retry api call release.session.request.return_value.status_code = 400 with pytest.raises(requests.exceptions.HTTPError):