From f85d33f6aacac7842428dd084e80790db8978d72 Mon Sep 17 00:00:00 2001 From: Surya Teja Date: Sat, 28 Sep 2019 16:34:44 +0530 Subject: [PATCH] Add wait-for-deployments feature --- Dockerfile | 9 ------- README.md | 4 ++- action.yml | 4 +++ review_app_status.py | 24 +++++++++++++---- tests.py | 63 +++++++++++++++++++++++++++++++++++++++----- 5 files changed, 83 insertions(+), 21 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9598381..916450d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,5 @@ FROM python:3.7-alpine -LABEL version="1.0.1" \ - repository="https://github.com/niteoweb/reviewapps-deploy-status" \ - homepage="https://github.com/niteoweb" \ - maintainer="niteo.co" \ - "com.github.actions.name"="Heroku Review App Deployment Status" \ - "com.github.actions.description"="A Github Action to test the deployment status of a Heroku Review App." \ - "com.github.actions.icon"="git-pull-request" \ - "com.github.actions.color"="orange" - RUN pip install --upgrade pip RUN pip install requests==2.22.0 diff --git a/README.md b/README.md index ae9a71c..10d202e 100644 --- a/README.md +++ b/README.md @@ -22,12 +22,13 @@ A Github Action that tests the deployment status of a Heroku Review App. steps: - name: Run review-app test - uses: niteoweb/reviewapps-deploy-status@v1.0.1 + uses: niteoweb/reviewapps-deploy-status@v1.0.2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: interval: 10 # in seconds, optional, default is 10 accepted_responses: 200 # comma separated status codes, optional, default is 200 + deployments_timeout: 120 # in seconds, optional, default is 120 ``` > Note: Work flow should include `pull_request` event. @@ -40,6 +41,7 @@ A Github Action that tests the deployment status of a Heroku Review App. |---|---|---| | interval | Wait for this amount of seconds before retrying the build check | 10 | | accepted_responses | Allow/Accept the specified status codes | 200 | + | deployments_timeout | Maximum waiting time (in seconds) to fetch the deployments | 120 | ## Local Development * Create a Python virtual environment(version > 3.6). diff --git a/action.yml b/action.yml index 125c0dd..c6c4da1 100644 --- a/action.yml +++ b/action.yml @@ -20,3 +20,7 @@ inputs: description: Status(es) which can be accepted. Separated by comma. required: false default: 200 # All OK status + deployments_timeout: + description: Maximum waiting time to fetch the deployments. + required: false + default: 120 # 120 seconds diff --git a/review_app_status.py b/review_app_status.py index 8076713..07d964f 100644 --- a/review_app_status.py +++ b/review_app_status.py @@ -33,18 +33,29 @@ def _make_github_api_request(url): return r.json() -def _get_github_deployment_status_url(deployments_url, commit_sha): +def _get_github_deployment_status_url(deployments_url, commit_sha, timeout, interval): """Get deployment_status URL for the head commit. Inputs: deployments_url: This can be obtained from `pull_request` event payload. commit_sha: SHA of head/latest commit. This also can be obtained from `pull_request` event payload. + timeout: Maximum waiting time to fetch the deployments. + interval: Amount of time (in seconds) to check the deployments + if the deployments are not available. Output: Github deployment_status URL. """ - deployments = _make_github_api_request(deployments_url) - for deployment in deployments: - if deployment["sha"] == commit_sha: - return deployment["statuses_url"] + + if interval > timeout: + raise ValueError("Interval can't be greater than deployments_timeout.") + + while timeout > 0: + deployments = _make_github_api_request(deployments_url) + for deployment in deployments: + if deployment["sha"] == commit_sha: + return deployment["statuses_url"] + time.sleep(interval) + timeout = timeout - interval + logger.info(f"Waiting for deployments. Will check after {interval} seconds.") raise ValueError("No deployment found for the lastest commit.") @@ -102,6 +113,7 @@ def main(): All the inputs are received from workflow as environment variables. """ interval_arg = int(os.environ["INPUT_INTERVAL"]) + deployments_timeout_arg = int(os.environ["INPUT_DEPLOYMENTS_TIMEOUT"]) accepted_responses_arg = os.environ["INPUT_ACCEPTED_RESPONSES"] event_payload_path = os.environ["GITHUB_EVENT_PATH"] @@ -115,6 +127,8 @@ def main(): github_deployment_status_url = _get_github_deployment_status_url( pull_request_data["repository"]["deployments_url"], pull_request_data["pull_request"]["head"]["sha"], + deployments_timeout_arg, + interval_arg, ) reviewapp_build_data = _get_build_data(github_deployment_status_url, interval_arg) diff --git a/tests.py b/tests.py index fb8f324..8b64c2b 100644 --- a/tests.py +++ b/tests.py @@ -53,6 +53,18 @@ def test_make_github_api_request_failure(): ) +@mock.patch("review_app_status._make_github_api_request") +def test_get_deployment_status_interval_greater_failure(mock_github_request): + + from review_app_status import _get_github_deployment_status_url + + with pytest.raises(ValueError) as excinfo: + url = _get_github_deployment_status_url( + "https://foo.bar/deployments", "commitsha12345", 3, 4 + ) + assert "Interval can't be greater than deployments_timeout." in str(excinfo.value) + + @mock.patch("review_app_status._make_github_api_request") def test_get_deployment_status_url_success(mock_github_request): @@ -65,14 +77,14 @@ def test_get_deployment_status_url_success(mock_github_request): } ] url = _get_github_deployment_status_url( - "https://foo.bar/deployments", "commitsha12345" + "https://foo.bar/deployments", "commitsha12345", 2, 1 ) assert url == "https://foo.bar/deployment/statuses/1" mock_github_request.assert_called_once_with("https://foo.bar/deployments") @mock.patch("review_app_status._make_github_api_request") -def test_get_deployment_status_url_failure(mock_github_request): +def test_get_deployment_status_url_failure(mock_github_request, caplog): from review_app_status import _get_github_deployment_status_url @@ -81,11 +93,48 @@ def test_get_deployment_status_url_failure(mock_github_request): ] with pytest.raises(ValueError) as excinfo: url = _get_github_deployment_status_url( - "https://foo.bar/deployments", "commitsha12345" + "https://foo.bar/deployments", "commitsha12345", 2, 1 ) + assert ( + caplog.records[0].message + == "Waiting for deployments. Will check after 1 seconds." + ) assert "No deployment found for the lastest commit." in str(excinfo.value) - mock_github_request.assert_called_once_with("https://foo.bar/deployments") + mock_github_request.call_count == 2 + + +@mock.patch( + "review_app_status._make_github_api_request", + side_effect=[ + [], + [ + { + "sha": "commitsha12345", + "statuses_url": "https://foo.bar/deployment/statuses/1", + } + ], + ], +) +def test_get_deployment_pending_status(mock_github_request, caplog): + + from review_app_status import _get_github_deployment_status_url + + url = _get_github_deployment_status_url( + "https://foo.bar/deployments", "commitsha12345", 2, 1 + ) + + assert url == "https://foo.bar/deployment/statuses/1" + assert ( + caplog.records[0].message + == "Waiting for deployments. Will check after 1 seconds." + ) + assert mock_github_request.call_count == 2 + expected = [ + mock.call("https://foo.bar/deployments"), + mock.call("https://foo.bar/deployments"), + ] + assert mock_github_request.call_args_list == expected @mock.patch("review_app_status._make_github_api_request") @@ -181,6 +230,7 @@ def test_check_review_app_custom_status_success(caplog): @mock.patch.dict( os.environ, { + "INPUT_DEPLOYMENTS_TIMEOUT": "20", "INPUT_INTERVAL": "10", "INPUT_ACCEPTED_RESPONSES": "200, 302", "GITHUB_EVENT_PATH": "./test_path", @@ -212,7 +262,7 @@ def test_main_success( mock_file.assert_called_with("./test_path") mock_deployment_status_url.assert_called_once_with( - "http://foo.bar/deployments", "commit12345" + "http://foo.bar/deployments", "commit12345", 20, 10 ) mock_build_data.assert_called_once_with("http://foo.bar/deployment_status", 10) mock_review_app_deployment.assert_called_once_with( @@ -226,6 +276,7 @@ def test_main_success( @mock.patch.dict( os.environ, { + "INPUT_DEPLOYMENTS_TIMEOUT": "20", "INPUT_INTERVAL": "10", "INPUT_ACCEPTED_RESPONSES": "200, 302", "GITHUB_EVENT_PATH": "./test_path", @@ -254,7 +305,7 @@ def test_main_failure( mock_file.assert_called_with("./test_path") mock_deployment_status_url.assert_called_once_with( - "http://foo.bar/deployments", "commit12345" + "http://foo.bar/deployments", "commit12345", 20, 10 ) mock_build_data.assert_called_once_with("http://foo.bar/deployment_status", 10)