diff --git a/doc/changes/unreleased.md b/doc/changes/unreleased.md index 4dfeba0af..29f939fba 100644 --- a/doc/changes/unreleased.md +++ b/doc/changes/unreleased.md @@ -10,3 +10,4 @@ * #664: Removed deprecation warning for projects to switch over to BaseConfig * #637: Added id to workflow templates & synchronized on naming conventions +* #702: Fixed StepCustomization.content to list[StepContent] and security concern for `update_cookiecutter_default` diff --git a/exasol/toolbox/util/release/cookiecutter.py b/exasol/toolbox/util/release/cookiecutter.py index 82b2a89bb..67b70d6af 100644 --- a/exasol/toolbox/util/release/cookiecutter.py +++ b/exasol/toolbox/util/release/cookiecutter.py @@ -6,12 +6,15 @@ from exasol.toolbox.util.version import Version +PROJECT_ROOT = Path(__file__).resolve().parents[4] +COOKIECUTTER_JSON = PROJECT_ROOT / "project-template" / "cookiecutter.json" -def update_cookiecutter_default(cookiecutter_json: Path, version: Version) -> None: - contents = cookiecutter_json.read_text() + +def update_cookiecutter_default(version: Version) -> None: + contents = COOKIECUTTER_JSON.read_text() contents_as_dict = loads(contents) contents_as_dict["exasol_toolbox_version_range"] = f">={version},<{version.major+1}" updated_contents = dumps(contents_as_dict, indent=2) - cookiecutter_json.write_text(updated_contents) + COOKIECUTTER_JSON.write_text(updated_contents) diff --git a/exasol/toolbox/util/workflows/patch_workflow.py b/exasol/toolbox/util/workflows/patch_workflow.py index c4bc10d5a..9b48fac6e 100644 --- a/exasol/toolbox/util/workflows/patch_workflow.py +++ b/exasol/toolbox/util/workflows/patch_workflow.py @@ -18,6 +18,20 @@ class ActionType(str, Enum): class StepContent(BaseModel): + """ + The :class:`StepContent` is used to lightly validate the content which + would be used to REPLACE or INSERT_AFTER the specified step in the GitHub workflow. + + With the value `ConfigDict(extra="allow")`, this model allows for further fields + (e.g. `dummy`) to be specified without any validation. This design choice was + intentional, as GitHub already allows additional fields and may specify more fields + than what has been specified in this model. + + As the validation here is light, it is left to GitHub to validate the content. + For further information on what is allowed & expected for the fields, refer to + `GitHub's documentation on jobs..steps `__. + """ + model_config = ConfigDict(extra="allow") # This allows extra fields name: str @@ -29,19 +43,42 @@ class StepContent(BaseModel): class StepCustomization(BaseModel): + """ + The :class:`StepCustomization` is used to specify the desired modification: + * REPLACE - means that the contents of the specified `step_id` should be replaced + with whatever `content` is provided. + * INSERT_AFTER - means that the specified `content` should be inserted after + the specified `step_id`. + For a given step + """ + action: ActionType job: str step_id: str - content: StepContent + content: list[StepContent] class Workflow(BaseModel): + """ + The :class:`Workflow` is used to specify which workflow should be modified. + This is determined by the workflow `name`. A workflow can be modified by specifying: + * `remove_jobs` - job names in this list will be removed from the workflow. + * `step_customization` - items in this list indicate which job's step + should be modified. + """ + name: str remove_jobs: list[str] = Field(default_factory=list) step_customizations: list[StepCustomization] = Field(default_factory=list) class WorkflowPatcherConfig(BaseModel): + """ + The :class:`WorkflowPatcherConfig` is used to validate the expected format for + the `.workflow-patcher.yml`, which is used to modify the workflow templates provided + by the PTB. + """ + workflows: list[Workflow] diff --git a/noxconfig.py b/noxconfig.py index 8f20383e3..74e1ce021 100644 --- a/noxconfig.py +++ b/noxconfig.py @@ -9,7 +9,10 @@ from exasol.toolbox.config import BaseConfig from exasol.toolbox.nox.plugin import hookimpl from exasol.toolbox.tools.replace_version import update_github_yml -from exasol.toolbox.util.release.cookiecutter import update_cookiecutter_default +from exasol.toolbox.util.release.cookiecutter import ( + COOKIECUTTER_JSON, + update_cookiecutter_default, +) from exasol.toolbox.util.version import Version @@ -33,10 +36,6 @@ def github_actions(self) -> list[Path]: gh_actions = self.PARENT_PATH / ".github" / "actions" return [f for f in gh_actions.rglob("*") if f.is_file()] - @property - def cookiecutter_json(self) -> Path: - return self.PARENT_PATH / "project-template" / "cookiecutter.json" - @hookimpl def prepare_release_update_version(self, session, config, version: Version) -> None: for workflow in self.github_template_workflows: @@ -45,14 +44,12 @@ def prepare_release_update_version(self, session, config, version: Version) -> N for action in self.github_actions: update_github_yml(action, version) - update_cookiecutter_default(self.cookiecutter_json, version) + update_cookiecutter_default(version) @hookimpl def prepare_release_add_files(self, session, config) -> list[Path]: return ( - self.github_template_workflows - + self.github_actions - + [self.cookiecutter_json] + self.github_template_workflows + self.github_actions + [COOKIECUTTER_JSON] ) diff --git a/test/unit/util/release/cookiecutter_test.py b/test/unit/util/release/cookiecutter_test.py index e363d20a6..ed6dee49a 100644 --- a/test/unit/util/release/cookiecutter_test.py +++ b/test/unit/util/release/cookiecutter_test.py @@ -1,6 +1,7 @@ from inspect import cleandoc from json import loads from pathlib import Path +from unittest.mock import patch import pytest @@ -46,7 +47,10 @@ def cookiecutter_json(tmp_path: Path) -> Path: def test_update_cookiecutter_default( cookiecutter_json, version: Version, expected: str ): - update_cookiecutter_default(cookiecutter_json=cookiecutter_json, version=version) + with patch( + "exasol.toolbox.util.release.cookiecutter.COOKIECUTTER_JSON", cookiecutter_json + ): + update_cookiecutter_default(version=version) updated_json = cookiecutter_json.read_text() updated_dict = loads(updated_json) diff --git a/test/unit/util/workflows/patch_workflow_test.py b/test/unit/util/workflows/patch_workflow_test.py index aab1acd76..dc6d98151 100644 --- a/test/unit/util/workflows/patch_workflow_test.py +++ b/test/unit/util/workflows/patch_workflow_test.py @@ -28,11 +28,11 @@ class ExampleYaml: job: Tests step_id: checkout-repo content: - name: SCM Checkout - id: checkout-repo - uses: actions/checkout@v6 - with: - fetch-depth: 0 + - name: SCM Checkout + id: checkout-repo + uses: actions/checkout@v6 + with: + fetch-depth: 0 """