diff --git a/.import_linter_config b/.import_linter_config index 01a924fd8f..03b655cc76 100644 --- a/.import_linter_config +++ b/.import_linter_config @@ -27,4 +27,3 @@ ignore_imports = # To reduce the effort in using nox sessions (i.e. having to pass the config path # in each CLI usage), we allow the noxconfig to be imported within these modules. exasol.toolbox.nox.* -> noxconfig - exasol.toolbox.tools.template -> noxconfig diff --git a/doc/changes/unreleased.md b/doc/changes/unreleased.md index 441aa79060..6f88b33b79 100644 --- a/doc/changes/unreleased.md +++ b/doc/changes/unreleased.md @@ -9,5 +9,6 @@ ## Refactoring * #731: Reduced costly `test-python-environment.yml` to run when triggered on `main` or when the files related to the action are altered -* #785: Remove nox session `project:report` and metrics-schema, as superseded by Sonar usage -* #763: Parse and Manipulate Changes Files +* #785: Removed nox session `project:report` and metrics-schema, as superseded by Sonar usage +* #763: Parsed and manipulated Changes Files +* #788: Removed tbx workflow CLI commands, as superseded by nox session `workflow:generate` 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 a82eb48670..d2b1f9dffa 100644 --- a/doc/user_guide/features/github_workflows/create_and_update.rst +++ b/doc/user_guide/features/github_workflows/create_and_update.rst @@ -90,21 +90,3 @@ Add all Workflows to Your Project .. warning:: Some workflows depend on other workflows. Please ensure you have all the required workflows if you do not install all of them. - -.. note:: - - The commands: - - * ``tbx workflow install all`` - used to install workflows - * ``tbx workflow update all`` - used to update workflows - - are considered historic variants of this command. - - **Deprecation Notice:** - These ``tbx`` endpoints are marked as **deprecated** and are scheduled for removal - by **April 22nd, 2026**. - - Please note that these legacy commands do not allow users to use their specified - ``.workflow-patcher.yml`` file to further customise or patch workflows. Users - should transition to the ``nox``-based command to leverage full customisation - features. diff --git a/doc/user_guide/features/github_workflows/index.rst b/doc/user_guide/features/github_workflows/index.rst index fee087d5b1..366918dc28 100644 --- a/doc/user_guide/features/github_workflows/index.rst +++ b/doc/user_guide/features/github_workflows/index.rst @@ -28,8 +28,6 @@ workflows from the templates. In most cases, we recommend using _all_ workflows without change to ensure consistent interdependencies. For more details, see :ref:`ci_actions`. - The deprecated alternate is to use the CLI provided by - ``poetry run -- tbx workflow --help``. This will be removed by April 22nd, 2026. Workflows --------- diff --git a/exasol/toolbox/tools/issue.py b/exasol/toolbox/tools/issue.py index 837ab3359e..a41658841e 100644 --- a/exasol/toolbox/tools/issue.py +++ b/exasol/toolbox/tools/issue.py @@ -6,7 +6,6 @@ CLI = typer.Typer() PKG = "exasol.toolbox.templates.github.ISSUE_TEMPLATE" -TEMPLATE_TYPE = "issue" LEXER = "markdown" @@ -25,9 +24,7 @@ def show_issue( issue: str = typer.Argument(..., help="issue which shall be shown."), ) -> None: """Shows a specific issue.""" - template.show_templates( - template=issue, pkg=PKG, template_type=TEMPLATE_TYPE, lexer=LEXER - ) + template.show_templates(template=issue, pkg=PKG, lexer=LEXER) @CLI.command(name="diff") @@ -40,7 +37,9 @@ def diff_issue( ) -> None: """Diff a specific issue against the installed one.""" template.diff_template( - template=issue, dest=dest, pkg=PKG, template_type=TEMPLATE_TYPE + template=issue, + dest=dest, + pkg=PKG, ) @@ -57,9 +56,7 @@ def install_issue( Attention: If there is an existing issue with the same name it will be overwritten! """ - template.install_template( - template=issue, dest=dest, pkg=PKG, template_type=TEMPLATE_TYPE - ) + template.install_template(template=issue, dest=dest, pkg=PKG) @CLI.command(name="update") @@ -74,9 +71,7 @@ def update_issue( ), ) -> None: """Similar to install but checks for existing issues and shows diff""" - template.update_template( - template=issue, dest=dest, confirm=confirm, pkg=PKG, template_type=TEMPLATE_TYPE - ) + template.update_template(template=issue, dest=dest, confirm=confirm, pkg=PKG) if __name__ == "__main__": diff --git a/exasol/toolbox/tools/tbx.py b/exasol/toolbox/tools/tbx.py index 17403b839c..dd9cc96673 100644 --- a/exasol/toolbox/tools/tbx.py +++ b/exasol/toolbox/tools/tbx.py @@ -4,11 +4,9 @@ issue, lint, security, - workflow, ) CLI = typer.Typer() -CLI.add_typer(workflow.CLI, name="workflow", help="Manage github workflows") CLI.add_typer(security.CLI, name="security", help="Security related helpers") CLI.add_typer(issue.CLI, name="issue", help="Manage issue templates") CLI.add_typer(lint.CLI, name="lint", help="linting related helpers") diff --git a/exasol/toolbox/tools/template.py b/exasol/toolbox/tools/template.py index 1fcc28568f..fc5aa6006c 100644 --- a/exasol/toolbox/tools/template.py +++ b/exasol/toolbox/tools/template.py @@ -13,9 +13,6 @@ from rich.console import Console from rich.syntax import Syntax -from exasol.toolbox.util.workflows.workflow import Workflow -from noxconfig import PROJECT_CONFIG - stdout = Console() stderr = Console(stderr=True) @@ -49,13 +46,12 @@ def __rich__(self) -> str: def show_templates( template: str, pkg: str, - template_type: str, lexer: str, ) -> None: """Shows a specific template.""" templates = _templates(pkg) if template not in templates: - stdout.print(f"Unknown {template_type} <{template}>.", style="red") + stdout.print(f"Unknown <{template}>.", style="red") raise typer.Exit(code=1) template = templates[template] @@ -64,22 +60,11 @@ def show_templates( ) # type: ignore -def _render_template( - src: str | Path, -) -> str: - src_path = Path(src) - github_template_dict = PROJECT_CONFIG.github_template_dict - workflow = Workflow.load_from_template( - file_path=src_path, github_template_dict=github_template_dict - ) - return workflow.content + "\n" - - -def diff_template(template: str, dest: Path, pkg: str, template_type: str) -> None: +def diff_template(template: str, dest: Path, pkg: str) -> None: """Diff a specific template against the installed one.""" templates = _templates(pkg) if template not in templates: - stdout.print(f"Unknown {template_type} <{template}>.", style="red") + stdout.print(f"Unknown <{template}>.", style="red") raise typer.Exit(code=1) # Use Any type to enable reuse of the variable/binding name @@ -91,21 +76,15 @@ def diff_template(template: str, dest: Path, pkg: str, template_type: str) -> No old = stack.enter_context( open(old, encoding="utf-8") if old.exists() else io.StringIO("") ) - if template_type == "issue": - new = stack.enter_context(open(new, encoding="utf-8")) - old = old.read().split("\n") - new = new.read().split("\n") - elif template_type == "workflow": - new = _render_template(src=new) - old = old.read().split("\n") - new = new.split("\n") + new = stack.enter_context(open(new, encoding="utf-8")) + old = old.read().split("\n") + new = new.read().split("\n") diff = difflib.unified_diff(old, new, fromfile="old", tofile="new") stdout.print(Syntax("\n".join(diff), "diff")) def _install_template( - template_type: str, src: str | Path, dest: str | Path, exists_ok: bool = False, @@ -113,24 +92,18 @@ def _install_template( src, dest = Path(src), Path(dest) if dest.exists() and not exists_ok: - raise FileExistsError(f"{template_type} already exists") + raise FileExistsError(f"{dest} already exists") with ExitStack() as stack: - if template_type == "issue": - input_file = stack.enter_context(open(src, "rb")) - output_file = stack.enter_context(open(dest, "wb")) - output_file.write(input_file.read()) - return - + input_file = stack.enter_context(open(src, "rb")) output_file = stack.enter_context(open(dest, "wb")) - rendered_string = _render_template(src=src) - output_file.write(rendered_string.encode("utf-8")) + output_file.write(input_file.read()) -def _select_templates(template: str, pkg: str, template_type: str) -> Mapping[str, Any]: +def _select_templates(template: str, pkg: str) -> Mapping[str, Any]: templates = _templates(pkg) if template != "all" and template not in templates: - raise Exception(f"{template_type} <{template}> is unknown") + raise FileNotFoundError(f"<{template}> is unknown") templates = ( templates if template == "all" @@ -139,7 +112,7 @@ def _select_templates(template: str, pkg: str, template_type: str) -> Mapping[st return templates -def install_template(template: str, dest: Path, pkg: str, template_type: str) -> None: +def install_template(template: str, dest: Path, pkg: str) -> None: """ Installs the requested template into the target directory. @@ -149,49 +122,50 @@ def install_template(template: str, dest: Path, pkg: str, template_type: str) -> dest.mkdir(parents=True) try: - templates = _select_templates(template, pkg, template_type) - except Exception as ex: + templates = _select_templates(template, pkg) + except FileNotFoundError as ex: stderr.print(f"[red]{ex}[/red]") raise typer.Exit(-1) for name, path in templates.items(): destination = dest / f"{name}{path.suffix}" - _install_template(template_type, path, destination, exists_ok=True) + _install_template(path, destination, exists_ok=True) stderr.print(f"Installed {name} in {destination}") def update_template( - template: str, dest: Path, confirm: bool, pkg: str, template_type: str + template: str, + dest: Path, + confirm: bool, + pkg: str, ) -> None: """Similar to install but checks for existing templates and shows diff""" if not dest.exists(): dest.mkdir(parents=True) try: - templates = _select_templates(template, pkg, template_type) - except Exception as ex: + templates = _select_templates(template, pkg) + except FileNotFoundError as ex: stderr.print(f"[red]{ex}[/red]") raise typer.Exit(-1) if confirm: - install_template(template, dest, pkg, template_type) + install_template(template, dest, pkg) raise typer.Exit(0) for name, path in templates.items(): destination = dest / f"{name}{path.suffix}" try: - _install_template(template_type, path, destination, exists_ok=False) + _install_template(path, destination, exists_ok=False) stderr.print(f"Updated {name} in {destination}") except FileExistsError: - show_diff = typer.confirm( - f"{template_type} <{name}> already exists, show diff?" - ) + show_diff = typer.confirm(f"<{name}> already exists, show diff?") if show_diff: - diff_template(name, dest, pkg, template_type) + diff_template(name, dest, pkg) - overwrite = typer.confirm(f"Overwrite existing {template_type}?") + overwrite = typer.confirm(f"Overwrite existing {template}?") if overwrite: - _install_template(template_type, path, destination, exists_ok=True) + _install_template(path, destination, exists_ok=True) stderr.print(f"Updated {name} in {destination}") diff --git a/exasol/toolbox/tools/workflow.py b/exasol/toolbox/tools/workflow.py deleted file mode 100644 index ddab468915..0000000000 --- a/exasol/toolbox/tools/workflow.py +++ /dev/null @@ -1,111 +0,0 @@ -import warnings -from pathlib import Path - -import typer - -from exasol.toolbox.tools import template - -CLI = typer.Typer() -PKG = "exasol.toolbox.templates.github.workflows" -TEMPLATE_TYPE = "workflow" -LEXER = "yaml" - - -@CLI.command(name="list") -def list_workflows( - columns: bool = typer.Option( - False, "--columns", "-c", help="use column style presentation like `ls`" - ) -) -> None: - """List all available workflows.""" - template.list_templates(columns=columns, pkg=PKG) - warnings.warn( - "\033[31m`tbx workflow list` is deprecated; this will be removed after 2026-04-22\033[0m", - category=FutureWarning, - stacklevel=2, - ) - - -@CLI.command(name="show") -def show_workflow( - workflow: str = typer.Argument(..., help="Workflow which shall be shown."), -) -> None: - """Shows a specific workflow.""" - template.show_templates( - template=workflow, pkg=PKG, template_type=TEMPLATE_TYPE, lexer=LEXER - ) - warnings.warn( - "\033[31m`tbx workflow show` is deprecated; this will be removed after 2026-04-22\033[0m", - category=FutureWarning, - stacklevel=2, - ) - - -@CLI.command(name="diff") -def diff_workflow( - workflow: str = typer.Argument(..., help="workflow which shall be diffed."), - dest: Path = typer.Argument( - Path("./.github/workflows"), - help="target directory to diff the workflow against.", - ), -) -> None: - """Diff a specific workflow against the installed one.""" - template.diff_template( - template=workflow, dest=dest, pkg=PKG, template_type=TEMPLATE_TYPE - ) - warnings.warn( - "\033[31m`tbx workflow diff` is deprecated; this will be removed after 2026-04-22\033[0m", - category=FutureWarning, - stacklevel=2, - ) - - -@CLI.command(name="install") -def install_workflow( - workflow: str = typer.Argument("all", help="name of the workflow to install."), - dest: Path = typer.Argument( - Path("./.github/workflows"), help="target directory to install the workflow to." - ), -) -> None: - """ - Installs the requested workflow into the target directory. - - Attention: If there is an existing workflow with the same name it will be overwritten! - """ - template.install_template( - template=workflow, dest=dest, pkg=PKG, template_type=TEMPLATE_TYPE - ) - warnings.warn( - "\033[31m`tbx workflow install` is deprecated; this will be replaced by the Nox session `workflow:generate` after 2026-04-22\033[0m", - category=FutureWarning, - stacklevel=2, - ) - - -@CLI.command(name="update") -def update_workflow( - workflow: str = typer.Argument("all", help="name of the workflow to install."), - dest: Path = typer.Argument( - Path("./.github/workflows"), help="target directory to install the workflow to." - ), - confirm: bool = typer.Option( - False, help="Automatically confirm overwriting the existing workflow(s)" - ), -) -> None: - """Similar to install but checks for existing workflows and shows diff""" - template.update_template( - template=workflow, - dest=dest, - confirm=confirm, - pkg=PKG, - template_type=TEMPLATE_TYPE, - ) - warnings.warn( - "\033[31m`tbx workflow update` is deprecated; this will be replaced by the Nox session `workflow:generate` after 2026-04-22\033[0m", - category=FutureWarning, - stacklevel=2, - ) - - -if __name__ == "__main__": - CLI() diff --git a/test/integration/tools/workflow_integration_test.py b/test/integration/tools/workflow_integration_test.py deleted file mode 100644 index cf85d01208..0000000000 --- a/test/integration/tools/workflow_integration_test.py +++ /dev/null @@ -1,139 +0,0 @@ -from unittest.mock import patch - -import pytest -from structlog.testing import capture_logs - -from exasol.toolbox.tools.workflow import CLI - - -class TestListWorkflows: - @staticmethod - def test_with_default(cli_runner): - result = cli_runner.invoke(CLI, ["list"]) - - assert result.exit_code == 0 - assert result.output == ( - "build-and-publish\n" - "cd\n" - "check-release-tag\n" - "checks\n" - "ci\n" - "gh-pages\n" - "matrix-all\n" - "matrix-exasol\n" - "matrix-python\n" - "merge-gate\n" - "pr-merge\n" - "report\n" - "slow-checks\n" - ) - - @staticmethod - def test_with_columns(cli_runner): - result = cli_runner.invoke(CLI, ["list", "--columns"]) - - assert result.exit_code == 0 - assert result.output == ( - "build-and-publish cd check-release-tag checks ci " - "gh-pages\n" - "matrix-all matrix-exasol matrix-python merge-gate pr-merge " - "report \n" - "slow-checks \n" - ) - - -def test_show_workflow(cli_runner): - result = cli_runner.invoke(CLI, ["show", "checks"]) - - assert result.exit_code == 0 - assert "name: Checks " in result.output - - -@pytest.mark.parametrize( - "workflow", - [ - "build-and-publish", - "cd", - "check-release-tag", - "checks", - "ci", - "gh-pages", - "matrix-all", - "matrix-exasol", - "matrix-python", - "merge-gate", - "pr-merge", - "report", - "slow-checks", - ], -) -def test_diff_workflow(cli_runner, tmp_path, workflow): - with capture_logs(): - # set up with file in tmp_path so checks files are the same - cli_runner.invoke(CLI, ["install", workflow, str(tmp_path)]) - - result = cli_runner.invoke(CLI, ["diff", workflow, str(tmp_path)]) - - assert result.exit_code == 0 - # as the files are the same, we expect no difference - assert result.output.strip() == "" - - -class TestInstallWorkflow: - @staticmethod - def test_all_workflows(cli_runner, tmp_path): - result = cli_runner.invoke(CLI, ["install", "all", str(tmp_path)]) - all_files = sorted([filepath.name for filepath in tmp_path.iterdir()]) - - assert result.exit_code == 0 - assert all_files == [ - "build-and-publish.yml", - "cd.yml", - "check-release-tag.yml", - "checks.yml", - "ci.yml", - "gh-pages.yml", - "matrix-all.yml", - "matrix-exasol.yml", - "matrix-python.yml", - "merge-gate.yml", - "pr-merge.yml", - "report.yml", - "slow-checks.yml", - ] - assert ( - f"Installed build-and-publish in \n{tmp_path}/build-and-publish.yml" - in result.output - ) - - @staticmethod - def test_install_twice_no_error(cli_runner, tmp_path): - cli_runner.invoke(CLI, ["install", "checks", str(tmp_path)]) - result = cli_runner.invoke(CLI, ["install", "checks", str(tmp_path)]) - all_files = sorted([filepath.name for filepath in tmp_path.iterdir()]) - - assert result.exit_code == 0 - assert all_files == ["checks.yml"] - assert f"Installed checks in \n{tmp_path}/checks.yml" in result.output - - -class TestUpdateWorkflow: - @staticmethod - def test_when_file_does_not_previously_exist(cli_runner, tmp_path): - with capture_logs(): - result = cli_runner.invoke(CLI, ["update", "checks", str(tmp_path)]) - - assert result.exit_code == 0 - assert result.output.strip() == f"Updated checks in \n{tmp_path}/checks.yml" - - @staticmethod - def test_with_existing_file(cli_runner, tmp_path): - # set up with file in tmp_path so checks files are the same - cli_runner.invoke(CLI, ["install", "checks", str(tmp_path)]) - - with patch("typer.confirm", return_value=False): - result = cli_runner.invoke(CLI, ["update", "checks", str(tmp_path)]) - - assert result.exit_code == 0 - # files are identical, so no output expected - assert result.output.strip() == "" diff --git a/test/unit/tool_template_test.py b/test/unit/tool_template_test.py index 74734877d3..e091450449 100644 --- a/test/unit/tool_template_test.py +++ b/test/unit/tool_template_test.py @@ -3,28 +3,6 @@ from exasol.toolbox.tools import template -def test_retrieve_workflow_templates(): - subpackage = "exasol.toolbox.templates.github.workflows" - expected = { - "build-and-publish": "build-and-publish.yml", - "cd": "cd.yml", - "check-release-tag": "check-release-tag.yml", - "checks": "checks.yml", - "ci": "ci.yml", - "gh-pages": "gh-pages.yml", - "matrix-all": "matrix-all.yml", - "matrix-exasol": "matrix-exasol.yml", - "matrix-python": "matrix-python.yml", - "merge-gate": "merge-gate.yml", - "pr-merge": "pr-merge.yml", - "report": "report.yml", - "slow-checks": "slow-checks.yml", - } - actual = template._templates(subpackage) - actual = {name: path.name for name, path in actual.items()} - assert actual == expected - - def test_retrieve_issue_templates(): subpackage = "exasol.toolbox.templates.github.ISSUE_TEMPLATE" expected = { @@ -43,24 +21,6 @@ def test_retrieve_issue_templates(): @pytest.mark.parametrize( "subpackage,expected", [ - ( - "exasol.toolbox.templates.github.workflows", - { - "build-and-publish": "build-and-publish.yml", - "cd": "cd.yml", - "check-release-tag": "check-release-tag.yml", - "checks": "checks.yml", - "ci": "ci.yml", - "gh-pages": "gh-pages.yml", - "matrix-all": "matrix-all.yml", - "matrix-exasol": "matrix-exasol.yml", - "matrix-python": "matrix-python.yml", - "merge-gate": "merge-gate.yml", - "pr-merge": "pr-merge.yml", - "report": "report.yml", - "slow-checks": "slow-checks.yml", - }, - ), ( "exasol.toolbox.templates.github.ISSUE_TEMPLATE", { @@ -81,12 +41,11 @@ def test_retrieve_templates(subpackage, expected): @pytest.mark.parametrize( - "templates,pkg,template_type,expected", + "templates,pkg,expected", [ ( "all", "exasol.toolbox.templates.github.ISSUE_TEMPLATE", - "issue", [ "blank.md", "bug.md", @@ -96,30 +55,10 @@ def test_retrieve_templates(subpackage, expected): "security.md", ], ), - ( - "all", - "exasol.toolbox.templates.github.workflows", - "workflow", - [ - "build-and-publish.yml", - "cd.yml", - "check-release-tag.yml", - "checks.yml", - "ci.yml", - "gh-pages.yml", - "matrix-all.yml", - "matrix-exasol.yml", - "matrix-python.yml", - "merge-gate.yml", - "pr-merge.yml", - "report.yml", - "slow-checks.yml", - ], - ), ], ) -def test_install_templates(templates, pkg, template_type, expected, tmp_path): - template.install_template(templates, tmp_path, pkg, template_type) +def test_install_templates(templates, pkg, expected, tmp_path): + template.install_template(templates, tmp_path, pkg) actual = {file.name for file in tmp_path.iterdir()} expected = {name for name in expected} assert actual == expected