diff --git a/ansible/roles/operator-pipeline/templates/openshift/pipelines/community-hosted-pipeline.yml b/ansible/roles/operator-pipeline/templates/openshift/pipelines/community-hosted-pipeline.yml index 9b42e0a65..120b7ddf8 100644 --- a/ansible/roles/operator-pipeline/templates/openshift/pipelines/community-hosted-pipeline.yml +++ b/ansible/roles/operator-pipeline/templates/openshift/pipelines/community-hosted-pipeline.yml @@ -257,6 +257,24 @@ spec: - name: results workspace: results + - name: wait-prow-tests + taskRef: + name: wait-prow-tests + kind: Task + runAfter: + - add-bundle-to-index + params: + - name: pipeline_image + value: "$(params.pipeline_image)" + - name: supported_ocp_versions + value: "$(tasks.get-supported-versions.results.indices_ocp_versions)" + - name: request_url + value: "$(params.git_pr_url)" + - name: github_token_secret_name + value: "$(params.github_token_secret_name)" + - name: github_token_secret_key + value: "$(params.github_token_secret_key)" + finally: - name: set-github-passed-label diff --git a/ansible/roles/operator-pipeline/templates/openshift/tasks/wait-prow-tests.yml b/ansible/roles/operator-pipeline/templates/openshift/tasks/wait-prow-tests.yml new file mode 100644 index 000000000..df99900a1 --- /dev/null +++ b/ansible/roles/operator-pipeline/templates/openshift/tasks/wait-prow-tests.yml @@ -0,0 +1,67 @@ +--- +apiVersion: tekton.dev/v1beta1 +kind: Task +metadata: + name: wait-prow-tests +spec: + description: Wait for the termination of all prow tests. + + params: + - name: pipeline_image + description: The common pipeline image. + + - name: supported_ocp_versions + description: | + Space separated list of ocp versions supported by the pipeline + and the bundle + + - name: github_host_url + description: | + The GitHub host, adjust this if you run a GitHub enterprise. + default: "https://api.github.com" + + - name: request_url + description: | + The GitHub issue or pull request URL where we want to add a new + comment. + + - name: github_token_secret_name + description: | + The name of the Kubernetes Secret that contains the GitHub token. + default: github + + - name: github_token_secret_key + description: | + The key within the Kubernetes Secret that contains the GitHub token. + default: token + + steps: + - name: poll-pr-labels + image: "$(params.pipeline_image)" + env: + - name: GITHUB_TOKEN + valueFrom: + secretKeyRef: + name: "$(params.github_token_secret_name)" + key: "$(params.github_token_secret_key)" + script: | + #!/usr/bin/env bash + set -xe + + expected_labels="" + for version in $(params.supported_ocp_versions); do + expected_labels="${expected_labels} --any ocp/${version//./\\.}/(pass|fail)" + done + + echo "Waiting for prow tests to finish..." + + github-wait-labels \ + --github-host-url "$(params.github_host_url)" \ + --pull-request-url "$(params.request_url)" \ + ${expected_labels} \ + --poll-interval 30 \ + --timeout 3600 \ + --verbose \ + >/tmp/labels || exit 1 + + grep -E 'ocp/[^/]+/fail' /tmp/labels && exit 1 || exit 0 diff --git a/operator-pipeline-images/operatorcert/entrypoints/github_wait_labels.py b/operator-pipeline-images/operatorcert/entrypoints/github_wait_labels.py index cdbbdf2eb..eebcadbc6 100644 --- a/operator-pipeline-images/operatorcert/entrypoints/github_wait_labels.py +++ b/operator-pipeline-images/operatorcert/entrypoints/github_wait_labels.py @@ -73,6 +73,9 @@ def __eq__(self, __value: object) -> bool: # pragma: nocover return self.wait_type == __value.wait_type and self.regexp == __value.regexp + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.wait_type}, {self.regexp})" + def setup_argparser() -> ap.ArgumentParser: """ @@ -109,12 +112,19 @@ def setup_argparser() -> ap.ArgumentParser: help="The GitHub pull request URL where we want to wait for labels.", ) - parser.add_argument("--timeout", default=60, help="Timeout for waiting in seconds.") + parser.add_argument( + "--timeout", type=int, default=60, help="Timeout for waiting in seconds." + ) parser.add_argument( - "--poll-interval", default=1, help="Interval between requests in seconds" + "--poll-interval", + type=int, + default=5, + help="Interval between requests in seconds", ) + parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output") + return parser @@ -157,6 +167,7 @@ def wait_on_pr_labels( poll_interval_s < timeout_s ), "Timeout needs to be bigger than the poll interval" + LOGGER.debug("Waiting for %s on PR #%s", wait_conditions, pull_request_id) start_time = time.monotonic() while time.monotonic() - start_time < timeout_s: pr_labels = get_pr_labels(repository, pull_request_id) @@ -169,14 +180,17 @@ def wait_on_pr_labels( time.sleep(poll_interval_s) + LOGGER.debug("Timed out!") return False -def main(): +def main() -> int: parser = setup_argparser() args = parser.parse_args() log_level = "INFO" + if args.verbose: + log_level = "DEBUG" setup_logger(level=log_level) github_auth = Auth.Token(os.environ["GITHUB_TOKEN"]) @@ -188,7 +202,7 @@ def main(): repository = github.get_repo(repo_url) except GithubException as exc: LOGGER.error("Unable to get repository from GitHub: %s", str(exc)) - exit(2) + return 2 conditions = WaitCondition.get_wait_conditions(args) if not wait_on_pr_labels( @@ -198,10 +212,10 @@ def main(): args.timeout, args.poll_interval, ): - exit(1) + return 1 - exit(0) + return 0 if __name__ == "__main__": # pragma: no cover - main() + exit(main()) diff --git a/operator-pipeline-images/tests/entrypoints/test_github_wait_labels.py b/operator-pipeline-images/tests/entrypoints/test_github_wait_labels.py index f536a8f7d..d3c267136 100644 --- a/operator-pipeline-images/tests/entrypoints/test_github_wait_labels.py +++ b/operator-pipeline-images/tests/entrypoints/test_github_wait_labels.py @@ -23,115 +23,100 @@ def test_setup_argparser() -> None: @patch("operatorcert.entrypoints.github_wait_labels.wait_on_pr_labels") @patch("operatorcert.entrypoints.github_wait_labels.Github.get_repo") @patch("operatorcert.entrypoints.github_wait_labels.setup_logger") -@patch("operatorcert.entrypoints.github_wait_labels.setup_argparser") -@patch("operatorcert.entrypoints.github_wait_labels.exit") def test_main( - mock_sys_exit: MagicMock, - mock_setup_argparser: MagicMock, mock_setup_logger: MagicMock, mock_github_get_repo: MagicMock, mock_wait_on_pr_labels: MagicMock, monkeypatch: Any, ) -> None: - args = MagicMock() - args.any = ["regexp1", "regexp2"] - args.none = ["regexp3"] - args.timeout = 3600 - args.poll_interval = 1 - - args.pull_request_url = "https://github.com/foo/bar/pull/123" - mock_setup_argparser.return_value.parse_args.return_value = args - mock_repo = MagicMock() mock_github_get_repo.return_value = mock_repo() + mock_wait_on_pr_labels.return_value = True monkeypatch.setenv("GITHUB_TOKEN", "foo_api_token") - main() + args = [ + "github-wait-labels", + "--github-host-url", + "https://api.example.com", + "--pull-request-url", + "https://example.com/namespace/repo/pull/999", + "--any", + r"ocp/4\.10/(pass|fail)", + "--any", + r"ocp/4\.11/(pass|fail)", + "--none", + r"do-not-merge", + "--poll-interval", + "15", + "--timeout", + "1000", + "--verbose", + ] + + with patch("sys.argv", args): + assert main() == 0 # want to test with __eq__ here to avoid mocking assert mock_wait_on_pr_labels.call_args[0][2] == [ - WaitCondition(WaitType.WaitAny, "regexp1"), - WaitCondition(WaitType.WaitAny, "regexp2"), - WaitCondition(WaitType.WaitNone, "regexp3"), + WaitCondition(WaitType.WaitAny, r"ocp/4\.10/(pass|fail)"), + WaitCondition(WaitType.WaitAny, r"ocp/4\.11/(pass|fail)"), + WaitCondition(WaitType.WaitNone, r"do-not-merge"), ] - assert mock_wait_on_pr_labels.call_args[0][1] == 123 - assert mock_wait_on_pr_labels.call_args[0][3] == 3600 - assert mock_wait_on_pr_labels.call_args[0][4] == 1 - - mock_sys_exit.assert_called_once_with(0) + assert mock_wait_on_pr_labels.call_args[0][1] == 999 + assert mock_wait_on_pr_labels.call_args[0][3] == 1000 + assert mock_wait_on_pr_labels.call_args[0][4] == 15 @patch("operatorcert.entrypoints.github_wait_labels.wait_on_pr_labels") @patch("operatorcert.entrypoints.github_wait_labels.Github.get_repo") @patch("operatorcert.entrypoints.github_wait_labels.setup_logger") -@patch("operatorcert.entrypoints.github_wait_labels.setup_argparser") -@patch("operatorcert.entrypoints.github_wait_labels.exit") def test_main_error( - mock_sys_exit: MagicMock, - mock_setup_argparser: MagicMock, mock_setup_logger: MagicMock, mock_github_get_repo: MagicMock, mock_wait_on_pr_labels: MagicMock, monkeypatch: Any, -): - args = MagicMock() - args.any = ["regexp1", "regexp2"] - args.none = ["regexp3"] - args.timeout = 3600 - args.poll_interval = 1 - - args.pull_request_url = "https://github.com/foo/bar/pull/123" - mock_setup_argparser.return_value.parse_args.return_value = args - +) -> None: mock_repo = MagicMock() mock_github_get_repo.return_value = mock_repo() monkeypatch.setenv("GITHUB_TOKEN", "foo_api_token") mock_wait_on_pr_labels.return_value = False - mock_sys_exit.side_effect = Exception("So that the utility terminates") - - with pytest.raises(Exception): - main() + args = [ + "github-wait-labels", + "--pull-request-url", + "https://example.com/namespace/repo/pull/999", + ] - mock_sys_exit.assert_called_once_with(1) + with patch("sys.argv", args): + assert main() == 1 @patch("operatorcert.entrypoints.github_wait_labels.wait_on_pr_labels") @patch("operatorcert.entrypoints.github_wait_labels.Github.get_repo") @patch("operatorcert.entrypoints.github_wait_labels.setup_logger") -@patch("operatorcert.entrypoints.github_wait_labels.setup_argparser") -@patch("operatorcert.entrypoints.github_wait_labels.exit") def test_main_get_repo_exception( - mock_sys_exit: MagicMock, - mock_setup_argparser: MagicMock, mock_setup_logger: MagicMock, mock_github_get_repo: MagicMock, mock_wait_on_pr_labels: MagicMock, monkeypatch: Any, -): - args = MagicMock() - args.any = ["regexp1", "regexp2"] - args.none = ["regexp3"] - args.timeout = 3600 - args.poll_interval = 1 - - args.pull_request_url = "https://github.com/foo/bar/pull/123" - mock_setup_argparser.return_value.parse_args.return_value = args - +) -> None: + mock_repo = MagicMock() mock_github_get_repo.side_effect = GithubException(0, "err", None) monkeypatch.setenv("GITHUB_TOKEN", "foo_api_token") mock_wait_on_pr_labels.return_value = False - mock_sys_exit.side_effect = Exception("So that the utility terminates") - - with pytest.raises((GithubException, Exception)): - main() + args = [ + "github-wait-labels", + "--pull-request-url", + "https://example.com/namespace/repo/pull/999", + ] - mock_sys_exit.assert_called_once_with(2) + with patch("sys.argv", args): + assert main() == 2 def test_get_pr_labels():