diff --git a/doc/changes/unreleased.md b/doc/changes/unreleased.md index 58a72b160d..07091ef249 100644 --- a/doc/changes/unreleased.md +++ b/doc/changes/unreleased.md @@ -5,3 +5,7 @@ ## Bugfix * #563: Fixed merge-gate to prevent auto-merges from happening when integration tests failed + +## Feature + +* #829: Extended removing a job from a workflow to also remove it from the `needs` of another job diff --git a/doc/user_guide/features/github_workflows/create_and_update.rst b/doc/user_guide/features/github_workflows/create_and_update.rst index d2b1f9dffa..2273d95a5f 100644 --- a/doc/user_guide/features/github_workflows/create_and_update.rst +++ b/doc/user_guide/features/github_workflows/create_and_update.rst @@ -62,10 +62,10 @@ The PTB allows you to customise workflows by targeting specific jobs or steps: .. note:: - These operations do not currently cascade. For example, removing a job - without accounting for its downstream dependencies may result in errors. - You must manually adjust any subsequent steps that rely on the removed - job's or step's output. + These operations do not currently fully cascade. For example, removing a job will + remove it from the workflow and the needs of any subsequent job, but it does not + alter a matrix or steps relying on it. Thus, you must manually adjust any subsequent + steps relying on the removed job's or step's output. To utilize this feature, create a ``.workflow-patcher.yml`` file in your project's root directory, as specified further in :ref:`workflow_patcher`. This file will be diff --git a/exasol/toolbox/util/workflows/process_template.py b/exasol/toolbox/util/workflows/process_template.py index ca68987848..44257b1f1d 100644 --- a/exasol/toolbox/util/workflows/process_template.py +++ b/exasol/toolbox/util/workflows/process_template.py @@ -99,6 +99,27 @@ def _remove_jobs(self, remove_jobs: CommentedMap) -> None: self._verify_job_exists(job_name) logger.debug(f"Remove job '{job_name}'") self.jobs_dict.pop(job_name) + self._remove_job_from_needs(job_name=job_name) + + def _remove_job_from_needs(self, job_name: str) -> None: + """ + Remove the job from the needs of subsequent jobs. + This does NOT handle cases where the needed job is used for subsequent + operations in another job (e.g. when it is used to define the matrix). + """ + for other_job_name, other_job in self.jobs_dict.items(): + needs = other_job.get("needs") + if needs is None: + continue + + logger.debug( + f"Remove job '{job_name}' from needs of job '{other_job_name}'" + ) + needs_without_removed_job = [need for need in needs if need != job_name] + if len(needs_without_removed_job) == 0: + other_job.pop("needs") + return + other_job["needs"] = needs_without_removed_job def _verify_job_exists(self, job_name: str) -> None: if job_name not in self.jobs_dict: diff --git a/test/unit/util/workflows/process_template_test.py b/test/unit/util/workflows/process_template_test.py index 22f609e013..935cbc78a0 100644 --- a/test/unit/util/workflows/process_template_test.py +++ b/test/unit/util/workflows/process_template_test.py @@ -41,6 +41,16 @@ id: check-out-repository uses: actions/checkout@v6 + fast-report: + name: Fast Report + needs: + - build-documentation-and-check-links + - run-unit-tests + uses: ./.github/workflows/report.yml + secrets: inherit + permissions: + contents: read + """ @@ -79,6 +89,7 @@ def test__remove_jobs_works( assert list(workflow_dict["jobs"].keys()) == [ "build-documentation-and-check-links", "run-unit-tests", + "fast-report", ] # The original and resulting workflows should have the same values here. assert result["name"] == workflow_dict["name"] @@ -87,7 +98,41 @@ def test__remove_jobs_works( result["jobs"]["run-unit-tests"] == workflow_dict["jobs"]["run-unit-tests"] ) # The resulting workflow has job "build-documentation-and-check-links" removed. - assert list(result["jobs"].keys()) == ["run-unit-tests"] + assert list(result["jobs"].keys()) == ["run-unit-tests", "fast-report"] + # fast-report still needs "run-unit-tests" + assert result["jobs"]["fast-report"]["needs"] == ["run-unit-tests"] + + @staticmethod + def test__remove_jobs_works_removes_one_job( + workflow_name, workflow_dict, workflow_patcher, remove_job_yaml + ): + workflow_modifier = WorkflowModifier( + workflow_dict=workflow_dict, + patch_yaml=workflow_patcher.extract_by_workflow(workflow_name), + ) + + workflow_modifier._remove_jobs(["run-unit-tests"]) + + result = workflow_modifier.workflow_dict + assert result["jobs"]["fast-report"]["needs"] == [ + "build-documentation-and-check-links" + ] + + @staticmethod + def test__remove_jobs_works_removes_empty_needs( + workflow_name, workflow_dict, workflow_patcher, remove_job_yaml + ): + workflow_modifier = WorkflowModifier( + workflow_dict=workflow_dict, + patch_yaml=workflow_patcher.extract_by_workflow(workflow_name), + ) + + workflow_modifier._remove_jobs( + ["build-documentation-and-check-links", "run-unit-tests"] + ) + + result = workflow_modifier.workflow_dict + assert "needs" not in result["jobs"]["fast-report"] @staticmethod @pytest.mark.parametrize(