From e90acb42cad9078bb81322c945eacf0840db7950 Mon Sep 17 00:00:00 2001 From: Lucas Guillermou Date: Wed, 22 Jan 2025 15:10:50 +0100 Subject: [PATCH 01/12] WIP: Run CI tests against multiple versions --- .github/workflows/ci.yml | 83 ++++++++++++++++++++++- infrahub_sdk/testing/docker.py | 30 ++++++++ tests/integration/test_infrahub_client.py | 4 -- tests/integration/test_node.py | 4 -- tests/integration/test_repository.py | 6 -- 5 files changed, 111 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b8a348a6..834a7051 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -173,7 +173,7 @@ jobs: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} # ------------------------------------------ Integration Tests ------------------------------------------ - integration-tests: + integration-tests-latest-infrahub: if: | always() && !cancelled() && !contains(needs.*.result, 'failure') && @@ -201,8 +201,87 @@ jobs: pip install invoke toml codecov - name: "Install Package" run: "poetry install --all-extras" + - name: "Set environment variables for python_testcontainers" + run: | + echo INFRAHUB_TESTING_IMAGE_VER=latest >> $GITHUB_ENV - name: "Integration Tests" - run: "poetry run pytest --cov infrahub_sdk tests/integration/" + run: | + poetry run pytest --cov infrahub_sdk tests/integration/ + - name: "Upload coverage to Codecov" + run: | + codecov --flags integration-tests + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + integration-tests-local-infrahub: + if: | + always() && !cancelled() && + !contains(needs.*.result, 'failure') && + !contains(needs.*.result, 'cancelled') && + needs.files-changed.outputs.python == 'true' && + (github.base_ref == 'stable' || github.base_ref == 'develop') + needs: ["files-changed", "yaml-lint", "python-lint"] + runs-on: + group: "huge-runners" + timeout-minutes: 30 + steps: + - name: "Check out repository code" + uses: "actions/checkout@v4" + + - name: "Extract target branch name" + id: extract_branch + run: echo "TARGET_BRANCH=${{ github.base_ref }}" >> $GITHUB_ENV + + - name: "Checkout infrahub repository" + uses: "actions/checkout@v4" + with: + repository: "opsmill/infrahub" + path: "infrahub-server" + ref: ${{ github.base_ref }} + submodules: true + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: "Setup git credentials prior dev.build" + run: | + cd infrahub-server + git config --global user.name 'Infrahub' + git config --global user.email 'infrahub@opsmill.com' + git config --global --add safe.directory '*' + git config --global credential.usehttppath true + git config --global credential.helper /usr/local/bin/infrahub-git-credential + + - name: "Set environment variables prior dev.build" + run: | + echo "INFRAHUB_BUILD_NAME=infrahub-${{ runner.name }}" >> $GITHUB_ENV + 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 + echo "INFRAHUB_IMAGE_VER=local-${{ runner.name }}-${{ github.sha }}" >> $GITHUB_ENV + echo "INFRAHUB_TESTING_IMAGE_VER=local-${{ runner.name }}-${{ github.sha }}" >> $GITHUB_ENV + echo "INFRAHUB_TESTING_DOCKER_IMAGE=opsmill/infrahub" >> $GITHUB_ENV + + - name: "Build container" + run: | + cd infrahub-server + inv dev.build + + - name: "Setup environment" + run: | + pipx install poetry==1.8.5 + poetry config virtualenvs.create true --local + pip install invoke toml codecov + + - name: "Install Package" + run: "poetry install --all-extras" + + - name: "Integration Tests" + run: | + echo "Running tests for version: $INFRAHUB_TESTING_IMAGE_VER" + poetry run pytest --cov infrahub_sdk tests/integration/ + - name: "Upload coverage to Codecov" run: | codecov --flags integration-tests diff --git a/infrahub_sdk/testing/docker.py b/infrahub_sdk/testing/docker.py index 4beccb16..e5865678 100644 --- a/infrahub_sdk/testing/docker.py +++ b/infrahub_sdk/testing/docker.py @@ -1,10 +1,40 @@ +from __future__ import annotations + +import os + import pytest from infrahub_testcontainers.helpers import TestInfrahubDocker +from packaging.version import InvalidVersion, Version from .. import Config, InfrahubClient, InfrahubClientSync +INFRAHUB_VERSION = os.getenv("INFRAHUB_TESTING_IMAGE_VER", "latest") + + +def skip_version(min_infrahub_version: str | None = None, max_infrahub_version: str | None = None) -> bool: + """ + Check if a test should be skipped depending on infrahub version. + """ + try: + version = Version(INFRAHUB_VERSION) + except InvalidVersion: + # We would typically end up here for development purpose while running a CI test against + # unreleased versions of infrahub, like `stable` or `develop` branch. + # For now, we consider this means we are testing against the most recent version of infrahub, + # so we skip if the test should not be ran against a maximum version. + return max_infrahub_version is None + + if min_infrahub_version is not None and version < Version(min_infrahub_version): + return True + + return max_infrahub_version is not None and version > Version(max_infrahub_version) + class TestInfrahubDockerClient(TestInfrahubDocker): + @pytest.fixture(scope="class") + def infrahub_version(self) -> str: + return INFRAHUB_VERSION + @pytest.fixture(scope="class") def client(self, infrahub_port: int) -> InfrahubClient: return InfrahubClient( diff --git a/tests/integration/test_infrahub_client.py b/tests/integration/test_infrahub_client.py index 3c607518..817205ec 100644 --- a/tests/integration/test_infrahub_client.py +++ b/tests/integration/test_infrahub_client.py @@ -16,10 +16,6 @@ class TestInfrahubNode(TestInfrahubDockerClient, SchemaAnimal): - @pytest.fixture(scope="class") - def infrahub_version(self) -> str: - return "1.1.0" - @pytest.fixture(scope="class") async def base_dataset( self, diff --git a/tests/integration/test_node.py b/tests/integration/test_node.py index a0d8e890..03d7e241 100644 --- a/tests/integration/test_node.py +++ b/tests/integration/test_node.py @@ -11,10 +11,6 @@ class TestInfrahubNode(TestInfrahubDockerClient, SchemaCarPerson): - @pytest.fixture(scope="class") - def infrahub_version(self) -> str: - return "1.1.0" - @pytest.fixture(scope="class") async def initial_schema(self, default_branch: str, client: InfrahubClient, schema_base: SchemaRoot) -> None: await client.schema.wait_until_converged(branch=default_branch) diff --git a/tests/integration/test_repository.py b/tests/integration/test_repository.py index 359f1e68..76b14f58 100644 --- a/tests/integration/test_repository.py +++ b/tests/integration/test_repository.py @@ -2,8 +2,6 @@ from typing import TYPE_CHECKING -import pytest - from infrahub_sdk.testing.docker import TestInfrahubDockerClient from infrahub_sdk.testing.repository import GitRepo from infrahub_sdk.utils import get_fixtures_dir @@ -13,10 +11,6 @@ class TestInfrahubRepository(TestInfrahubDockerClient): - @pytest.fixture(scope="class") - def infrahub_version(self) -> str: - return "1.1.0" - async def test_add_repository(self, client: InfrahubClient, remote_repos_dir): src_directory = get_fixtures_dir() / "integration/mock_repo" repo = GitRepo(name="mock_repo", src_directory=src_directory, dst_directory=remote_repos_dir) From fa351f75cd2f601236011cc4a8f77e0a32c2ac8e Mon Sep 17 00:00:00 2001 From: Mikhail Yohman Date: Tue, 28 Jan 2025 06:09:03 -0700 Subject: [PATCH 02/12] pyproject.toml: Removed [project] section for now. (#252) --- pyproject.toml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2ccddc6d..f9a0ac13 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,3 @@ -[project] -name = "infrahub-sdk" -version = "1.5.0" -requires-python = ">=3.9" - [tool.poetry] name = "infrahub-sdk" version = "1.7.0" From abf9ead5ae2f67be71cdc6c5fee5c6caebbb7109 Mon Sep 17 00:00:00 2001 From: wvandeun Date: Mon, 10 Feb 2025 16:03:10 +0100 Subject: [PATCH 03/12] fixes artifact_fetch method to fetch artifact using artifact name --- infrahub_sdk/node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrahub_sdk/node.py b/infrahub_sdk/node.py index 174cc06a..77ba7e76 100644 --- a/infrahub_sdk/node.py +++ b/infrahub_sdk/node.py @@ -1125,7 +1125,7 @@ async def artifact_generate(self, name: str) -> None: async def artifact_fetch(self, name: str) -> str | dict[str, Any]: self._validate_artifact_support(ARTIFACT_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE) - artifact = await self._client.get(kind="CoreArtifact", definition__name__value=name, object__ids=[self.id]) + artifact = await self._client.get(kind="CoreArtifact", name__value=name, object__ids=[self.id]) content = await self._client.object_store.get(identifier=artifact.storage_id.value) # type: ignore[attr-defined] return content From 25ec60bed64446fe91406ee6ae340118c05337d4 Mon Sep 17 00:00:00 2001 From: wvandeun Date: Mon, 10 Feb 2025 16:03:38 +0100 Subject: [PATCH 04/12] fixes artifact_generate method to use the name of the artifact --- infrahub_sdk/node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrahub_sdk/node.py b/infrahub_sdk/node.py index 77ba7e76..5be89a01 100644 --- a/infrahub_sdk/node.py +++ b/infrahub_sdk/node.py @@ -1118,7 +1118,7 @@ async def generate(self, nodes: list[str] | None = None) -> None: async def artifact_generate(self, name: str) -> None: self._validate_artifact_support(ARTIFACT_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE) - artifact = await self._client.get(kind="CoreArtifact", definition__name__value=name, object__ids=[self.id]) + artifact = await self._client.get(kind="CoreArtifact", name__value=name, object__ids=[self.id]) await artifact.definition.fetch() # type: ignore[attr-defined] await artifact.definition.peer.generate([artifact.id]) # type: ignore[attr-defined] From 86024fcdba808d40663af99332c9bca54f0deef5 Mon Sep 17 00:00:00 2001 From: wvandeun Date: Tue, 11 Feb 2025 09:52:10 +0100 Subject: [PATCH 05/12] add changelog entry --- changelog/+artifact_methods.changed.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/+artifact_methods.changed.md diff --git a/changelog/+artifact_methods.changed.md b/changelog/+artifact_methods.changed.md new file mode 100644 index 00000000..6aa2b60b --- /dev/null +++ b/changelog/+artifact_methods.changed.md @@ -0,0 +1 @@ +Changes InfrahubNode `artifact_fetch` and `artifact_generate` methods to use the name of the artifact instead of the name of the artifact definition From aa65bb01e54367e5b7ffa118b5a14431af3ec1c7 Mon Sep 17 00:00:00 2001 From: Baptiste <32564248+BaptisteGi@users.noreply.github.com> Date: Tue, 11 Feb 2025 16:39:00 +0100 Subject: [PATCH 06/12] Protocols command consumes default branch from env (#263) * Get default branch from env * Add comment on all hardcoded main branch to be fixed later * Add release note --- changelog/104.fixed.md | 1 + infrahub_sdk/ctl/cli_commands.py | 3 ++- infrahub_sdk/ctl/exporter.py | 2 +- infrahub_sdk/ctl/importer.py | 2 +- infrahub_sdk/ctl/menu.py | 2 +- infrahub_sdk/ctl/object.py | 2 +- infrahub_sdk/ctl/repository.py | 2 +- infrahub_sdk/ctl/schema.py | 4 ++-- 8 files changed, 10 insertions(+), 8 deletions(-) create mode 100644 changelog/104.fixed.md diff --git a/changelog/104.fixed.md b/changelog/104.fixed.md new file mode 100644 index 00000000..e6e64a72 --- /dev/null +++ b/changelog/104.fixed.md @@ -0,0 +1 @@ +- `protocols` CTL command properly gets default branch setting from environment variable \ No newline at end of file diff --git a/infrahub_sdk/ctl/cli_commands.py b/infrahub_sdk/ctl/cli_commands.py index 3cb23a55..568e02c8 100644 --- a/infrahub_sdk/ctl/cli_commands.py +++ b/infrahub_sdk/ctl/cli_commands.py @@ -129,7 +129,7 @@ async def run( method: str = "run", debug: bool = False, _: str = CONFIG_PARAM, - branch: str = typer.Option("main", help="Branch on which to run the script."), + branch: str = typer.Option("main", help="Branch on which to run the script."), # TODO: Replace main by None concurrent: int | None = typer.Option( None, help="Maximum number of requests to execute at the same time.", @@ -383,6 +383,7 @@ def protocols( else: client = initialize_client_sync() + branch = branch or client.default_branch schema.update(client.schema.fetch(branch=branch)) code_generator = CodeGenerator(schema=schema) diff --git a/infrahub_sdk/ctl/exporter.py b/infrahub_sdk/ctl/exporter.py index 50be3282..c1e81412 100644 --- a/infrahub_sdk/ctl/exporter.py +++ b/infrahub_sdk/ctl/exporter.py @@ -22,7 +22,7 @@ def dump( directory: Path = typer.Option(directory_name_with_timestamp, help="Directory path to store export"), quiet: bool = typer.Option(False, help="No console output"), _: str = CONFIG_PARAM, - branch: str = typer.Option("main", help="Branch from which to export"), + branch: str = typer.Option("main", help="Branch from which to export"), # TODO: Replace main by None concurrent: int = typer.Option( 4, help="Maximum number of requests to execute at the same time.", diff --git a/infrahub_sdk/ctl/importer.py b/infrahub_sdk/ctl/importer.py index be087361..00d88db5 100644 --- a/infrahub_sdk/ctl/importer.py +++ b/infrahub_sdk/ctl/importer.py @@ -25,7 +25,7 @@ def load( ), quiet: bool = typer.Option(False, help="No console output"), _: str = CONFIG_PARAM, - branch: str = typer.Option("main", help="Branch from which to export"), + branch: str = typer.Option("main", help="Branch from which to export"), # TODO: Replace main by None concurrent: int | None = typer.Option( None, help="Maximum number of requests to execute at the same time.", diff --git a/infrahub_sdk/ctl/menu.py b/infrahub_sdk/ctl/menu.py index fe9798fa..533c0d0d 100644 --- a/infrahub_sdk/ctl/menu.py +++ b/infrahub_sdk/ctl/menu.py @@ -27,7 +27,7 @@ def callback() -> None: async def load( menus: list[Path], debug: bool = False, - branch: str = typer.Option("main", help="Branch on which to load the menu."), + branch: str = typer.Option("main", help="Branch on which to load the menu."), # TODO: Replace main by None _: str = CONFIG_PARAM, ) -> None: """Load one or multiple menu files into Infrahub.""" diff --git a/infrahub_sdk/ctl/object.py b/infrahub_sdk/ctl/object.py index 5f8e71cf..e619e229 100644 --- a/infrahub_sdk/ctl/object.py +++ b/infrahub_sdk/ctl/object.py @@ -27,7 +27,7 @@ def callback() -> None: async def load( paths: list[Path], debug: bool = False, - branch: str = typer.Option("main", help="Branch on which to load the objects."), + branch: str = typer.Option("main", help="Branch on which to load the objects."), # TODO: Replace main by None _: str = CONFIG_PARAM, ) -> None: """Load one or multiple objects files into Infrahub.""" diff --git a/infrahub_sdk/ctl/repository.py b/infrahub_sdk/ctl/repository.py index e57ee6bf..09003f6e 100644 --- a/infrahub_sdk/ctl/repository.py +++ b/infrahub_sdk/ctl/repository.py @@ -74,7 +74,7 @@ async def add( commit: str = "", read_only: bool = False, debug: bool = False, - branch: str = typer.Option("main", help="Branch on which to add the repository."), + branch: str = typer.Option("main", help="Branch on which to add the repository."), # TODO: Replace main by None _: str = CONFIG_PARAM, ) -> None: """Add a new repository.""" diff --git a/infrahub_sdk/ctl/schema.py b/infrahub_sdk/ctl/schema.py index 3c9557c5..c3a2e794 100644 --- a/infrahub_sdk/ctl/schema.py +++ b/infrahub_sdk/ctl/schema.py @@ -108,7 +108,7 @@ def get_node(schemas_data: list[dict], schema_index: int, node_index: int) -> di async def load( schemas: list[Path], debug: bool = False, - branch: str = typer.Option("main", help="Branch on which to load the schema."), + branch: str = typer.Option("main", help="Branch on which to load the schema."), # TODO: Replace main by None wait: int = typer.Option(0, help="Time in seconds to wait until the schema has converged across all workers"), _: str = CONFIG_PARAM, ) -> None: @@ -159,7 +159,7 @@ async def load( async def check( schemas: list[Path], debug: bool = False, - branch: str = typer.Option("main", help="Branch on which to check the schema."), + branch: str = typer.Option("main", help="Branch on which to check the schema."), # TODO: Replace main by None _: str = CONFIG_PARAM, ) -> None: """Check if schema files are valid and what would be the impact of loading them with Infrahub.""" From 81c74930565dda2673cf3a31d641e9c0dff271ff Mon Sep 17 00:00:00 2001 From: Mikhail Yohman Date: Tue, 11 Feb 2025 12:21:26 -0700 Subject: [PATCH 07/12] InfrahubCTL: Repository command changes (#244) * Refactor repository to not create blank credentials. Remove commit option in favor of ref option to match API and allow setting the ref on CTL. * Format/Lint. * Fixed payload between CoreRepositoryCreate and CoreReadOnlyRepositoryCreate mutations. --- infrahub_sdk/ctl/repository.py | 19 +++++-- tests/unit/ctl/test_repository_app.py | 71 ++++++++++++++++++++++----- 2 files changed, 74 insertions(+), 16 deletions(-) diff --git a/infrahub_sdk/ctl/repository.py b/infrahub_sdk/ctl/repository.py index 09003f6e..8f793946 100644 --- a/infrahub_sdk/ctl/repository.py +++ b/infrahub_sdk/ctl/repository.py @@ -71,7 +71,7 @@ async def add( description: str = "", username: str | None = None, password: str = "", - commit: str = "", + ref: str = "", read_only: bool = False, debug: bool = False, branch: str = typer.Option("main", help="Branch on which to add the repository."), # TODO: Replace main by None @@ -86,15 +86,24 @@ async def add( "name": {"value": name}, "location": {"value": location}, "description": {"value": description}, - "commit": {"value": commit}, }, } + if read_only: + input_data["data"]["ref"] = {"value": ref} + else: + input_data["data"]["default_branch"] = {"value": ref} client = initialize_client() - credential = await client.create(kind="CorePasswordCredential", name=name, username=username, password=password) - await credential.save(allow_upsert=True) - input_data["data"]["credential"] = {"id": credential.id} + if username or password: + credential = await client.create( + kind="CorePasswordCredential", + name=name, + username=username, + password=password, + ) + await credential.save(allow_upsert=True) + input_data["data"]["credential"] = {"id": credential.id} query = Mutation( mutation="CoreReadOnlyRepositoryCreate" if read_only else "CoreRepositoryCreate", diff --git a/tests/unit/ctl/test_repository_app.py b/tests/unit/ctl/test_repository_app.py index a7cd70f0..04d05ed4 100644 --- a/tests/unit/ctl/test_repository_app.py +++ b/tests/unit/ctl/test_repository_app.py @@ -29,6 +29,55 @@ def mock_client() -> mock.Mock: class TestInfrahubctlRepository: """Groups the 'infrahubctl repository' test cases.""" + @requires_python_310 + @mock.patch("infrahub_sdk.ctl.repository.initialize_client") + def test_repo_no_username_or_password(self, mock_init_client, mock_client) -> None: + """Case allow no username to be passed in and set it as None rather than blank string that fails.""" + mock_cred = mock.AsyncMock() + mock_cred.id = "1234" + mock_client.create.return_value = mock_cred + + mock_init_client.return_value = mock_client + output = runner.invoke( + app, + [ + "repository", + "add", + "Gitlab", + "https://gitlab.com/opsmill/example-repo.git", + ], + ) + assert output.exit_code == 0 + mock_client.create.assert_not_called() + mock_cred.save.assert_not_called() + mock_client.execute_graphql.assert_called_once() + mock_client.execute_graphql.assert_called_with( + query=""" +mutation { + CoreRepositoryCreate( + data: { + name: { + value: "Gitlab" + } + location: { + value: "https://gitlab.com/opsmill/example-repo.git" + } + description: { + value: "" + } + default_branch: { + value: "" + } + } + ){ + ok + } +} +""", + branch_name="main", + tracker="mutation-repository-create", + ) + @requires_python_310 @mock.patch("infrahub_sdk.ctl.repository.initialize_client") def test_repo_no_username(self, mock_init_client, mock_client) -> None: @@ -74,7 +123,7 @@ def test_repo_no_username(self, mock_init_client, mock_client) -> None: description: { value: "" } - commit: { + default_branch: { value: "" } credential: { @@ -137,7 +186,7 @@ def test_repo_username(self, mock_init_client, mock_client) -> None: description: { value: "" } - commit: { + default_branch: { value: "" } credential: { @@ -168,7 +217,7 @@ def test_repo_readonly_true(self, mock_init_client, mock_client) -> None: "repository", "add", "Gitlab", - "https://gitlab.com/FragmentedPacket/nautobot-plugin-ansible-filters.git", + "https://gitlab.com/opsmill/example-repo.git", "--password", "mySup3rSecureP@ssw0rd", "--read-only", @@ -194,12 +243,12 @@ def test_repo_readonly_true(self, mock_init_client, mock_client) -> None: value: "Gitlab" } location: { - value: "https://gitlab.com/FragmentedPacket/nautobot-plugin-ansible-filters.git" + value: "https://gitlab.com/opsmill/example-repo.git" } description: { value: "" } - commit: { + ref: { value: "" } credential: { @@ -230,15 +279,15 @@ def test_repo_description_commit_branch(self, mock_init_client, mock_client) -> "repository", "add", "Gitlab", - "https://gitlab.com/FragmentedPacket/nautobot-plugin-ansible-filters.git", + "https://gitlab.com/opsmill/example-repo.git", "--password", "mySup3rSecureP@ssw0rd", "--username", "opsmill", "--description", "This is a test description", - "--commit", - "myHashCommit", + "--ref", + "my-custom-branch", "--branch", "develop", ], @@ -263,13 +312,13 @@ def test_repo_description_commit_branch(self, mock_init_client, mock_client) -> value: "Gitlab" } location: { - value: "https://gitlab.com/FragmentedPacket/nautobot-plugin-ansible-filters.git" + value: "https://gitlab.com/opsmill/example-repo.git" } description: { value: "This is a test description" } - commit: { - value: "myHashCommit" + default_branch: { + value: "my-custom-branch" } credential: { id: "1234" From 61adb22aa8c3ce7d2aff8a0501e34ce63ee13345 Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Thu, 20 Feb 2025 06:00:19 +0100 Subject: [PATCH 08/12] Move the function `read_file` from the ctl module to the SDK --- changelog/+move-read-file.housekeeping.md | 1 + infrahub_sdk/ctl/_file.py | 13 ------------- infrahub_sdk/ctl/exceptions.py | 6 ------ infrahub_sdk/ctl/repository.py | 9 ++++----- infrahub_sdk/ctl/utils.py | 3 ++- infrahub_sdk/exceptions.py | 6 ++++++ infrahub_sdk/utils.py | 12 +++++++++++- infrahub_sdk/yaml.py | 5 ++--- 8 files changed, 26 insertions(+), 29 deletions(-) create mode 100644 changelog/+move-read-file.housekeeping.md delete mode 100644 infrahub_sdk/ctl/_file.py diff --git a/changelog/+move-read-file.housekeeping.md b/changelog/+move-read-file.housekeeping.md new file mode 100644 index 00000000..e9d818c5 --- /dev/null +++ b/changelog/+move-read-file.housekeeping.md @@ -0,0 +1 @@ +Move the function `read_file` from the ctl module to the SDK. \ No newline at end of file diff --git a/infrahub_sdk/ctl/_file.py b/infrahub_sdk/ctl/_file.py deleted file mode 100644 index 96629d72..00000000 --- a/infrahub_sdk/ctl/_file.py +++ /dev/null @@ -1,13 +0,0 @@ -from pathlib import Path - -from .exceptions import FileNotValidError - - -def read_file(file_name: Path) -> str: - if not file_name.is_file(): - raise FileNotValidError(name=str(file_name), message=f"{file_name} is not a valid file") - try: - with Path.open(file_name, encoding="utf-8") as fobj: - return fobj.read() - except UnicodeDecodeError as exc: - raise FileNotValidError(name=str(file_name), message=f"Unable to read {file_name} with utf-8 encoding") from exc diff --git a/infrahub_sdk/ctl/exceptions.py b/infrahub_sdk/ctl/exceptions.py index 28436d5b..fc764f3b 100644 --- a/infrahub_sdk/ctl/exceptions.py +++ b/infrahub_sdk/ctl/exceptions.py @@ -6,9 +6,3 @@ class QueryNotFoundError(Error): def __init__(self, name: str, message: str = ""): self.message = message or f"The requested query '{name}' was not found." super().__init__(self.message) - - -class FileNotValidError(Error): - def __init__(self, name: str, message: str = ""): - self.message = message or f"Cannot parse '{name}' content." - super().__init__(self.message) diff --git a/infrahub_sdk/ctl/repository.py b/infrahub_sdk/ctl/repository.py index 8f793946..d6332d7c 100644 --- a/infrahub_sdk/ctl/repository.py +++ b/infrahub_sdk/ctl/repository.py @@ -8,15 +8,14 @@ from rich.console import Console from rich.table import Table -from infrahub_sdk.ctl.client import initialize_client - from ..async_typer import AsyncTyper -from ..ctl.exceptions import FileNotValidError -from ..ctl.utils import init_logging +from ..exceptions import FileNotValidError from ..graphql import Mutation, Query from ..schema.repository import InfrahubRepositoryConfig -from ._file import read_file +from ..utils import read_file +from .client import initialize_client from .parameters import CONFIG_PARAM +from .utils import init_logging app = AsyncTyper() console = Console() diff --git a/infrahub_sdk/ctl/utils.py b/infrahub_sdk/ctl/utils.py index 5c0b069e..e4474d8d 100644 --- a/infrahub_sdk/ctl/utils.py +++ b/infrahub_sdk/ctl/utils.py @@ -17,10 +17,10 @@ from rich.logging import RichHandler from rich.markup import escape -from ..ctl.exceptions import FileNotValidError, QueryNotFoundError from ..exceptions import ( AuthenticationError, Error, + FileNotValidError, GraphQLError, NodeNotFoundError, ResourceNotDefinedError, @@ -30,6 +30,7 @@ ) from ..yaml import YamlFile from .client import initialize_client_sync +from .exceptions import QueryNotFoundError if TYPE_CHECKING: from ..schema.repository import InfrahubRepositoryConfig diff --git a/infrahub_sdk/exceptions.py b/infrahub_sdk/exceptions.py index f8eff9cc..b3120441 100644 --- a/infrahub_sdk/exceptions.py +++ b/infrahub_sdk/exceptions.py @@ -131,3 +131,9 @@ class UninitializedError(Error): class InvalidResponseError(Error): """Raised when an object requires an initialization step before use""" + + +class FileNotValidError(Error): + def __init__(self, name: str, message: str = ""): + self.message = message or f"Cannot parse '{name}' content." + super().__init__(self.message) diff --git a/infrahub_sdk/utils.py b/infrahub_sdk/utils.py index aa065e27..2627a062 100644 --- a/infrahub_sdk/utils.py +++ b/infrahub_sdk/utils.py @@ -17,7 +17,7 @@ from infrahub_sdk.repository import GitRepoManager -from .exceptions import JsonDecodeError +from .exceptions import FileNotValidError, JsonDecodeError if TYPE_CHECKING: from graphql import GraphQLResolveInfo @@ -342,6 +342,16 @@ def write_to_file(path: Path, value: Any) -> bool: return written is not None +def read_file(file_name: Path) -> str: + if not file_name.is_file(): + raise FileNotValidError(name=str(file_name), message=f"{file_name} is not a valid file") + try: + with Path.open(file_name, encoding="utf-8") as fobj: + return fobj.read() + except UnicodeDecodeError as exc: + raise FileNotValidError(name=str(file_name), message=f"Unable to read {file_name} with utf-8 encoding") from exc + + def get_user_permissions(data: list[dict]) -> dict: groups = {} for group in data: diff --git a/infrahub_sdk/yaml.py b/infrahub_sdk/yaml.py index 5cfd5bce..69d973cf 100644 --- a/infrahub_sdk/yaml.py +++ b/infrahub_sdk/yaml.py @@ -8,9 +8,8 @@ from pydantic import BaseModel, Field from typing_extensions import Self -from .ctl._file import read_file -from .ctl.exceptions import FileNotValidError -from .utils import find_files +from .exceptions import FileNotValidError +from .utils import find_files, read_file class InfrahubFileApiVersion(str, Enum): From 75aebe4b5cee2f76eafc0ea48fae3bc86eda147d Mon Sep 17 00:00:00 2001 From: Mikhail Yohman Date: Thu, 20 Feb 2025 07:21:54 -0700 Subject: [PATCH 09/12] Add `infrahub.tasks` as built-in logger to `InfrahubGenerator` class (#274) * Test building in logger to InfrahubGenerator class to allow logs to be easily propagated via CTL and CI pipeline logs. * Remove unused logging import. * Add changelog to describe change. --- changelog/+add-logger-to-generator-class.md | 1 + infrahub_sdk/ctl/generator.py | 6 +++--- infrahub_sdk/generator.py | 3 +++ 3 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 changelog/+add-logger-to-generator-class.md diff --git a/changelog/+add-logger-to-generator-class.md b/changelog/+add-logger-to-generator-class.md new file mode 100644 index 00000000..ab8be4d5 --- /dev/null +++ b/changelog/+add-logger-to-generator-class.md @@ -0,0 +1 @@ +Added logger to `InfrahubGenerator` class to allow users use built-in logging (`self.logger`) to show logging within Infrahub CI pipeline. diff --git a/infrahub_sdk/ctl/generator.py b/infrahub_sdk/ctl/generator.py index 689f17e3..5258caa6 100644 --- a/infrahub_sdk/ctl/generator.py +++ b/infrahub_sdk/ctl/generator.py @@ -9,7 +9,7 @@ from ..ctl import config from ..ctl.client import initialize_client from ..ctl.repository import get_repository_config -from ..ctl.utils import execute_graphql_query, parse_cli_vars +from ..ctl.utils import execute_graphql_query, init_logging, parse_cli_vars from ..exceptions import ModuleImportError from ..node import InfrahubNode @@ -20,11 +20,12 @@ async def run( generator_name: str, path: str, # noqa: ARG001 - debug: bool, # noqa: ARG001 + debug: bool, list_available: bool, branch: str | None = None, variables: list[str] | None = None, ) -> None: + init_logging(debug=debug) repository_config = get_repository_config(Path(config.INFRAHUB_REPO_CONFIG_FILE)) if list_available or not generator_name: @@ -34,7 +35,6 @@ async def run( generator_config = repository_config.get_generator_definition(name=generator_name) console = Console() - relative_path = str(generator_config.file_path.parent) if generator_config.file_path.parent != Path() else None try: diff --git a/infrahub_sdk/generator.py b/infrahub_sdk/generator.py index e6448b3e..3ba6c767 100644 --- a/infrahub_sdk/generator.py +++ b/infrahub_sdk/generator.py @@ -1,5 +1,6 @@ from __future__ import annotations +import logging import os from abc import abstractmethod from typing import TYPE_CHECKING @@ -27,6 +28,7 @@ def __init__( generator_instance: str = "", params: dict | None = None, convert_query_response: bool = False, + logger: logging.Logger | None = None, ) -> None: self.query = query self.branch = branch @@ -41,6 +43,7 @@ def __init__( self._related_nodes: list[InfrahubNode] = [] self.infrahub_node = infrahub_node self.convert_query_response = convert_query_response + self.logger = logger if logger else logging.getLogger("infrahub.tasks") @property def store(self) -> NodeStore: From 73cda313819098d2dabc00c8b8a37ff8463a05c0 Mon Sep 17 00:00:00 2001 From: Baptiste <32564248+BaptisteGi@users.noreply.github.com> Date: Fri, 21 Feb 2025 10:17:27 +0100 Subject: [PATCH 10/12] Remove default value "main" for branch parameter from all Infrahub CTL commands (#270) * Remove default value main for branch parameter * Changelog * Remove branch parameter from repository commands as node is branch agnostic * Add None for username * Remove branch parameter from tests * Remove branch parameter from test * Add option to list repository by branch * Adjust parameter help --- changelog/264.fixed.md | 1 + infrahub_sdk/ctl/cli_commands.py | 2 +- infrahub_sdk/ctl/exporter.py | 2 +- infrahub_sdk/ctl/importer.py | 2 +- infrahub_sdk/ctl/menu.py | 2 +- infrahub_sdk/ctl/object.py | 2 +- infrahub_sdk/ctl/repository.py | 7 +++---- infrahub_sdk/ctl/schema.py | 4 ++-- tests/unit/ctl/test_repository_app.py | 9 +-------- 9 files changed, 12 insertions(+), 19 deletions(-) create mode 100644 changelog/264.fixed.md diff --git a/changelog/264.fixed.md b/changelog/264.fixed.md new file mode 100644 index 00000000..da596b06 --- /dev/null +++ b/changelog/264.fixed.md @@ -0,0 +1 @@ +Remove default value "main" for branch parameter from all Infrahub CTL commands. \ No newline at end of file diff --git a/infrahub_sdk/ctl/cli_commands.py b/infrahub_sdk/ctl/cli_commands.py index 568e02c8..19980eb2 100644 --- a/infrahub_sdk/ctl/cli_commands.py +++ b/infrahub_sdk/ctl/cli_commands.py @@ -129,7 +129,7 @@ async def run( method: str = "run", debug: bool = False, _: str = CONFIG_PARAM, - branch: str = typer.Option("main", help="Branch on which to run the script."), # TODO: Replace main by None + branch: str = typer.Option(None, help="Branch on which to run the script."), concurrent: int | None = typer.Option( None, help="Maximum number of requests to execute at the same time.", diff --git a/infrahub_sdk/ctl/exporter.py b/infrahub_sdk/ctl/exporter.py index c1e81412..ae5e5d18 100644 --- a/infrahub_sdk/ctl/exporter.py +++ b/infrahub_sdk/ctl/exporter.py @@ -22,7 +22,7 @@ def dump( directory: Path = typer.Option(directory_name_with_timestamp, help="Directory path to store export"), quiet: bool = typer.Option(False, help="No console output"), _: str = CONFIG_PARAM, - branch: str = typer.Option("main", help="Branch from which to export"), # TODO: Replace main by None + branch: str = typer.Option(None, help="Branch from which to export"), concurrent: int = typer.Option( 4, help="Maximum number of requests to execute at the same time.", diff --git a/infrahub_sdk/ctl/importer.py b/infrahub_sdk/ctl/importer.py index 00d88db5..4645fa06 100644 --- a/infrahub_sdk/ctl/importer.py +++ b/infrahub_sdk/ctl/importer.py @@ -25,7 +25,7 @@ def load( ), quiet: bool = typer.Option(False, help="No console output"), _: str = CONFIG_PARAM, - branch: str = typer.Option("main", help="Branch from which to export"), # TODO: Replace main by None + branch: str = typer.Option(None, help="Branch from which to export"), concurrent: int | None = typer.Option( None, help="Maximum number of requests to execute at the same time.", diff --git a/infrahub_sdk/ctl/menu.py b/infrahub_sdk/ctl/menu.py index 533c0d0d..560564ed 100644 --- a/infrahub_sdk/ctl/menu.py +++ b/infrahub_sdk/ctl/menu.py @@ -27,7 +27,7 @@ def callback() -> None: async def load( menus: list[Path], debug: bool = False, - branch: str = typer.Option("main", help="Branch on which to load the menu."), # TODO: Replace main by None + branch: str = typer.Option(None, help="Branch on which to load the menu."), _: str = CONFIG_PARAM, ) -> None: """Load one or multiple menu files into Infrahub.""" diff --git a/infrahub_sdk/ctl/object.py b/infrahub_sdk/ctl/object.py index e619e229..b589fcc2 100644 --- a/infrahub_sdk/ctl/object.py +++ b/infrahub_sdk/ctl/object.py @@ -27,7 +27,7 @@ def callback() -> None: async def load( paths: list[Path], debug: bool = False, - branch: str = typer.Option("main", help="Branch on which to load the objects."), # TODO: Replace main by None + branch: str = typer.Option(None, help="Branch on which to load the objects."), _: str = CONFIG_PARAM, ) -> None: """Load one or multiple objects files into Infrahub.""" diff --git a/infrahub_sdk/ctl/repository.py b/infrahub_sdk/ctl/repository.py index d6332d7c..4e6d3535 100644 --- a/infrahub_sdk/ctl/repository.py +++ b/infrahub_sdk/ctl/repository.py @@ -73,7 +73,6 @@ async def add( ref: str = "", read_only: bool = False, debug: bool = False, - branch: str = typer.Option("main", help="Branch on which to add the repository."), # TODO: Replace main by None _: str = CONFIG_PARAM, ) -> None: """Add a new repository.""" @@ -110,18 +109,18 @@ async def add( query={"ok": None}, ) - await client.execute_graphql(query=query.render(), branch_name=branch, tracker="mutation-repository-create") + await client.execute_graphql(query=query.render(), tracker="mutation-repository-create") @app.command() async def list( - branch: str | None = None, + branch: str = typer.Option(None, help="Branch on which to list repositories."), debug: bool = False, _: str = CONFIG_PARAM, ) -> None: init_logging(debug=debug) - client = initialize_client(branch=branch) + client = initialize_client() repo_status_query = { "CoreGenericRepository": { diff --git a/infrahub_sdk/ctl/schema.py b/infrahub_sdk/ctl/schema.py index c3a2e794..6e9ff994 100644 --- a/infrahub_sdk/ctl/schema.py +++ b/infrahub_sdk/ctl/schema.py @@ -108,7 +108,7 @@ def get_node(schemas_data: list[dict], schema_index: int, node_index: int) -> di async def load( schemas: list[Path], debug: bool = False, - branch: str = typer.Option("main", help="Branch on which to load the schema."), # TODO: Replace main by None + branch: str = typer.Option(None, help="Branch on which to load the schema."), wait: int = typer.Option(0, help="Time in seconds to wait until the schema has converged across all workers"), _: str = CONFIG_PARAM, ) -> None: @@ -159,7 +159,7 @@ async def load( async def check( schemas: list[Path], debug: bool = False, - branch: str = typer.Option("main", help="Branch on which to check the schema."), # TODO: Replace main by None + branch: str = typer.Option(None, help="Branch on which to check the schema."), _: str = CONFIG_PARAM, ) -> None: """Check if schema files are valid and what would be the impact of loading them with Infrahub.""" diff --git a/tests/unit/ctl/test_repository_app.py b/tests/unit/ctl/test_repository_app.py index 04d05ed4..7fb01ee1 100644 --- a/tests/unit/ctl/test_repository_app.py +++ b/tests/unit/ctl/test_repository_app.py @@ -74,7 +74,6 @@ def test_repo_no_username_or_password(self, mock_init_client, mock_client) -> No } } """, - branch_name="main", tracker="mutation-repository-create", ) @@ -135,7 +134,6 @@ def test_repo_no_username(self, mock_init_client, mock_client) -> None: } } """, - branch_name="main", tracker="mutation-repository-create", ) @@ -198,7 +196,6 @@ def test_repo_username(self, mock_init_client, mock_client) -> None: } } """, - branch_name="main", tracker="mutation-repository-create", ) @@ -260,7 +257,6 @@ def test_repo_readonly_true(self, mock_init_client, mock_client) -> None: } } """, - branch_name="main", tracker="mutation-repository-create", ) @@ -288,8 +284,6 @@ def test_repo_description_commit_branch(self, mock_init_client, mock_client) -> "This is a test description", "--ref", "my-custom-branch", - "--branch", - "develop", ], ) assert output.exit_code == 0 @@ -329,11 +323,10 @@ def test_repo_description_commit_branch(self, mock_init_client, mock_client) -> } } """, - branch_name="develop", tracker="mutation-repository-create", ) def test_repo_list(self, mock_repositories_list) -> None: - result = runner.invoke(app, ["repository", "list", "--branch", "main"]) + result = runner.invoke(app, ["repository", "list"]) assert result.exit_code == 0 assert strip_color(result.stdout) == read_fixture("output.txt", "integration/test_infrahubctl/repository_list") From 1aa09f5b799e9e4035ee9d460996400b6e9177e4 Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Thu, 20 Feb 2025 08:40:55 +0100 Subject: [PATCH 11/12] Fix typing for Python 3.9 and remove support for Python 3.13 --- .github/workflows/ci.yml | 3 +-- changelog/251.fixed.md | 1 + infrahub_sdk/ctl/check.py | 6 +++--- infrahub_sdk/ctl/cli_commands.py | 18 +++++++++--------- infrahub_sdk/ctl/generator.py | 4 ++-- infrahub_sdk/ctl/importer.py | 3 ++- infrahub_sdk/ctl/repository.py | 5 +++-- infrahub_sdk/ctl/utils.py | 4 ++-- infrahub_sdk/ctl/validate.py | 3 ++- poetry.lock | 9 +++------ pyproject.toml | 7 +++++-- tests/unit/ctl/test_cli.py | 11 ----------- tests/unit/ctl/test_repository_app.py | 8 -------- tests/unit/ctl/test_validate_app.py | 7 ------- 14 files changed, 33 insertions(+), 56 deletions(-) create mode 100644 changelog/251.fixed.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d5651ad3..d0dc95fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -126,7 +126,6 @@ jobs: - "3.10" - "3.11" - "3.12" - - "3.13" if: | always() && !cancelled() && !contains(needs.*.result, 'failure') && @@ -144,7 +143,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: "Setup environment" run: | - pipx install poetry==1.8.5 + pipx install poetry==1.8.5 --python python${{ matrix.python-version }} poetry config virtualenvs.create true --local pip install invoke toml codecov - name: "Install Package" diff --git a/changelog/251.fixed.md b/changelog/251.fixed.md new file mode 100644 index 00000000..84e39163 --- /dev/null +++ b/changelog/251.fixed.md @@ -0,0 +1 @@ +Fix typing for Python 3.9 and remove support for Python 3.13 \ No newline at end of file diff --git a/infrahub_sdk/ctl/check.py b/infrahub_sdk/ctl/check.py index 3d5319dd..0626d884 100644 --- a/infrahub_sdk/ctl/check.py +++ b/infrahub_sdk/ctl/check.py @@ -5,7 +5,7 @@ from asyncio import run as aiorun from dataclasses import dataclass from pathlib import Path -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional import typer from rich.console import Console @@ -50,8 +50,8 @@ def run( format_json: bool, list_available: bool, variables: dict[str, str], - name: str | None = None, - branch: str | None = None, + name: Optional[str] = None, + branch: Optional[str] = None, ) -> None: """Locate and execute all checks under the defined path.""" diff --git a/infrahub_sdk/ctl/cli_commands.py b/infrahub_sdk/ctl/cli_commands.py index 19980eb2..7a10f136 100644 --- a/infrahub_sdk/ctl/cli_commands.py +++ b/infrahub_sdk/ctl/cli_commands.py @@ -7,7 +7,7 @@ import platform import sys from pathlib import Path -from typing import TYPE_CHECKING, Any, Callable +from typing import TYPE_CHECKING, Any, Callable, Optional import jinja2 import typer @@ -74,13 +74,13 @@ @catch_exception(console=console) def check( check_name: str = typer.Argument(default="", help="Name of the Python check"), - branch: str | None = None, + branch: Optional[str] = None, path: str = typer.Option(".", help="Root directory"), debug: bool = False, format_json: bool = False, _: str = CONFIG_PARAM, list_available: bool = typer.Option(False, "--list", help="Show available Python checks"), - variables: list[str] | None = typer.Argument( + variables: Optional[list[str]] = typer.Argument( None, help="Variables to pass along with the query. Format key=value key=value." ), ) -> None: @@ -102,12 +102,12 @@ def check( @catch_exception(console=console) async def generator( generator_name: str = typer.Argument(default="", help="Name of the Generator"), - branch: str | None = None, + branch: Optional[str] = None, path: str = typer.Option(".", help="Root directory"), debug: bool = False, _: str = CONFIG_PARAM, list_available: bool = typer.Option(False, "--list", help="Show available Generators"), - variables: list[str] | None = typer.Argument( + variables: Optional[list[str]] = typer.Argument( None, help="Variables to pass along with the query. Format key=value key=value." ), ) -> None: @@ -130,13 +130,13 @@ async def run( debug: bool = False, _: str = CONFIG_PARAM, branch: str = typer.Option(None, help="Branch on which to run the script."), - concurrent: int | None = typer.Option( + concurrent: Optional[int] = typer.Option( None, help="Maximum number of requests to execute at the same time.", envvar="INFRAHUB_MAX_CONCURRENT_EXECUTION", ), timeout: int = typer.Option(60, help="Timeout in sec", envvar="INFRAHUB_TIMEOUT"), - variables: list[str] | None = typer.Argument( + variables: Optional[list[str]] = typer.Argument( None, help="Variables to pass along with the query. Format key=value key=value." ), ) -> None: @@ -259,7 +259,7 @@ def _run_transform( @catch_exception(console=console) def render( transform_name: str = typer.Argument(default="", help="Name of the Python transformation", show_default=False), - variables: list[str] | None = typer.Argument( + variables: Optional[list[str]] = typer.Argument( None, help="Variables to pass along with the query. Format key=value key=value." ), branch: str = typer.Option(None, help="Branch on which to render the transform."), @@ -309,7 +309,7 @@ def render( @catch_exception(console=console) def transform( transform_name: str = typer.Argument(default="", help="Name of the Python transformation", show_default=False), - variables: list[str] | None = typer.Argument( + variables: Optional[list[str]] = typer.Argument( None, help="Variables to pass along with the query. Format key=value key=value." ), branch: str = typer.Option(None, help="Branch on which to run the transformation"), diff --git a/infrahub_sdk/ctl/generator.py b/infrahub_sdk/ctl/generator.py index 5258caa6..22501568 100644 --- a/infrahub_sdk/ctl/generator.py +++ b/infrahub_sdk/ctl/generator.py @@ -1,7 +1,7 @@ from __future__ import annotations from pathlib import Path -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional import typer from rich.console import Console @@ -23,7 +23,7 @@ async def run( debug: bool, list_available: bool, branch: str | None = None, - variables: list[str] | None = None, + variables: Optional[list[str]] = None, ) -> None: init_logging(debug=debug) repository_config = get_repository_config(Path(config.INFRAHUB_REPO_CONFIG_FILE)) diff --git a/infrahub_sdk/ctl/importer.py b/infrahub_sdk/ctl/importer.py index 4645fa06..e9181c8b 100644 --- a/infrahub_sdk/ctl/importer.py +++ b/infrahub_sdk/ctl/importer.py @@ -2,6 +2,7 @@ from asyncio import run as aiorun from pathlib import Path +from typing import Optional import typer from rich.console import Console @@ -26,7 +27,7 @@ def load( quiet: bool = typer.Option(False, help="No console output"), _: str = CONFIG_PARAM, branch: str = typer.Option(None, help="Branch from which to export"), - concurrent: int | None = typer.Option( + concurrent: Optional[int] = typer.Option( None, help="Maximum number of requests to execute at the same time.", envvar="INFRAHUB_MAX_CONCURRENT_EXECUTION", diff --git a/infrahub_sdk/ctl/repository.py b/infrahub_sdk/ctl/repository.py index 4e6d3535..1992a537 100644 --- a/infrahub_sdk/ctl/repository.py +++ b/infrahub_sdk/ctl/repository.py @@ -1,6 +1,7 @@ from __future__ import annotations from pathlib import Path +from typing import Optional import typer import yaml @@ -68,7 +69,7 @@ async def add( name: str, location: str, description: str = "", - username: str | None = None, + username: Optional[str] = None, password: str = "", ref: str = "", read_only: bool = False, @@ -114,7 +115,7 @@ async def add( @app.command() async def list( - branch: str = typer.Option(None, help="Branch on which to list repositories."), + branch: Optional[str] = typer.Option(None, help="Branch on which to list repositories."), debug: bool = False, _: str = CONFIG_PARAM, ) -> None: diff --git a/infrahub_sdk/ctl/utils.py b/infrahub_sdk/ctl/utils.py index e4474d8d..4c627119 100644 --- a/infrahub_sdk/ctl/utils.py +++ b/infrahub_sdk/ctl/utils.py @@ -6,7 +6,7 @@ from collections.abc import Coroutine from functools import wraps from pathlib import Path -from typing import TYPE_CHECKING, Any, Callable, NoReturn, TypeVar +from typing import TYPE_CHECKING, Any, Callable, NoReturn, Optional, TypeVar import pendulum import typer @@ -145,7 +145,7 @@ def print_graphql_errors(console: Console, errors: list) -> None: console.print(f"[red]{escape(str(error))}") -def parse_cli_vars(variables: list[str] | None) -> dict[str, str]: +def parse_cli_vars(variables: Optional[list[str]]) -> dict[str, str]: if not variables: return {} diff --git a/infrahub_sdk/ctl/validate.py b/infrahub_sdk/ctl/validate.py index d93260b2..99318239 100644 --- a/infrahub_sdk/ctl/validate.py +++ b/infrahub_sdk/ctl/validate.py @@ -2,6 +2,7 @@ import sys from pathlib import Path +from typing import Optional import typer import ujson @@ -57,7 +58,7 @@ async def validate_schema(schema: Path, _: str = CONFIG_PARAM) -> None: @catch_exception(console=console) def validate_graphql( query: str, - variables: list[str] | None = typer.Argument( + variables: Optional[list[str]] = typer.Argument( None, help="Variables to pass along with the query. Format key=value key=value." ), debug: bool = typer.Option(False, help="Display more troubleshooting information."), diff --git a/poetry.lock b/poetry.lock index fc23269b..3b60de6a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1301,10 +1301,7 @@ files = [ [package.dependencies] annotated-types = ">=0.6.0" pydantic-core = "2.23.3" -typing-extensions = [ - {version = ">=4.6.1", markers = "python_version < \"3.13\""}, - {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, -] +typing-extensions = {version = ">=4.6.1", markers = "python_version < \"3.13\""} [package.extras] email = ["email-validator (>=2.0.0)"] @@ -2237,5 +2234,5 @@ tests = ["Jinja2", "pytest", "pyyaml", "rich"] [metadata] lock-version = "2.0" -python-versions = "^3.9" -content-hash = "8df86b45a0478a859bbe784b5d9027b2f033da597a52815f6006fd81fd424fdf" +python-versions = "^3.9, < 3.13" +content-hash = "7cf3b9fd5e6ad627c30cb1660ef9c45d5b6a264150d064bc47cc7ae7a2be4030" diff --git a/pyproject.toml b/pyproject.toml index 7cc22f4b..9fbab36f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,11 +16,10 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", ] [tool.poetry.dependencies] -python = "^3.9" +python = "^3.9, < 3.13" pydantic = ">=2.0.0,!=2.0.1,!=2.1.0,<3.0.0" pydantic-settings = ">=2.0" graphql-core = ">=3.1,<3.3" @@ -248,6 +247,10 @@ max-complexity = 17 "ANN401", # Dynamically typed expressions (typing.Any) are disallowed ] +"infrahub_sdk/ctl/**/*.py" = [ + "UP007", # Use `X | Y` for type annotations | Required for Typer until we can drop the support for Python 3.9 +] + "infrahub_sdk/client.py" = [ ################################################################################################## # Review and change the below later # diff --git a/tests/unit/ctl/test_cli.py b/tests/unit/ctl/test_cli.py index d73a7105..30d63e92 100644 --- a/tests/unit/ctl/test_cli.py +++ b/tests/unit/ctl/test_cli.py @@ -1,16 +1,10 @@ -import sys - -import pytest from typer.testing import CliRunner from infrahub_sdk.ctl.cli import app runner = CliRunner() -requires_python_310 = pytest.mark.skipif(sys.version_info < (3, 10), reason="Requires Python 3.10 or higher") - -@requires_python_310 def test_main_app(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 @@ -29,14 +23,12 @@ def test_validate_all_groups_have_names(): assert group.name -@requires_python_310 def test_version_command(): result = runner.invoke(app, ["version"]) assert result.exit_code == 0 assert "Python SDK: v" in result.stdout -@requires_python_310 def test_info_command_success(mock_query_infrahub_version, mock_query_infrahub_user): result = runner.invoke(app, ["info"]) assert result.exit_code == 0 @@ -44,14 +36,12 @@ def test_info_command_success(mock_query_infrahub_version, mock_query_infrahub_u assert expected in result.stdout, f"'{expected}' not found in info command output" -@requires_python_310 def test_info_command_failure(): result = runner.invoke(app, ["info"]) assert result.exit_code == 0 assert "Connection Error" in result.stdout -@requires_python_310 def test_info_detail_command_success(mock_query_infrahub_version, mock_query_infrahub_user): result = runner.invoke(app, ["info", "--detail"]) assert result.exit_code == 0 @@ -65,7 +55,6 @@ def test_info_detail_command_success(mock_query_infrahub_version, mock_query_inf assert expected in result.stdout, f"'{expected}' not found in detailed info command output" -@requires_python_310 def test_info_detail_command_failure(): result = runner.invoke(app, ["info", "--detail"]) assert result.exit_code == 0 diff --git a/tests/unit/ctl/test_repository_app.py b/tests/unit/ctl/test_repository_app.py index 7fb01ee1..ba0a7721 100644 --- a/tests/unit/ctl/test_repository_app.py +++ b/tests/unit/ctl/test_repository_app.py @@ -1,6 +1,5 @@ """Integration tests for infrahubctl commands.""" -import sys from unittest import mock import pytest @@ -13,8 +12,6 @@ runner = CliRunner() -requires_python_310 = pytest.mark.skipif(sys.version_info < (3, 10), reason="Requires Python 3.10 or higher") - @pytest.fixture def mock_client() -> mock.Mock: @@ -29,7 +26,6 @@ def mock_client() -> mock.Mock: class TestInfrahubctlRepository: """Groups the 'infrahubctl repository' test cases.""" - @requires_python_310 @mock.patch("infrahub_sdk.ctl.repository.initialize_client") def test_repo_no_username_or_password(self, mock_init_client, mock_client) -> None: """Case allow no username to be passed in and set it as None rather than blank string that fails.""" @@ -77,7 +73,6 @@ def test_repo_no_username_or_password(self, mock_init_client, mock_client) -> No tracker="mutation-repository-create", ) - @requires_python_310 @mock.patch("infrahub_sdk.ctl.repository.initialize_client") def test_repo_no_username(self, mock_init_client, mock_client) -> None: """Case allow no username to be passed in and set it as None rather than blank string that fails.""" @@ -137,7 +132,6 @@ def test_repo_no_username(self, mock_init_client, mock_client) -> None: tracker="mutation-repository-create", ) - @requires_python_310 @mock.patch("infrahub_sdk.ctl.repository.initialize_client") def test_repo_username(self, mock_init_client, mock_client) -> None: """Case allow no username to be passed in and set it as None rather than blank string that fails.""" @@ -199,7 +193,6 @@ def test_repo_username(self, mock_init_client, mock_client) -> None: tracker="mutation-repository-create", ) - @requires_python_310 @mock.patch("infrahub_sdk.ctl.repository.initialize_client") def test_repo_readonly_true(self, mock_init_client, mock_client) -> None: """Case allow no username to be passed in and set it as None rather than blank string that fails.""" @@ -260,7 +253,6 @@ def test_repo_readonly_true(self, mock_init_client, mock_client) -> None: tracker="mutation-repository-create", ) - @requires_python_310 @mock.patch("infrahub_sdk.ctl.repository.initialize_client") def test_repo_description_commit_branch(self, mock_init_client, mock_client) -> None: """Case allow no username to be passed in and set it as None rather than blank string that fails.""" diff --git a/tests/unit/ctl/test_validate_app.py b/tests/unit/ctl/test_validate_app.py index 3264b087..43d41ddb 100644 --- a/tests/unit/ctl/test_validate_app.py +++ b/tests/unit/ctl/test_validate_app.py @@ -1,5 +1,3 @@ -import sys - import pytest from typer.testing import CliRunner @@ -9,10 +7,7 @@ runner = CliRunner() -requires_python_310 = pytest.mark.skipif(sys.version_info < (3, 10), reason="Requires Python 3.10 or higher") - -@requires_python_310 def test_validate_schema_valid(): fixture_file = get_fixtures_dir() / "models" / "valid_model_01.json" @@ -21,7 +16,6 @@ def test_validate_schema_valid(): assert "Schema is valid" in result.stdout -@requires_python_310 def test_validate_schema_empty(): fixture_file = get_fixtures_dir() / "models" / "empty.json" @@ -30,7 +24,6 @@ def test_validate_schema_empty(): assert "Empty YAML/JSON file" in remove_ansi_color(result.stdout) -@requires_python_310 def test_validate_schema_non_valid(): fixture_file = get_fixtures_dir() / "models" / "non_valid_model_01.json" From 4d03af64f696745d36255b35f2da95336be477af Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Sat, 22 Feb 2025 15:15:58 +0100 Subject: [PATCH 12/12] Disable test integration-tests-local-infrahub for now --- .github/workflows/ci.yml | 130 ++++++++++++++++++++------------------- 1 file changed, 66 insertions(+), 64 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d0dc95fb..cde9aa72 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -213,77 +213,79 @@ jobs: env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - integration-tests-local-infrahub: - if: | - always() && !cancelled() && - !contains(needs.*.result, 'failure') && - !contains(needs.*.result, 'cancelled') && - needs.files-changed.outputs.python == 'true' && - (github.base_ref == 'stable' || github.base_ref == 'develop') - needs: ["files-changed", "yaml-lint", "python-lint"] - runs-on: - group: "huge-runners" - timeout-minutes: 30 - steps: - - name: "Check out repository code" - uses: "actions/checkout@v4" + # NOTE: Disabling this test for now because it's expected that we can't start the latest version of infrahub + # with the current shipping version of infrahub-testcontainers + # integration-tests-local-infrahub: + # if: | + # always() && !cancelled() && + # !contains(needs.*.result, 'failure') && + # !contains(needs.*.result, 'cancelled') && + # needs.files-changed.outputs.python == 'true' && + # (github.base_ref == 'stable' || github.base_ref == 'develop') + # needs: ["files-changed", "yaml-lint", "python-lint"] + # runs-on: + # group: "huge-runners" + # timeout-minutes: 30 + # steps: + # - name: "Check out repository code" + # uses: "actions/checkout@v4" - - name: "Extract target branch name" - id: extract_branch - run: echo "TARGET_BRANCH=${{ github.base_ref }}" >> $GITHUB_ENV + # - name: "Extract target branch name" + # id: extract_branch + # run: echo "TARGET_BRANCH=${{ github.base_ref }}" >> $GITHUB_ENV - - name: "Checkout infrahub repository" - uses: "actions/checkout@v4" - with: - repository: "opsmill/infrahub" - path: "infrahub-server" - ref: ${{ github.base_ref }} - submodules: true + # - name: "Checkout infrahub repository" + # uses: "actions/checkout@v4" + # with: + # repository: "opsmill/infrahub" + # path: "infrahub-server" + # ref: ${{ github.base_ref }} + # submodules: true - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.12" + # - name: Set up Python + # uses: actions/setup-python@v5 + # with: + # python-version: "3.12" - - name: "Setup git credentials prior dev.build" - run: | - cd infrahub-server - git config --global user.name 'Infrahub' - git config --global user.email 'infrahub@opsmill.com' - git config --global --add safe.directory '*' - git config --global credential.usehttppath true - git config --global credential.helper /usr/local/bin/infrahub-git-credential + # - name: "Setup git credentials prior dev.build" + # run: | + # cd infrahub-server + # git config --global user.name 'Infrahub' + # git config --global user.email 'infrahub@opsmill.com' + # git config --global --add safe.directory '*' + # git config --global credential.usehttppath true + # git config --global credential.helper /usr/local/bin/infrahub-git-credential - - name: "Set environment variables prior dev.build" - run: | - echo "INFRAHUB_BUILD_NAME=infrahub-${{ runner.name }}" >> $GITHUB_ENV - 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 - echo "INFRAHUB_IMAGE_VER=local-${{ runner.name }}-${{ github.sha }}" >> $GITHUB_ENV - echo "INFRAHUB_TESTING_IMAGE_VER=local-${{ runner.name }}-${{ github.sha }}" >> $GITHUB_ENV - echo "INFRAHUB_TESTING_DOCKER_IMAGE=opsmill/infrahub" >> $GITHUB_ENV + # - name: "Set environment variables prior dev.build" + # run: | + # echo "INFRAHUB_BUILD_NAME=infrahub-${{ runner.name }}" >> $GITHUB_ENV + # 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 + # echo "INFRAHUB_IMAGE_VER=local-${{ runner.name }}-${{ github.sha }}" >> $GITHUB_ENV + # echo "INFRAHUB_TESTING_IMAGE_VER=local-${{ runner.name }}-${{ github.sha }}" >> $GITHUB_ENV + # echo "INFRAHUB_TESTING_DOCKER_IMAGE=opsmill/infrahub" >> $GITHUB_ENV - - name: "Build container" - run: | - cd infrahub-server - inv dev.build + # - name: "Build container" + # run: | + # cd infrahub-server + # inv dev.build - - name: "Setup environment" - run: | - pipx install poetry==1.8.5 - poetry config virtualenvs.create true --local - pip install invoke toml codecov + # - name: "Setup environment" + # run: | + # pipx install poetry==1.8.5 + # poetry config virtualenvs.create true --local + # pip install invoke toml codecov - - name: "Install Package" - run: "poetry install --all-extras" + # - name: "Install Package" + # run: "poetry install --all-extras" - - name: "Integration Tests" - run: | - echo "Running tests for version: $INFRAHUB_TESTING_IMAGE_VER" - poetry run pytest --cov infrahub_sdk tests/integration/ + # - name: "Integration Tests" + # run: | + # echo "Running tests for version: $INFRAHUB_TESTING_IMAGE_VER" + # poetry run pytest --cov infrahub_sdk tests/integration/ - - name: "Upload coverage to Codecov" - run: | - codecov --flags integration-tests - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + # - name: "Upload coverage to Codecov" + # run: | + # codecov --flags integration-tests + # env: + # CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}