From 5c8e9ebecf877f92ee3122da49ac891e01bd03d7 Mon Sep 17 00:00:00 2001 From: Alex Gittings Date: Wed, 19 Feb 2025 17:32:51 +0000 Subject: [PATCH 01/14] Initial go at repository init command --- infrahub_sdk/ctl/example_repo/.infrahub.yml | 2 ++ infrahub_sdk/ctl/example_repo/README.md | 11 +++++++ infrahub_sdk/ctl/example_repo/copier.yml | 27 +++++++++++++++ infrahub_sdk/ctl/example_repo/poetry.lock | 33 +++++++++++++++++++ .../ctl/example_repo/pyproject.toml.jinja | 17 ++++++++++ infrahub_sdk/ctl/repository.py | 14 +++++++- pyproject.toml | 3 +- 7 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 infrahub_sdk/ctl/example_repo/.infrahub.yml create mode 100644 infrahub_sdk/ctl/example_repo/README.md create mode 100644 infrahub_sdk/ctl/example_repo/copier.yml create mode 100644 infrahub_sdk/ctl/example_repo/poetry.lock create mode 100644 infrahub_sdk/ctl/example_repo/pyproject.toml.jinja diff --git a/infrahub_sdk/ctl/example_repo/.infrahub.yml b/infrahub_sdk/ctl/example_repo/.infrahub.yml new file mode 100644 index 00000000..c097ab58 --- /dev/null +++ b/infrahub_sdk/ctl/example_repo/.infrahub.yml @@ -0,0 +1,2 @@ +schemas: + - schemas \ No newline at end of file diff --git a/infrahub_sdk/ctl/example_repo/README.md b/infrahub_sdk/ctl/example_repo/README.md new file mode 100644 index 00000000..aef517ef --- /dev/null +++ b/infrahub_sdk/ctl/example_repo/README.md @@ -0,0 +1,11 @@ +# Infrahub Repository Template + +This template allows you to initialize a repository that conforms to Opsmills best practices when organising a repository to be imported into Infrahub. + +To use this template simply install the `infrahub-sdk[ctl]` package and run the following: + +```shell +infrahubctl repository init /path/to/folder +``` + +You will be prompted with several options. \ No newline at end of file diff --git a/infrahub_sdk/ctl/example_repo/copier.yml b/infrahub_sdk/ctl/example_repo/copier.yml new file mode 100644 index 00000000..e3fa7825 --- /dev/null +++ b/infrahub_sdk/ctl/example_repo/copier.yml @@ -0,0 +1,27 @@ +project_name: + type: str + help: What is your project name? +generators: + type: bool + help: Would you like to use generators? + default: no +transforms: + type: bool + help: Would you like to use transforms? + default: no +scripts: + type: bool + help: Would you like to have local scripts? + default: no +queries: + type: bool + help: Would you like to store queries? + default: no +menus: + type: bool + help: Would you like to store menus? + default: no +tests: + type: bool + help: Would you like to have testing? + default: no \ No newline at end of file diff --git a/infrahub_sdk/ctl/example_repo/poetry.lock b/infrahub_sdk/ctl/example_repo/poetry.lock new file mode 100644 index 00000000..a5bc4be8 --- /dev/null +++ b/infrahub_sdk/ctl/example_repo/poetry.lock @@ -0,0 +1,33 @@ +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. + +[[package]] +name = "ruff" +version = "0.9.6" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.9.6-py3-none-linux_armv6l.whl", hash = "sha256:2f218f356dd2d995839f1941322ff021c72a492c470f0b26a34f844c29cdf5ba"}, + {file = "ruff-0.9.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b908ff4df65dad7b251c9968a2e4560836d8f5487c2f0cc238321ed951ea0504"}, + {file = "ruff-0.9.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b109c0ad2ececf42e75fa99dc4043ff72a357436bb171900714a9ea581ddef83"}, + {file = "ruff-0.9.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1de4367cca3dac99bcbd15c161404e849bb0bfd543664db39232648dc00112dc"}, + {file = "ruff-0.9.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac3ee4d7c2c92ddfdaedf0bf31b2b176fa7aa8950efc454628d477394d35638b"}, + {file = "ruff-0.9.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dc1edd1775270e6aa2386119aea692039781429f0be1e0949ea5884e011aa8e"}, + {file = "ruff-0.9.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4a091729086dffa4bd070aa5dab7e39cc6b9d62eb2bef8f3d91172d30d599666"}, + {file = "ruff-0.9.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1bbc6808bf7b15796cef0815e1dfb796fbd383e7dbd4334709642649625e7c5"}, + {file = "ruff-0.9.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:589d1d9f25b5754ff230dce914a174a7c951a85a4e9270613a2b74231fdac2f5"}, + {file = "ruff-0.9.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc61dd5131742e21103fbbdcad683a8813be0e3c204472d520d9a5021ca8b217"}, + {file = "ruff-0.9.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5e2d9126161d0357e5c8f30b0bd6168d2c3872372f14481136d13de9937f79b6"}, + {file = "ruff-0.9.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:68660eab1a8e65babb5229a1f97b46e3120923757a68b5413d8561f8a85d4897"}, + {file = "ruff-0.9.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c4cae6c4cc7b9b4017c71114115db0445b00a16de3bcde0946273e8392856f08"}, + {file = "ruff-0.9.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:19f505b643228b417c1111a2a536424ddde0db4ef9023b9e04a46ed8a1cb4656"}, + {file = "ruff-0.9.6-py3-none-win32.whl", hash = "sha256:194d8402bceef1b31164909540a597e0d913c0e4952015a5b40e28c146121b5d"}, + {file = "ruff-0.9.6-py3-none-win_amd64.whl", hash = "sha256:03482d5c09d90d4ee3f40d97578423698ad895c87314c4de39ed2af945633caa"}, + {file = "ruff-0.9.6-py3-none-win_arm64.whl", hash = "sha256:0e2bb706a2be7ddfea4a4af918562fdc1bcb16df255e5fa595bbd800ce322a5a"}, + {file = "ruff-0.9.6.tar.gz", hash = "sha256:81761592f72b620ec8fa1068a6fd00e98a5ebee342a3642efd84454f3031dca9"}, +] + +[metadata] +lock-version = "2.0" +python-versions = ">=3.9,<3.13" +content-hash = "d2be6a7f103f35aef839756f12e41a435786e726615f19e1a32bd7b3bd648146" diff --git a/infrahub_sdk/ctl/example_repo/pyproject.toml.jinja b/infrahub_sdk/ctl/example_repo/pyproject.toml.jinja new file mode 100644 index 00000000..0cb1c184 --- /dev/null +++ b/infrahub_sdk/ctl/example_repo/pyproject.toml.jinja @@ -0,0 +1,17 @@ +[tool.poetry] +name = "{{ project_name }}" +version = "0.1.0" +description = "Infrahub Repository" +authors = [] +readme = "README.md" + +[tool.poetry.dependencies] +python = ">=3.9,<3.13" + + +[tool.poetry.group.dev.dependencies] +ruff = "^0.9.6" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/infrahub_sdk/ctl/repository.py b/infrahub_sdk/ctl/repository.py index e57ee6bf..b592d68a 100644 --- a/infrahub_sdk/ctl/repository.py +++ b/infrahub_sdk/ctl/repository.py @@ -1,8 +1,9 @@ from __future__ import annotations from pathlib import Path - +from copier import run_copy import typer +import asyncio import yaml from pydantic import ValidationError from rich.console import Console @@ -157,3 +158,14 @@ async def list( ) console.print(table) + + +@app.command() +async def init( + dst: Path, + _: str = CONFIG_PARAM, +) -> None: + """Initialize a new Infrahub repository.""" + example_repo = Path(__file__).parent / "example_repo" + await asyncio.to_thread(run_copy, str(example_repo), str(dst)) + diff --git a/pyproject.toml b/pyproject.toml index 86314d7b..e9aa0280 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,6 +51,7 @@ pytest = { version = "*", optional = true } pyyaml = { version = "^6", optional = true } eval-type-backport = { version = "^0.2.2", python = "~3.9" } dulwich = "^0.21.4" +copier = { version = "^9.5.0", optional = true} [tool.poetry.group.dev.dependencies] pytest = "*" @@ -75,7 +76,7 @@ infrahub-testcontainers = "^1.1.0b2" astroid = "~3.1" [tool.poetry.extras] -ctl = ["Jinja2", "numpy", "pyarrow", "pyyaml", "rich", "toml", "typer"] +ctl = ["copier", "Jinja2", "numpy", "pyarrow", "pyyaml", "rich", "toml", "typer"] tests = ["Jinja2", "pytest", "pyyaml", "rich"] all = [ "Jinja2", From 212653dc837305eb72bfcbe4e31fec83d5affd8c Mon Sep 17 00:00:00 2001 From: Alex Gittings Date: Wed, 19 Feb 2025 23:29:56 +0000 Subject: [PATCH 02/14] add test --- tests/unit/ctl/test_repository_app.py | 31 +++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/unit/ctl/test_repository_app.py b/tests/unit/ctl/test_repository_app.py index a7cd70f0..83696a01 100644 --- a/tests/unit/ctl/test_repository_app.py +++ b/tests/unit/ctl/test_repository_app.py @@ -1,9 +1,12 @@ """Integration tests for infrahubctl commands.""" import sys +import tempfile +from pathlib import Path from unittest import mock import pytest +import yaml from typer.testing import CliRunner from infrahub_sdk.client import InfrahubClient @@ -288,3 +291,31 @@ def test_repo_list(self, mock_repositories_list) -> None: result = runner.invoke(app, ["repository", "list", "--branch", "main"]) assert result.exit_code == 0 assert strip_color(result.stdout) == read_fixture("output.txt", "integration/test_infrahubctl/repository_list") + + def test_repo_init(self) -> None: + with tempfile.TemporaryDirectory() as temp_dst, tempfile.NamedTemporaryFile( + mode="w", suffix=".yml", delete=False, encoding="utf-8" + ) as temp_yaml: + dst = Path(temp_dst) + yaml_path = Path(temp_yaml.name) + + answers = { + "generators": True, + "menus": True, + "project_name": "test", + "queries": True, + "scripts": True, + "tests": True, + "transforms": True, + } + + yaml.safe_dump(answers, temp_yaml) + temp_yaml.close() + runner.invoke(app, ["repository", "init", str(dst), "--data", str(yaml_path)]) + + coppied_answers = yaml.safe_load((dst / ".copier-answers.yml").read_text()) + coppied_answers.pop("_src_path") + + assert coppied_answers == answers + assert (dst / "generators").is_dir() + assert (dst / "pyproject.toml").is_file() From 705cda2fe5a909b91f661ef3000068373af79bb5 Mon Sep 17 00:00:00 2001 From: Alex Gittings Date: Wed, 19 Feb 2025 23:30:11 +0000 Subject: [PATCH 03/14] Pass data file --- infrahub_sdk/ctl/example_repo/.infrahub.yml | 3 ++- infrahub_sdk/ctl/example_repo/copier.yml | 17 +++++++++-------- .../{{_copier_conf.answers_file}}.jinja | 2 ++ infrahub_sdk/ctl/repository.py | 19 +++++++++++++++---- 4 files changed, 28 insertions(+), 13 deletions(-) create mode 100644 infrahub_sdk/ctl/example_repo/{{_copier_conf.answers_file}}.jinja diff --git a/infrahub_sdk/ctl/example_repo/.infrahub.yml b/infrahub_sdk/ctl/example_repo/.infrahub.yml index c097ab58..27d38000 100644 --- a/infrahub_sdk/ctl/example_repo/.infrahub.yml +++ b/infrahub_sdk/ctl/example_repo/.infrahub.yml @@ -1,2 +1,3 @@ +--- schemas: - - schemas \ No newline at end of file + - schemas diff --git a/infrahub_sdk/ctl/example_repo/copier.yml b/infrahub_sdk/ctl/example_repo/copier.yml index e3fa7825..ca94405f 100644 --- a/infrahub_sdk/ctl/example_repo/copier.yml +++ b/infrahub_sdk/ctl/example_repo/copier.yml @@ -1,27 +1,28 @@ +--- project_name: - type: str - help: What is your project name? + type: str + help: What is your project name? generators: type: bool help: Would you like to use generators? - default: no + default: false transforms: type: bool help: Would you like to use transforms? - default: no + default: false scripts: type: bool help: Would you like to have local scripts? - default: no + default: false queries: type: bool help: Would you like to store queries? - default: no + default: false menus: type: bool help: Would you like to store menus? - default: no + default: false tests: type: bool help: Would you like to have testing? - default: no \ No newline at end of file + default: false diff --git a/infrahub_sdk/ctl/example_repo/{{_copier_conf.answers_file}}.jinja b/infrahub_sdk/ctl/example_repo/{{_copier_conf.answers_file}}.jinja new file mode 100644 index 00000000..ea97bd4b --- /dev/null +++ b/infrahub_sdk/ctl/example_repo/{{_copier_conf.answers_file}}.jinja @@ -0,0 +1,2 @@ +# Changes here will be overwritten by Copier +{{ _copier_answers|to_nice_yaml -}} \ No newline at end of file diff --git a/infrahub_sdk/ctl/repository.py b/infrahub_sdk/ctl/repository.py index b592d68a..546c8a33 100644 --- a/infrahub_sdk/ctl/repository.py +++ b/infrahub_sdk/ctl/repository.py @@ -1,10 +1,11 @@ from __future__ import annotations +import asyncio from pathlib import Path -from copier import run_copy + import typer -import asyncio import yaml +from copier import run_copy from pydantic import ValidationError from rich.console import Console from rich.table import Table @@ -163,9 +164,19 @@ async def list( @app.command() async def init( dst: Path, + data: Path | None = None, _: str = CONFIG_PARAM, ) -> None: """Initialize a new Infrahub repository.""" example_repo = Path(__file__).parent / "example_repo" - await asyncio.to_thread(run_copy, str(example_repo), str(dst)) - + config_data = None + if data: + try: + with Path.open(data, encoding="utf-8") as f: + config_data = yaml.safe_load(f) # Load YAML contents + typer.echo(f"Loaded config: {config_data}") # Print for debugging + except Exception as e: + typer.echo(f"Error loading YAML file: {e}", err=True) + raise typer.Exit(code=1) + + await asyncio.to_thread(run_copy, str(example_repo), str(dst), data=config_data) From 25cf197e86f3aef4a2ea92787eaa4554786fe27e Mon Sep 17 00:00:00 2001 From: Alex Gittings Date: Fri, 14 Mar 2025 15:08:36 +0000 Subject: [PATCH 04/14] Add package mode --- infrahub_sdk/ctl/example_repo/copier.yml | 4 +++ infrahub_sdk/ctl/example_repo/poetry.lock | 33 ------------------- .../ctl/example_repo/pyproject.toml.jinja | 14 ++++++-- .../ctl/example_repo/schemas/example.yml | 32 ++++++++++++++++++ .../__init__.py | 0 .../example.py | 0 .../__init__.py | 0 .../integration/conftest.py | 26 +++++++++++++++ .../integration/test_infrahub.py | 24 ++++++++++++++ tests/unit/ctl/test_repository_app.py | 2 +- 10 files changed, 99 insertions(+), 36 deletions(-) delete mode 100644 infrahub_sdk/ctl/example_repo/poetry.lock create mode 100644 infrahub_sdk/ctl/example_repo/schemas/example.yml create mode 100644 infrahub_sdk/ctl/example_repo/{% if package_mode %}lib{% endif %}/__init__.py create mode 100644 infrahub_sdk/ctl/example_repo/{% if package_mode %}lib{% endif %}/example.py create mode 100644 infrahub_sdk/ctl/example_repo/{% if tests %}tests{% endif %}/__init__.py create mode 100644 infrahub_sdk/ctl/example_repo/{% if tests %}tests{% endif %}/integration/conftest.py create mode 100644 infrahub_sdk/ctl/example_repo/{% if tests %}tests{% endif %}/integration/test_infrahub.py diff --git a/infrahub_sdk/ctl/example_repo/copier.yml b/infrahub_sdk/ctl/example_repo/copier.yml index ca94405f..57b5613b 100644 --- a/infrahub_sdk/ctl/example_repo/copier.yml +++ b/infrahub_sdk/ctl/example_repo/copier.yml @@ -26,3 +26,7 @@ tests: type: bool help: Would you like to have testing? default: false +package_mode: + type: bool + help: Would you like to have a python library initialized? + default: false diff --git a/infrahub_sdk/ctl/example_repo/poetry.lock b/infrahub_sdk/ctl/example_repo/poetry.lock deleted file mode 100644 index a5bc4be8..00000000 --- a/infrahub_sdk/ctl/example_repo/poetry.lock +++ /dev/null @@ -1,33 +0,0 @@ -# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. - -[[package]] -name = "ruff" -version = "0.9.6" -description = "An extremely fast Python linter and code formatter, written in Rust." -optional = false -python-versions = ">=3.7" -files = [ - {file = "ruff-0.9.6-py3-none-linux_armv6l.whl", hash = "sha256:2f218f356dd2d995839f1941322ff021c72a492c470f0b26a34f844c29cdf5ba"}, - {file = "ruff-0.9.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b908ff4df65dad7b251c9968a2e4560836d8f5487c2f0cc238321ed951ea0504"}, - {file = "ruff-0.9.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b109c0ad2ececf42e75fa99dc4043ff72a357436bb171900714a9ea581ddef83"}, - {file = "ruff-0.9.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1de4367cca3dac99bcbd15c161404e849bb0bfd543664db39232648dc00112dc"}, - {file = "ruff-0.9.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac3ee4d7c2c92ddfdaedf0bf31b2b176fa7aa8950efc454628d477394d35638b"}, - {file = "ruff-0.9.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dc1edd1775270e6aa2386119aea692039781429f0be1e0949ea5884e011aa8e"}, - {file = "ruff-0.9.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4a091729086dffa4bd070aa5dab7e39cc6b9d62eb2bef8f3d91172d30d599666"}, - {file = "ruff-0.9.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1bbc6808bf7b15796cef0815e1dfb796fbd383e7dbd4334709642649625e7c5"}, - {file = "ruff-0.9.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:589d1d9f25b5754ff230dce914a174a7c951a85a4e9270613a2b74231fdac2f5"}, - {file = "ruff-0.9.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc61dd5131742e21103fbbdcad683a8813be0e3c204472d520d9a5021ca8b217"}, - {file = "ruff-0.9.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5e2d9126161d0357e5c8f30b0bd6168d2c3872372f14481136d13de9937f79b6"}, - {file = "ruff-0.9.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:68660eab1a8e65babb5229a1f97b46e3120923757a68b5413d8561f8a85d4897"}, - {file = "ruff-0.9.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c4cae6c4cc7b9b4017c71114115db0445b00a16de3bcde0946273e8392856f08"}, - {file = "ruff-0.9.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:19f505b643228b417c1111a2a536424ddde0db4ef9023b9e04a46ed8a1cb4656"}, - {file = "ruff-0.9.6-py3-none-win32.whl", hash = "sha256:194d8402bceef1b31164909540a597e0d913c0e4952015a5b40e28c146121b5d"}, - {file = "ruff-0.9.6-py3-none-win_amd64.whl", hash = "sha256:03482d5c09d90d4ee3f40d97578423698ad895c87314c4de39ed2af945633caa"}, - {file = "ruff-0.9.6-py3-none-win_arm64.whl", hash = "sha256:0e2bb706a2be7ddfea4a4af918562fdc1bcb16df255e5fa595bbd800ce322a5a"}, - {file = "ruff-0.9.6.tar.gz", hash = "sha256:81761592f72b620ec8fa1068a6fd00e98a5ebee342a3642efd84454f3031dca9"}, -] - -[metadata] -lock-version = "2.0" -python-versions = ">=3.9,<3.13" -content-hash = "d2be6a7f103f35aef839756f12e41a435786e726615f19e1a32bd7b3bd648146" diff --git a/infrahub_sdk/ctl/example_repo/pyproject.toml.jinja b/infrahub_sdk/ctl/example_repo/pyproject.toml.jinja index 0cb1c184..0dc594e2 100644 --- a/infrahub_sdk/ctl/example_repo/pyproject.toml.jinja +++ b/infrahub_sdk/ctl/example_repo/pyproject.toml.jinja @@ -4,13 +4,23 @@ version = "0.1.0" description = "Infrahub Repository" authors = [] readme = "README.md" +{% if package_mode %} +packages = [ + { include = "lib" } +] +{% else %} +package-mode = false +{% endif %} [tool.poetry.dependencies] python = ">=3.9,<3.13" - +infrahub-sdk = {extras = ["ctl"], version = "^1.7.2"} [tool.poetry.group.dev.dependencies] -ruff = "^0.9.6" +ruff = "^0.9.10" +pytest = "^8.3.5" +infrahub-testcontainers = "^1.1.8" +pytest-asyncio = "^0.25.3" [build-system] requires = ["poetry-core"] diff --git a/infrahub_sdk/ctl/example_repo/schemas/example.yml b/infrahub_sdk/ctl/example_repo/schemas/example.yml new file mode 100644 index 00000000..17e54b4b --- /dev/null +++ b/infrahub_sdk/ctl/example_repo/schemas/example.yml @@ -0,0 +1,32 @@ +# yaml-language-server: $schema=https://schema.infrahub.app/infrahub/schema/latest.json +--- +version: "1.0" +nodes: + - name: Device + namespace: Network + human_friendly_id: ['hostname__value'] + attributes: + - name: hostname + kind: Text + unique: true + - name: model + kind: Text + relationships: + - name: interfaces + cardinality: many + peer: NetworkInterface + kind: Component + - name: Interface + namespace: Network + attributes: + - name: name + kind: Text + - name: description + kind: Text + optional: true + relationships: + - name: device + cardinality: one + peer: NetworkDevice + optional: false + kind: Parent \ No newline at end of file diff --git a/infrahub_sdk/ctl/example_repo/{% if package_mode %}lib{% endif %}/__init__.py b/infrahub_sdk/ctl/example_repo/{% if package_mode %}lib{% endif %}/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/infrahub_sdk/ctl/example_repo/{% if package_mode %}lib{% endif %}/example.py b/infrahub_sdk/ctl/example_repo/{% if package_mode %}lib{% endif %}/example.py new file mode 100644 index 00000000..e69de29b diff --git a/infrahub_sdk/ctl/example_repo/{% if tests %}tests{% endif %}/__init__.py b/infrahub_sdk/ctl/example_repo/{% if tests %}tests{% endif %}/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/infrahub_sdk/ctl/example_repo/{% if tests %}tests{% endif %}/integration/conftest.py b/infrahub_sdk/ctl/example_repo/{% if tests %}tests{% endif %}/integration/conftest.py new file mode 100644 index 00000000..3ddb4ced --- /dev/null +++ b/infrahub_sdk/ctl/example_repo/{% if tests %}tests{% endif %}/integration/conftest.py @@ -0,0 +1,26 @@ +import pytest +from pathlib import Path + +from typing import Any +from infrahub_sdk.yaml import SchemaFile + +CURRENT_DIRECTORY = Path(__file__).parent.resolve() + + +@pytest.fixture +def root_directory() -> Path: + """ + Return the path of the root directory of the repository. + """ + return CURRENT_DIRECTORY.parent.parent + + +@pytest.fixture +def schemas_directory(root_directory: Path) -> Path: + return root_directory / "schema" + + +@pytest.fixture +def schemas(schemas_directory: Path) -> list[dict[str, Any]]: + schema_files = SchemaFile.load_from_disk(paths=[schemas_directory]) + return [item.content for item in schema_files if item.content] \ No newline at end of file diff --git a/infrahub_sdk/ctl/example_repo/{% if tests %}tests{% endif %}/integration/test_infrahub.py b/infrahub_sdk/ctl/example_repo/{% if tests %}tests{% endif %}/integration/test_infrahub.py new file mode 100644 index 00000000..6a1de498 --- /dev/null +++ b/infrahub_sdk/ctl/example_repo/{% if tests %}tests{% endif %}/integration/test_infrahub.py @@ -0,0 +1,24 @@ +import pytest +from pathlib import Path +from infrahub_sdk import InfrahubClient +from infrahub_sdk.testing.docker import TestInfrahubDockerClient +from infrahub_sdk.protocols import CoreGenericRepository +from infrahub_sdk.testing.repository import GitRepo + + +class TestInfrahub(TestInfrahubDockerClient): + @pytest.fixture(scope="class") + def infrahub_version(self) -> str: + """Required to define the version of infrahub to use.""" + return "1.1.8" + + @pytest.mark.asyncio + async def test_load_schema( + self, default_branch: str, client: InfrahubClient, schemas + ): + await client.schema.wait_until_converged(branch=default_branch) + + resp = await client.schema.load( + schemas=schemas, branch=default_branch, wait_until_converged=True + ) + assert resp.errors == {} diff --git a/tests/unit/ctl/test_repository_app.py b/tests/unit/ctl/test_repository_app.py index 83696a01..c2a11ba6 100644 --- a/tests/unit/ctl/test_repository_app.py +++ b/tests/unit/ctl/test_repository_app.py @@ -307,12 +307,12 @@ def test_repo_init(self) -> None: "scripts": True, "tests": True, "transforms": True, + "package_mode": False, } yaml.safe_dump(answers, temp_yaml) temp_yaml.close() runner.invoke(app, ["repository", "init", str(dst), "--data", str(yaml_path)]) - coppied_answers = yaml.safe_load((dst / ".copier-answers.yml").read_text()) coppied_answers.pop("_src_path") From e5bfa55dc5c8daaceac39f58eaaf421bddf1a4f4 Mon Sep 17 00:00:00 2001 From: Alex Gittings Date: Mon, 17 Mar 2025 11:22:17 +0000 Subject: [PATCH 05/14] Ensure empty folders are present --- tests/unit/ctl/test_repository_app.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/unit/ctl/test_repository_app.py b/tests/unit/ctl/test_repository_app.py index c2a11ba6..44bb4ce8 100644 --- a/tests/unit/ctl/test_repository_app.py +++ b/tests/unit/ctl/test_repository_app.py @@ -318,4 +318,6 @@ def test_repo_init(self) -> None: assert coppied_answers == answers assert (dst / "generators").is_dir() + assert (dst / "queries").is_dir() + assert (dst / "scripts").is_dir() assert (dst / "pyproject.toml").is_file() From b826d89340cf8d356a7aa56d66e91f474eac3cf2 Mon Sep 17 00:00:00 2001 From: Alex Gittings Date: Tue, 22 Apr 2025 10:17:53 +0100 Subject: [PATCH 06/14] Update examples --- infrahub_sdk/ctl/example_repo/README.md | 35 ++++++-- .../ctl/example_repo/pyproject.toml.jinja | 13 +-- infrahub_sdk/ctl/example_repo/tasks.py | 82 +++++++++++++++++++ .../example.py | 9 ++ .../example_script.py | 14 ++++ .../integration/conftest.py | 4 +- .../integration/test_infrahub.py | 38 ++++++--- 7 files changed, 170 insertions(+), 25 deletions(-) create mode 100644 infrahub_sdk/ctl/example_repo/tasks.py create mode 100644 infrahub_sdk/ctl/example_repo/{% if scripts %}scripts{% endif %}/example_script.py diff --git a/infrahub_sdk/ctl/example_repo/README.md b/infrahub_sdk/ctl/example_repo/README.md index aef517ef..85daf5e7 100644 --- a/infrahub_sdk/ctl/example_repo/README.md +++ b/infrahub_sdk/ctl/example_repo/README.md @@ -1,11 +1,34 @@ -# Infrahub Repository Template +# Infrahub Repository -This template allows you to initialize a repository that conforms to Opsmills best practices when organising a repository to be imported into Infrahub. +Welcome! This repository was initialized via the `infrahubctl repo init` command. That bootstraps a repository for use with some example data. -To use this template simply install the `infrahub-sdk[ctl]` package and run the following: +## Installation +Running `poetry install` will install all the main dependencies you need to interact with this repository. -```shell -infrahubctl repository init /path/to/folder +## Starting Infrahub + +Included in the repository are a set of helper commands to get Infrahub up and running using `invoke`. + +```bash +Available tasks: + + destroy Stop and remove containers, networks, and volumes. + download-compose-file Download docker-compose.yml from InfraHub if missing or override is True. + load-schema Load schemas into InfraHub using infrahubctl. + restart Restart all services or a specific one using docker-compose. + start Start the services using docker-compose in detached mode. + stop Stop containers and remove networks. + test Run tests using pytest. +``` + +To start infrahub simply use `invoke start` + +## Tests +By default there are some integration tests that will spin up Infrahub and its dependencies in docker and load the repository and schema. This can be run using the following: + +```bash +poetry install --with=dev +pytest tests/integration ``` -You will be prompted with several options. \ No newline at end of file +To change the version of infrahub being used you can use an environment variable: `export INFRAHUB_TESTING_IMAGE_VERSION=1.2.5`. \ No newline at end of file diff --git a/infrahub_sdk/ctl/example_repo/pyproject.toml.jinja b/infrahub_sdk/ctl/example_repo/pyproject.toml.jinja index 0dc594e2..e1c758dc 100644 --- a/infrahub_sdk/ctl/example_repo/pyproject.toml.jinja +++ b/infrahub_sdk/ctl/example_repo/pyproject.toml.jinja @@ -13,14 +13,15 @@ package-mode = false {% endif %} [tool.poetry.dependencies] -python = ">=3.9,<3.13" -infrahub-sdk = {extras = ["ctl"], version = "^1.7.2"} +python = ">=3.9,<=3.13.1" +infrahub-sdk = { extras = ["all"], version = "*" } +invoke = "*" [tool.poetry.group.dev.dependencies] -ruff = "^0.9.10" -pytest = "^8.3.5" -infrahub-testcontainers = "^1.1.8" -pytest-asyncio = "^0.25.3" +ruff = "*" +pytest = "*" +infrahub-testcontainers = "*" +pytest-asyncio = "*" [build-system] requires = ["poetry-core"] diff --git a/infrahub_sdk/ctl/example_repo/tasks.py b/infrahub_sdk/ctl/example_repo/tasks.py new file mode 100644 index 00000000..fd186c3b --- /dev/null +++ b/infrahub_sdk/ctl/example_repo/tasks.py @@ -0,0 +1,82 @@ +from invoke import Context, task +from pathlib import Path +import httpx +import os + +# If no version is indicated, we will take the latest +VERSION = os.getenv("INFRAHUB_IMAGE_VER", None) + + +@task +def start(context: Context) -> None: + """ + Start the services using docker-compose in detached mode. + """ + compose_file = download_compose_file(context, override=False) + context.run("docker compose up -d") + + +@task +def destroy(context: Context) -> None: + """ + Stop and remove containers, networks, and volumes. + """ + compose_file = download_compose_file(context, override=False) + context.run("docker compose down -v") + + +@task +def stop(context: Context) -> None: + """ + Stop containers and remove networks. + """ + compose_file = download_compose_file(context, override=False) + context.run("docker compose down") + + +@task(help={"component": "Optional name of a specific service to restart."}) +def restart(context: Context, component: str = "") -> None: + """ + Restart all services or a specific one using docker-compose. + """ + download_compose_file(context, override=False) + if component: + context.run(f"docker compose restart {component}") + return + + context.run("docker compose restart") + + +@task +def load_schema(ctx: Context): + """ + Load schemas into InfraHub using infrahubctl. + """ + ctx.run("infrahubctl schema load schemas") + + +@task +def test(ctx: Context): + """ + Run tests using pytest. + """ + ctx.run("pytest tests") + + +@task(help={"override": "Redownload the compose file even if it already exists."}) +def download_compose_file(context: Context, override: bool = False) -> Path: + """ + Download docker-compose.yml from InfraHub if missing or override is True. + """ + compose_file = Path("./docker-compose.yml") + + if compose_file.exists() and not override: + return compose_file + + response = httpx.get("https://infrahub.opsmill.io") + response.raise_for_status() + + with compose_file.open("w") as f: + f.write(response.content.decode()) + + return compose_file diff --git a/infrahub_sdk/ctl/example_repo/{% if package_mode %}lib{% endif %}/example.py b/infrahub_sdk/ctl/example_repo/{% if package_mode %}lib{% endif %}/example.py index e69de29b..ea6f3c68 100644 --- a/infrahub_sdk/ctl/example_repo/{% if package_mode %}lib{% endif %}/example.py +++ b/infrahub_sdk/ctl/example_repo/{% if package_mode %}lib{% endif %}/example.py @@ -0,0 +1,9 @@ +from infrahub_sdk import InfrahubClient +from infrahub_sdk.node import InfrahubNode +from typing import List +import logging + + +def print_nodes(client: InfrahubClient, log: logging.Logger, nodes: List[InfrahubNode]): + for node in nodes.keys(): + log.info(f"{node} present.") diff --git a/infrahub_sdk/ctl/example_repo/{% if scripts %}scripts{% endif %}/example_script.py b/infrahub_sdk/ctl/example_repo/{% if scripts %}scripts{% endif %}/example_script.py new file mode 100644 index 00000000..e2b8e629 --- /dev/null +++ b/infrahub_sdk/ctl/example_repo/{% if scripts %}scripts{% endif %}/example_script.py @@ -0,0 +1,14 @@ +import logging + +from infrahub_sdk import InfrahubClient +from lib.example import print_nodes + + +async def run( + client: InfrahubClient, + log: logging.Logger, + branch: str, +): + log.info("Running example script...") + nodes = await client.schema.all() + print_nodes(client, log, nodes) diff --git a/infrahub_sdk/ctl/example_repo/{% if tests %}tests{% endif %}/integration/conftest.py b/infrahub_sdk/ctl/example_repo/{% if tests %}tests{% endif %}/integration/conftest.py index 3ddb4ced..515662e9 100644 --- a/infrahub_sdk/ctl/example_repo/{% if tests %}tests{% endif %}/integration/conftest.py +++ b/infrahub_sdk/ctl/example_repo/{% if tests %}tests{% endif %}/integration/conftest.py @@ -17,10 +17,10 @@ def root_directory() -> Path: @pytest.fixture def schemas_directory(root_directory: Path) -> Path: - return root_directory / "schema" + return root_directory / "schemas" @pytest.fixture def schemas(schemas_directory: Path) -> list[dict[str, Any]]: schema_files = SchemaFile.load_from_disk(paths=[schemas_directory]) - return [item.content for item in schema_files if item.content] \ No newline at end of file + return [item.content for item in schema_files if item.content] diff --git a/infrahub_sdk/ctl/example_repo/{% if tests %}tests{% endif %}/integration/test_infrahub.py b/infrahub_sdk/ctl/example_repo/{% if tests %}tests{% endif %}/integration/test_infrahub.py index 6a1de498..6fd1a558 100644 --- a/infrahub_sdk/ctl/example_repo/{% if tests %}tests{% endif %}/integration/test_infrahub.py +++ b/infrahub_sdk/ctl/example_repo/{% if tests %}tests{% endif %}/integration/test_infrahub.py @@ -7,18 +7,34 @@ class TestInfrahub(TestInfrahubDockerClient): - @pytest.fixture(scope="class") - def infrahub_version(self) -> str: - """Required to define the version of infrahub to use.""" - return "1.1.8" - @pytest.mark.asyncio - async def test_load_schema( - self, default_branch: str, client: InfrahubClient, schemas - ): + async def test_load_schema(self, default_branch: str, client: InfrahubClient, schemas): await client.schema.wait_until_converged(branch=default_branch) - resp = await client.schema.load( - schemas=schemas, branch=default_branch, wait_until_converged=True - ) + resp = await client.schema.load(schemas=schemas, branch=default_branch, wait_until_converged=True) assert resp.errors == {} + + @pytest.mark.asyncio + async def test_load_repository( + self, + client: InfrahubClient, + remote_repos_dir: Path, + root_directory: Path, + ) -> None: + """Add the local directory as a repository in Infrahub and wait for the import to be complete""" + + repo = GitRepo( + name="local-repository", + src_directory=root_directory, + dst_directory=remote_repos_dir, + ) + await repo.add_to_infrahub(client=client) + in_sync = await repo.wait_for_sync_to_complete(client=client) + assert in_sync + + repos = await client.all(kind=CoreGenericRepository) + + ### A breakpoint can be added to pause the tests from running and keep the test containers active + # breakpoint() + + assert repos From 5972713628aa4239b74d018f54b6238868931dd8 Mon Sep 17 00:00:00 2001 From: Alex Gittings Date: Tue, 22 Apr 2025 10:32:29 +0100 Subject: [PATCH 07/14] format --- infrahub_sdk/ctl/example_repo/tasks.py | 13 +++++++------ .../{% if package_mode %}lib{% endif %}/example.py | 7 +++---- .../example_script.py | 7 ++++--- .../integration/conftest.py | 5 +++-- .../integration/test_infrahub.py | 10 ++++++---- pyproject.toml | 5 +++++ tests/unit/ctl/test_repository_app.py | 7 ++++--- 7 files changed, 32 insertions(+), 22 deletions(-) diff --git a/infrahub_sdk/ctl/example_repo/tasks.py b/infrahub_sdk/ctl/example_repo/tasks.py index fd186c3b..4b2e3b1d 100644 --- a/infrahub_sdk/ctl/example_repo/tasks.py +++ b/infrahub_sdk/ctl/example_repo/tasks.py @@ -1,7 +1,8 @@ -from invoke import Context, task +import os from pathlib import Path + import httpx -import os +from invoke import Context, task # If no version is indicated, we will take the latest VERSION = os.getenv("INFRAHUB_IMAGE_VER", None) @@ -12,7 +13,7 @@ def start(context: Context) -> None: """ Start the services using docker-compose in detached mode. """ - compose_file = download_compose_file(context, override=False) + download_compose_file(context, override=False) context.run("docker compose up -d") @@ -21,7 +22,7 @@ def destroy(context: Context) -> None: """ Stop and remove containers, networks, and volumes. """ - compose_file = download_compose_file(context, override=False) + download_compose_file(context, override=False) context.run("docker compose down -v") @@ -30,7 +31,7 @@ def stop(context: Context) -> None: """ Stop containers and remove networks. """ - compose_file = download_compose_file(context, override=False) + download_compose_file(context, override=False) context.run("docker compose down") @@ -64,7 +65,7 @@ def test(ctx: Context): @task(help={"override": "Redownload the compose file even if it already exists."}) -def download_compose_file(context: Context, override: bool = False) -> Path: +def download_compose_file(context: Context, override: bool = False) -> Path: # noqa ARG001 """ Download docker-compose.yml from InfraHub if missing or override is True. """ diff --git a/infrahub_sdk/ctl/example_repo/{% if package_mode %}lib{% endif %}/example.py b/infrahub_sdk/ctl/example_repo/{% if package_mode %}lib{% endif %}/example.py index ea6f3c68..cfb4b3ac 100644 --- a/infrahub_sdk/ctl/example_repo/{% if package_mode %}lib{% endif %}/example.py +++ b/infrahub_sdk/ctl/example_repo/{% if package_mode %}lib{% endif %}/example.py @@ -1,9 +1,8 @@ -from infrahub_sdk import InfrahubClient -from infrahub_sdk.node import InfrahubNode -from typing import List import logging +from infrahub_sdk.node import InfrahubNode + -def print_nodes(client: InfrahubClient, log: logging.Logger, nodes: List[InfrahubNode]): +def print_nodes(log: logging.Logger, nodes: list[InfrahubNode]): for node in nodes.keys(): log.info(f"{node} present.") diff --git a/infrahub_sdk/ctl/example_repo/{% if scripts %}scripts{% endif %}/example_script.py b/infrahub_sdk/ctl/example_repo/{% if scripts %}scripts{% endif %}/example_script.py index e2b8e629..84bf6907 100644 --- a/infrahub_sdk/ctl/example_repo/{% if scripts %}scripts{% endif %}/example_script.py +++ b/infrahub_sdk/ctl/example_repo/{% if scripts %}scripts{% endif %}/example_script.py @@ -1,14 +1,15 @@ import logging -from infrahub_sdk import InfrahubClient from lib.example import print_nodes +from infrahub_sdk import InfrahubClient + async def run( client: InfrahubClient, log: logging.Logger, branch: str, ): - log.info("Running example script...") + log.info(f"Running example script on {branch}...") nodes = await client.schema.all() - print_nodes(client, log, nodes) + print_nodes(log, nodes) diff --git a/infrahub_sdk/ctl/example_repo/{% if tests %}tests{% endif %}/integration/conftest.py b/infrahub_sdk/ctl/example_repo/{% if tests %}tests{% endif %}/integration/conftest.py index 515662e9..3bd0a177 100644 --- a/infrahub_sdk/ctl/example_repo/{% if tests %}tests{% endif %}/integration/conftest.py +++ b/infrahub_sdk/ctl/example_repo/{% if tests %}tests{% endif %}/integration/conftest.py @@ -1,7 +1,8 @@ -import pytest from pathlib import Path - from typing import Any + +import pytest + from infrahub_sdk.yaml import SchemaFile CURRENT_DIRECTORY = Path(__file__).parent.resolve() diff --git a/infrahub_sdk/ctl/example_repo/{% if tests %}tests{% endif %}/integration/test_infrahub.py b/infrahub_sdk/ctl/example_repo/{% if tests %}tests{% endif %}/integration/test_infrahub.py index 6fd1a558..dd3f4626 100644 --- a/infrahub_sdk/ctl/example_repo/{% if tests %}tests{% endif %}/integration/test_infrahub.py +++ b/infrahub_sdk/ctl/example_repo/{% if tests %}tests{% endif %}/integration/test_infrahub.py @@ -1,14 +1,16 @@ -import pytest from pathlib import Path + +import pytest + from infrahub_sdk import InfrahubClient -from infrahub_sdk.testing.docker import TestInfrahubDockerClient from infrahub_sdk.protocols import CoreGenericRepository +from infrahub_sdk.testing.docker import TestInfrahubDockerClient from infrahub_sdk.testing.repository import GitRepo class TestInfrahub(TestInfrahubDockerClient): @pytest.mark.asyncio - async def test_load_schema(self, default_branch: str, client: InfrahubClient, schemas): + async def test_load_schema(self, default_branch: str, client: InfrahubClient, schemas: list[dict]): await client.schema.wait_until_converged(branch=default_branch) resp = await client.schema.load(schemas=schemas, branch=default_branch, wait_until_converged=True) @@ -34,7 +36,7 @@ async def test_load_repository( repos = await client.all(kind=CoreGenericRepository) - ### A breakpoint can be added to pause the tests from running and keep the test containers active + # A breakpoint can be added to pause the tests from running and keep the test containers active # breakpoint() assert repos diff --git a/pyproject.toml b/pyproject.toml index e9aa0280..fec294a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -283,6 +283,11 @@ max-complexity = 17 "ANN204", # Missing return type annotation for special method ] +"infrahub_sdk/ctl/example_repo/**/*.py" = [ + "S101", # Use of assert detected + "N999", # Invalid module name +] + "tests/unit/sdk/test_client.py" = [ "W293", # Blank line contains whitespace (used within output check) ] diff --git a/tests/unit/ctl/test_repository_app.py b/tests/unit/ctl/test_repository_app.py index 44bb4ce8..ef94f5f7 100644 --- a/tests/unit/ctl/test_repository_app.py +++ b/tests/unit/ctl/test_repository_app.py @@ -293,9 +293,10 @@ def test_repo_list(self, mock_repositories_list) -> None: assert strip_color(result.stdout) == read_fixture("output.txt", "integration/test_infrahubctl/repository_list") def test_repo_init(self) -> None: - with tempfile.TemporaryDirectory() as temp_dst, tempfile.NamedTemporaryFile( - mode="w", suffix=".yml", delete=False, encoding="utf-8" - ) as temp_yaml: + with ( + tempfile.TemporaryDirectory() as temp_dst, + tempfile.NamedTemporaryFile(mode="w", suffix=".yml", delete=False, encoding="utf-8") as temp_yaml, + ): dst = Path(temp_dst) yaml_path = Path(temp_yaml.name) From 88c19f4e10fc311ebf634502c5357aa1af680b63 Mon Sep 17 00:00:00 2001 From: Alex Gittings Date: Tue, 22 Apr 2025 10:36:37 +0100 Subject: [PATCH 08/14] Add docs for infrahub repo init --- .../infrahubctl/infrahubctl-repository.mdx | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/docs/infrahubctl/infrahubctl-repository.mdx b/docs/docs/infrahubctl/infrahubctl-repository.mdx index 94375125..2addb7a9 100644 --- a/docs/docs/infrahubctl/infrahubctl-repository.mdx +++ b/docs/docs/infrahubctl/infrahubctl-repository.mdx @@ -19,6 +19,7 @@ $ infrahubctl repository [OPTIONS] COMMAND [ARGS]... **Commands**: * `add`: Add a new repository. +* `init`: Initialize a new Infrahub repository. * `list` ## `infrahubctl repository add` @@ -47,6 +48,26 @@ $ infrahubctl repository add [OPTIONS] NAME LOCATION * `--config-file TEXT`: [env var: INFRAHUBCTL_CONFIG; default: infrahubctl.toml] * `--help`: Show this message and exit. +## `infrahubctl repository init` + +Initialize a new Infrahub repository. + +**Usage**: + +```console +$ infrahubctl repository init [OPTIONS] DST +``` + +**Arguments**: + +* `DST`: [required] + +**Options**: + +* `--data PATH` +* `--config-file TEXT`: [env var: INFRAHUBCTL_CONFIG; default: infrahubctl.toml] +* `--help`: Show this message and exit. + ## `infrahubctl repository list` **Usage**: From 935c0205bdf5c4ce0388cd3302fae7eb166b7406 Mon Sep 17 00:00:00 2001 From: Alex Gittings Date: Tue, 22 Apr 2025 10:39:56 +0100 Subject: [PATCH 09/14] Update lock file --- poetry.lock | 152 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 136 insertions(+), 16 deletions(-) diff --git a/poetry.lock b/poetry.lock index ac79c770..464ea1ad 100644 --- a/poetry.lock +++ b/poetry.lock @@ -220,7 +220,36 @@ files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -markers = {main = "(extra == \"ctl\" or extra == \"all\") and platform_system == \"Windows\" or sys_platform == \"win32\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\""} +markers = {main = "(extra == \"ctl\" or extra == \"all\") and platform_system == \"Windows\" or sys_platform == \"win32\" or extra == \"ctl\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\""} + +[[package]] +name = "copier" +version = "9.6.0" +description = "A library for rendering project templates." +optional = true +python-versions = ">=3.9" +groups = ["main"] +markers = "extra == \"ctl\"" +files = [ + {file = "copier-9.6.0-py3-none-any.whl", hash = "sha256:aaf992600a373fa2dda9f61725916a230c210ede14a968c60dab855d524c6cf3"}, + {file = "copier-9.6.0.tar.gz", hash = "sha256:e05a18b387b96e8d1fbd5271d37f59c9a02be5ad717f7878d0505562fd62b786"}, +] + +[package.dependencies] +colorama = ">=0.4.6" +dunamai = ">=1.7.0" +eval-type-backport = {version = ">=0.1.3,<0.3.0", markers = "python_version < \"3.10\""} +funcy = ">=1.17" +jinja2 = ">=3.1.5" +jinja2-ansible-filters = ">=1.3.1" +packaging = ">=23.0" +pathspec = ">=0.9.0" +platformdirs = ">=4.3.6" +plumbum = ">=1.6.9" +pydantic = ">=2.4.2" +pygments = ">=2.7.1" +pyyaml = ">=5.3.1" +questionary = ">=1.8.1" [[package]] name = "coverage" @@ -446,6 +475,22 @@ https = ["urllib3 (>=1.24.1)"] paramiko = ["paramiko"] pgp = ["gpg"] +[[package]] +name = "dunamai" +version = "1.23.1" +description = "Dynamic version generation" +optional = true +python-versions = ">=3.5" +groups = ["main"] +markers = "extra == \"ctl\"" +files = [ + {file = "dunamai-1.23.1-py3-none-any.whl", hash = "sha256:2611b0b9105a5797149ef82f4968a01dd912bdac857d49fc06856a4cfa58cf78"}, + {file = "dunamai-1.23.1.tar.gz", hash = "sha256:0b5712fc63bfb235263d912bfc5eb84590ba2201bb737268d25a5dbad7085489"}, +] + +[package.dependencies] +packaging = ">=20.9" + [[package]] name = "eval-type-backport" version = "0.2.2" @@ -525,6 +570,19 @@ docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2. testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.1.1)", "pytest (>=8.3.2)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.3)"] typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] +[[package]] +name = "funcy" +version = "2.0" +description = "A fancy and practical functional tools" +optional = true +python-versions = "*" +groups = ["main"] +markers = "extra == \"ctl\"" +files = [ + {file = "funcy-2.0-py2.py3-none-any.whl", hash = "sha256:53df23c8bb1651b12f095df764bfb057935d49537a56de211b098f4c79614bb0"}, + {file = "funcy-2.0.tar.gz", hash = "sha256:3963315d59d41c6f30c04bc910e10ab50a3ac4a225868bfa96feed133df075cb"}, +] + [[package]] name = "graphql-core" version = "3.2.4" @@ -796,6 +854,26 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "jinja2-ansible-filters" +version = "1.3.2" +description = "A port of Ansible's jinja2 filters without requiring ansible core." +optional = true +python-versions = "*" +groups = ["main"] +markers = "extra == \"ctl\"" +files = [ + {file = "jinja2-ansible-filters-1.3.2.tar.gz", hash = "sha256:07c10cf44d7073f4f01102ca12d9a2dc31b41d47e4c61ed92ef6a6d2669b356b"}, + {file = "jinja2_ansible_filters-1.3.2-py3-none-any.whl", hash = "sha256:e1082f5564917649c76fed239117820610516ec10f87735d0338688800a55b34"}, +] + +[package.dependencies] +Jinja2 = "*" +PyYAML = "*" + +[package.extras] +test = ["pytest", "pytest-cov"] + [[package]] name = "markdown-it-py" version = "3.0.0" @@ -1089,11 +1167,12 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" -groups = ["dev"] +groups = ["main", "dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] +markers = {main = "extra == \"ctl\""} [[package]] name = "pexpect" @@ -1113,20 +1192,21 @@ ptyprocess = ">=0.5" [[package]] name = "platformdirs" -version = "4.3.3" +version = "4.3.7" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false -python-versions = ">=3.8" -groups = ["dev"] +python-versions = ">=3.9" +groups = ["main", "dev"] files = [ - {file = "platformdirs-4.3.3-py3-none-any.whl", hash = "sha256:50a5450e2e84f44539718293cbb1da0a0885c9d14adf21b77bae4e66fc99d9b5"}, - {file = "platformdirs-4.3.3.tar.gz", hash = "sha256:d4e0b7d8ec176b341fb03cb11ca12d0276faa8c485f9cd218f613840463fc2c0"}, + {file = "platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94"}, + {file = "platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351"}, ] +markers = {main = "extra == \"ctl\""} [package.extras] -docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] -type = ["mypy (>=1.11.2)"] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.14.1)"] [[package]] name = "pluggy" @@ -1144,6 +1224,28 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "plumbum" +version = "1.9.0" +description = "Plumbum: shell combinators library" +optional = true +python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"ctl\"" +files = [ + {file = "plumbum-1.9.0-py3-none-any.whl", hash = "sha256:9fd0d3b0e8d86e4b581af36edf3f3bbe9d1ae15b45b8caab28de1bcb27aaa7f5"}, + {file = "plumbum-1.9.0.tar.gz", hash = "sha256:e640062b72642c3873bd5bdc3effed75ba4d3c70ef6b6a7b907357a84d909219"}, +] + +[package.dependencies] +pywin32 = {version = "*", markers = "platform_system == \"Windows\" and platform_python_implementation != \"PyPy\""} + +[package.extras] +dev = ["coverage[toml]", "paramiko", "psutil", "pytest (>=6.0)", "pytest-cov", "pytest-mock", "pytest-timeout"] +docs = ["sphinx (>=4.0.0)", "sphinx-rtd-theme (>=1.0.0)"] +ssh = ["paramiko"] +test = ["coverage[toml]", "paramiko", "psutil", "pytest (>=6.0)", "pytest-cov", "pytest-mock", "pytest-timeout"] + [[package]] name = "pprintpp" version = "0.4.0" @@ -1181,11 +1283,12 @@ version = "3.0.47" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.7.0" -groups = ["dev"] +groups = ["main", "dev"] files = [ {file = "prompt_toolkit-3.0.47-py3-none-any.whl", hash = "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10"}, {file = "prompt_toolkit-3.0.47.tar.gz", hash = "sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360"}, ] +markers = {main = "extra == \"ctl\""} [package.dependencies] wcwidth = "*" @@ -1608,8 +1711,7 @@ version = "308" description = "Python for Window Extensions" optional = false python-versions = "*" -groups = ["dev"] -markers = "python_version >= \"3.10\" and sys_platform == \"win32\"" +groups = ["main", "dev"] files = [ {file = "pywin32-308-cp310-cp310-win32.whl", hash = "sha256:796ff4426437896550d2981b9c2ac0ffd75238ad9ea2d3bfa67a1abd546d262e"}, {file = "pywin32-308-cp310-cp310-win_amd64.whl", hash = "sha256:4fc888c59b3c0bef905ce7eb7e2106a07712015ea1c8234b703a088d46110e8e"}, @@ -1630,6 +1732,7 @@ files = [ {file = "pywin32-308-cp39-cp39-win32.whl", hash = "sha256:7873ca4dc60ab3287919881a7d4f88baee4a6e639aa6962de25a98ba6b193341"}, {file = "pywin32-308-cp39-cp39-win_amd64.whl", hash = "sha256:71b3322d949b4cc20776436a9c9ba0eeedcbc9c650daa536df63f0ff111bb920"}, ] +markers = {main = "platform_system == \"Windows\" and platform_python_implementation != \"PyPy\" and extra == \"ctl\"", dev = "python_version >= \"3.10\" and sys_platform == \"win32\""} [[package]] name = "pyyaml" @@ -1695,6 +1798,22 @@ files = [ ] markers = {main = "extra == \"ctl\" or extra == \"tests\" or extra == \"all\""} +[[package]] +name = "questionary" +version = "2.1.0" +description = "Python library to build pretty command line user prompts ⭐️" +optional = true +python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"ctl\"" +files = [ + {file = "questionary-2.1.0-py3-none-any.whl", hash = "sha256:44174d237b68bc828e4878c763a9ad6790ee61990e0ae72927694ead57bab8ec"}, + {file = "questionary-2.1.0.tar.gz", hash = "sha256:6302cdd645b19667d8f6e6634774e9538bfcd1aad9be287e743d96cacaf95587"}, +] + +[package.dependencies] +prompt_toolkit = ">=2.0,<4.0" + [[package]] name = "requests" version = "2.32.3" @@ -2180,11 +2299,12 @@ version = "0.2.13" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = "*" -groups = ["dev"] +groups = ["main", "dev"] files = [ {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, ] +markers = {main = "extra == \"ctl\""} [[package]] name = "whenever" @@ -2388,10 +2508,10 @@ type = ["pytest-mypy"] [extras] all = ["Jinja2", "numpy", "numpy", "pyarrow", "pytest", "pyyaml", "rich", "toml", "typer"] -ctl = ["Jinja2", "numpy", "numpy", "pyarrow", "pyyaml", "rich", "toml", "typer"] +ctl = ["Jinja2", "copier", "numpy", "numpy", "pyarrow", "pyyaml", "rich", "toml", "typer"] tests = ["Jinja2", "pytest", "pyyaml", "rich"] [metadata] lock-version = "2.1" python-versions = "^3.9, <3.14" -content-hash = "5e04cf55024fdf4f1c549eb7b17ef3791d8db85fa8447e92b4584f3d01b7f54c" +content-hash = "7b1d820d9eeaa5f3af9a7daed1a6ab16c98f0b4ce2e7abcf616379bb0d23e230" From 687c572f3de0d52437cff693614fa5521eefa0ee Mon Sep 17 00:00:00 2001 From: Alex Gittings Date: Tue, 22 Apr 2025 10:45:36 +0100 Subject: [PATCH 10/14] Fix formatting --- infrahub_sdk/ctl/example_repo/README.md | 2 ++ infrahub_sdk/ctl/example_repo/schemas/example.yml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/infrahub_sdk/ctl/example_repo/README.md b/infrahub_sdk/ctl/example_repo/README.md index 85daf5e7..fc1d6799 100644 --- a/infrahub_sdk/ctl/example_repo/README.md +++ b/infrahub_sdk/ctl/example_repo/README.md @@ -3,6 +3,7 @@ Welcome! This repository was initialized via the `infrahubctl repo init` command. That bootstraps a repository for use with some example data. ## Installation + Running `poetry install` will install all the main dependencies you need to interact with this repository. ## Starting Infrahub @@ -24,6 +25,7 @@ Available tasks: To start infrahub simply use `invoke start` ## Tests + By default there are some integration tests that will spin up Infrahub and its dependencies in docker and load the repository and schema. This can be run using the following: ```bash diff --git a/infrahub_sdk/ctl/example_repo/schemas/example.yml b/infrahub_sdk/ctl/example_repo/schemas/example.yml index 17e54b4b..3d1ab684 100644 --- a/infrahub_sdk/ctl/example_repo/schemas/example.yml +++ b/infrahub_sdk/ctl/example_repo/schemas/example.yml @@ -29,4 +29,4 @@ nodes: cardinality: one peer: NetworkDevice optional: false - kind: Parent \ No newline at end of file + kind: Parent From b7e870b9c73f8f3c327658a661376f5bd6ef072a Mon Sep 17 00:00:00 2001 From: Alex Gittings Date: Tue, 22 Apr 2025 10:55:15 +0100 Subject: [PATCH 11/14] Add return types --- infrahub_sdk/ctl/example_repo/tasks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infrahub_sdk/ctl/example_repo/tasks.py b/infrahub_sdk/ctl/example_repo/tasks.py index 4b2e3b1d..0d36f620 100644 --- a/infrahub_sdk/ctl/example_repo/tasks.py +++ b/infrahub_sdk/ctl/example_repo/tasks.py @@ -49,7 +49,7 @@ def restart(context: Context, component: str = "") -> None: @task -def load_schema(ctx: Context): +def load_schema(ctx: Context) -> None: """ Load schemas into InfraHub using infrahubctl. """ @@ -57,7 +57,7 @@ def load_schema(ctx: Context): @task -def test(ctx: Context): +def test(ctx: Context) -> None: """ Run tests using pytest. """ From 7a8508cce86d29fa30d506959a3480fe7b7ae2d4 Mon Sep 17 00:00:00 2001 From: Alex Gittings Date: Tue, 22 Apr 2025 10:55:31 +0100 Subject: [PATCH 12/14] Ignore coppier folders --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index fd415be1..fe4f41c0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -107,6 +107,7 @@ addopts = "-vs --cov-report term-missing --cov-report xml --dist loadscope" pretty = true ignore_missing_imports = true disallow_untyped_defs = true +exclude = "\\{%.*%\\}" [[tool.mypy.overrides]] module = "infrahub_sdk.ctl.check" From 8ffa5dee55911ca1c20e43632ea68a8feea54b57 Mon Sep 17 00:00:00 2001 From: Alex Gittings Date: Tue, 22 Apr 2025 11:24:32 +0100 Subject: [PATCH 13/14] Add optional type --- infrahub_sdk/ctl/repository.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrahub_sdk/ctl/repository.py b/infrahub_sdk/ctl/repository.py index 2f657be0..08de9610 100644 --- a/infrahub_sdk/ctl/repository.py +++ b/infrahub_sdk/ctl/repository.py @@ -172,7 +172,7 @@ async def list( @app.command() async def init( dst: Path, - data: Path | None = None, + data: Optional[Path] = None, _: str = CONFIG_PARAM, ) -> None: """Initialize a new Infrahub repository.""" From 7ce53cfabe9ae4d877e630eaf0c703dead1a7b9e Mon Sep 17 00:00:00 2001 From: Alex Gittings Date: Tue, 22 Apr 2025 14:06:36 +0100 Subject: [PATCH 14/14] Add directory to CI env --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d799ad1f..ed247280 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -228,6 +228,10 @@ jobs: uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + - name: "Set environment variables" + run: | + RUNNER_NAME=$(echo "${{ runner.name }}" | grep -o 'ghrunner[0-9]\+' | sed 's/ghrunner\([0-9]\+\)/ghrunner_\1/') + echo "PYTEST_DEBUG_TEMPROOT=/var/lib/github/${RUNNER_NAME}/_temp" >> $GITHUB_ENV - name: "Setup environment" run: | pipx install poetry==2.1 --python python${{ matrix.python-version }}