Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ jobs:


unit-tests:
env:
# workaround for Rich table column width
COLUMNS: 140
strategy:
matrix:
python-version:
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ This project uses [*towncrier*](https://towncrier.readthedocs.io/) and the chang

<!-- towncrier release notes start -->

## [1.7.0](https://github.com/opsmill/infrahub-sdk-python/tree/v1.7.0) - 2025-01-23

### Added

- adds `infrahubctl repository list` command

## [1.6.1](https://github.com/opsmill/infrahub-sdk-python/tree/v1.6.1) - 2025-01-16

Fixes release of v1.6.0
Expand Down
1 change: 1 addition & 0 deletions changelog/+align-infrahubctl-env-vars.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Aligned the environment variables used by the `infrahubctl` with the environment variables used by the SDK
53 changes: 53 additions & 0 deletions changelog/towncrier.md.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{% if render_title %}
{% if versiondata.name %}
# {{ versiondata.name }} {{ versiondata.version }} ({{ versiondata.date }})
{% else %}
# {{ versiondata.version }} ({{ versiondata.date }})
{% endif %}
{% endif %}
{% for section, _ in sections.items() %}
{% if section %}

## {{section}}
{% endif %}
{% if sections[section] %}
{% for category, val in definitions.items() if category in sections[section] %}
### {{ definitions[category]['name'] }}

{% for text, values in sections[section][category].items() %}
- {{ text }}
{%- if values %}
{% if "\n - " in text or '\n * ' in text %}


(
{%- else %}
{% if text %} ({% endif %}
{%- endif -%}
{%- for issue in values %}
{{ issue.split(": ", 1)[0] }}{% if not loop.last %}, {% endif %}
{%- endfor %}
{% if text %}){% endif %}

{% else %}

{% endif %}
{% endfor %}

{% if issues_by_category[section][category] and "]: " in issues_by_category[section][category][0] %}
{% for issue in issues_by_category[section][category] %}
{{ issue }}
{% endfor %}

{% endif %}
{% if sections[section][category]|length == 0 %}
No significant changes.

{% else %}
{% endif %}
{% endfor %}
{% else %}
No significant changes.

{% endif %}
{% endfor +%}
8 changes: 4 additions & 4 deletions infrahub_sdk/ctl/cli_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,12 @@ async def run(
debug: bool = False,
_: str = CONFIG_PARAM,
branch: str = typer.Option("main", help="Branch on which to run the script."),
concurrent: int = typer.Option(
4,
concurrent: int | None = typer.Option(
None,
help="Maximum number of requests to execute at the same time.",
envvar="INFRAHUBCTL_CONCURRENT_EXECUTION",
envvar="INFRAHUB_MAX_CONCURRENT_EXECUTION",
),
timeout: int = typer.Option(60, help="Timeout in sec", envvar="INFRAHUBCTL_TIMEOUT"),
timeout: int = typer.Option(60, help="Timeout in sec", envvar="INFRAHUB_TIMEOUT"),
variables: list[str] | None = typer.Argument(
None, help="Variables to pass along with the query. Format key=value key=value."
),
Expand Down
4 changes: 2 additions & 2 deletions infrahub_sdk/ctl/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ def dump(
concurrent: int = typer.Option(
4,
help="Maximum number of requests to execute at the same time.",
envvar="INFRAHUBCTL_CONCURRENT_EXECUTION",
envvar="INFRAHUB_MAX_CONCURRENT_EXECUTION",
),
timeout: int = typer.Option(60, help="Timeout in sec", envvar="INFRAHUBCTL_TIMEOUT"),
timeout: int = typer.Option(60, help="Timeout in sec", envvar="INFRAHUB_TIMEOUT"),
exclude: list[str] = typer.Option(
["CoreAccount"],
help="Prevent node kind(s) from being exported, CoreAccount is excluded by default",
Expand Down
10 changes: 6 additions & 4 deletions infrahub_sdk/ctl/importer.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from asyncio import run as aiorun
from pathlib import Path

Expand All @@ -24,12 +26,12 @@ 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"),
concurrent: int = typer.Option(
4,
concurrent: int | None = typer.Option(
None,
help="Maximum number of requests to execute at the same time.",
envvar="INFRAHUBCTL_CONCURRENT_EXECUTION",
envvar="INFRAHUB_MAX_CONCURRENT_EXECUTION",
),
timeout: int = typer.Option(60, help="Timeout in sec", envvar="INFRAHUBCTL_TIMEOUT"),
timeout: int = typer.Option(60, help="Timeout in sec", envvar="INFRAHUB_TIMEOUT"),
) -> None:
"""Import nodes and their relationships into the database."""
console = Console()
Expand Down
57 changes: 56 additions & 1 deletion infrahub_sdk/ctl/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
import yaml
from pydantic import ValidationError
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 ..graphql import Mutation
from ..graphql import Mutation, Query
from ..schema.repository import InfrahubRepositoryConfig
from ._file import read_file
from .parameters import CONFIG_PARAM
Expand Down Expand Up @@ -102,3 +103,57 @@ async def add(
)

await client.execute_graphql(query=query.render(), branch_name=branch, tracker="mutation-repository-create")


@app.command()
async def list(
branch: str | None = None,
debug: bool = False,
_: str = CONFIG_PARAM,
) -> None:
init_logging(debug=debug)

client = initialize_client(branch=branch)

repo_status_query = {
"CoreGenericRepository": {
"edges": {
"node": {
"__typename": None,
"name": {"value": None},
"operational_status": {"value": None},
"sync_status": {"value": None},
"internal_status": {"value": None},
"... on CoreReadOnlyRepository": {
"ref": {"value": None},
},
}
}
},
}

query = Query(name="GetRepositoryStatus", query=repo_status_query)
resp = await client.execute_graphql(query=query.render(), branch_name=branch, tracker="query-repository-list")

table = Table(title="List of all Repositories")

table.add_column("Name", justify="right", style="cyan", no_wrap=True)
table.add_column("Type")
table.add_column("Operational status")
table.add_column("Sync status")
table.add_column("Internal status")
table.add_column("Ref")

for repository_node in resp["CoreGenericRepository"]["edges"]:
repository = repository_node["node"]

table.add_row(
repository["name"]["value"],
repository["__typename"],
repository["operational_status"]["value"],
repository["sync_status"]["value"],
repository["internal_status"]["value"],
repository["ref"]["value"] if "ref" in repository else "",
)

console.print(table)
2 changes: 1 addition & 1 deletion infrahub_sdk/pytest_plugin/items/graphql_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def repr_failure(self, excinfo: ExceptionInfo, style: str | None = None) -> str:
)

if isinstance(excinfo.value, OutputMatchError):
return "\n".join([excinfo.value.message, excinfo.value.differences])
return f"{excinfo.value.message}\n{excinfo.value.differences}"

return super().repr_failure(excinfo, style=style)

Expand Down
2 changes: 1 addition & 1 deletion infrahub_sdk/pytest_plugin/items/jinja2_transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def repr_failure(self, excinfo: ExceptionInfo, style: str | None = None) -> str:
return "\n".join(["Syntax error detected in the template", excinfo.value.message or ""])

if isinstance(excinfo.value, OutputMatchError):
return "\n".join([excinfo.value.message, excinfo.value.differences])
return f"{excinfo.value.message}\n{excinfo.value.differences}"

return super().repr_failure(excinfo, style=style)

Expand Down
2 changes: 1 addition & 1 deletion infrahub_sdk/pytest_plugin/items/python_transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def repr_failure(self, excinfo: ExceptionInfo, style: str | None = None) -> str:
)

if isinstance(excinfo.value, OutputMatchError):
return "\n".join([excinfo.value.message, excinfo.value.differences])
return f"{excinfo.value.message}\n{excinfo.value.differences}"

return super().repr_failure(excinfo, style=style)

Expand Down
8 changes: 4 additions & 4 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 7 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ requires-python = ">=3.9"

[tool.poetry]
name = "infrahub-sdk"
version = "1.6.1"
version = "1.7.0"
description = "Python Client to interact with Infrahub"
authors = ["OpsMill <[email protected]>"]
readme = "README.md"
Expand Down Expand Up @@ -222,7 +222,6 @@ ignore = [
"TID", # flake8-tidy-imports
"FBT", # flake8-boolean-trap
"G", # flake8-logging-format
"FLY", # flynt
"RSE", # flake8-raise
"BLE", # flake8-blind-except (BLE)
"A", # flake8-builtins
Expand Down Expand Up @@ -345,6 +344,7 @@ underlines = ["", "", ""]
title_format = "## [{version}](https://github.com/opsmill/infrahub-sdk-python/tree/v{version}) - {project_date}"
issue_format = "[#{issue}](https://github.com/opsmill/infrahub-sdk-python/issues/{issue})"
orphan_prefix = "+"
template = "changelog/towncrier.md.template"

[[tool.towncrier.type]]
directory = "security"
Expand Down Expand Up @@ -376,6 +376,11 @@ directory = "fixed"
name = "Fixed"
showcontent = true

[[tool.towncrier.type]]
directory = "housekeeping"
name = "Housekeeping"
showcontent = true

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
List of all Repositories
┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Name ┃ Type ┃ Operational status ┃ Sync status ┃ Internal status ┃ Ref ┃
┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ Demo Edge Repo │ CoreReadOnlyRepository │ unknown │ in-sync │ active │ 5bffc938ba0d00dd111cb19331cdef6aab3729c2 │
│ My Own Repo │ CoreRepository │ in-sync │ in-sync │ active │ │
└────────────────┴────────────────────────┴────────────────────┴─────────────┴─────────────────┴──────────────────────────────────────────┘
9 changes: 9 additions & 0 deletions tests/helpers/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,12 @@ def get_fixtures_dir() -> Path:
"""Get the directory which stores fixtures that are common to multiple unit/integration tests."""
here = Path(__file__).parent.resolve()
return here.parent / "fixtures"


def read_fixture(file_name: str, fixture_subdir: str = ".") -> str:
"""Read the contents of a fixture."""
file_path = get_fixtures_dir() / fixture_subdir / file_name
with file_path.open("r", encoding="utf-8") as fhd:
fixture_contents = fhd.read()

return fixture_contents
40 changes: 40 additions & 0 deletions tests/unit/ctl/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,43 @@ async def mock_repositories_query(httpx_mock: HTTPXMock) -> HTTPXMock:
httpx_mock.add_response(method="POST", url="http://mock/graphql/main", json=response1)
httpx_mock.add_response(method="POST", url="http://mock/graphql/cr1234", json=response2)
return httpx_mock


@pytest.fixture
def mock_repositories_list(httpx_mock: HTTPXMock) -> HTTPXMock:
response = {
"data": {
"CoreGenericRepository": {
"edges": [
{
"node": {
"__typename": "CoreReadOnlyRepository",
"name": {"value": "Demo Edge Repo"},
"operational_status": {"value": "unknown"},
"sync_status": {"value": "in-sync"},
"internal_status": {"value": "active"},
"ref": {"value": "5bffc938ba0d00dd111cb19331cdef6aab3729c2"},
}
},
{
"node": {
"__typename": "CoreRepository",
"name": {"value": "My Own Repo"},
"operational_status": {"value": "in-sync"},
"sync_status": {"value": "in-sync"},
"internal_status": {"value": "active"},
}
},
]
}
}
}

httpx_mock.add_response(
method="POST",
status_code=200,
url="http://mock/graphql/main",
json=response,
match_headers={"X-Infrahub-Tracker": "query-repository-list"},
)
return httpx_mock
Loading
Loading