Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
91a828a
Merge pull request #443 from opsmill/stable
ogenstad Jun 12, 2025
544b49a
Merge pull request #449 from opsmill/stable
dgarros Jun 30, 2025
4717ffc
Infrahub repostory init (#467)
minitriga Jul 16, 2025
8e357b9
Finalize typing on ctl.schema
ogenstad Jun 20, 2025
f82744b
Merge pull request #458 from opsmill/stable
ogenstad Jul 28, 2025
199532f
Merge pull request #451 from opsmill/pog-ctl-schema-typing-20250620
ogenstad Jul 30, 2025
0cde3c6
Merge stable into develop (#486)
gmazoyer Aug 7, 2025
3269a41
Merge pull request #495 from opsmill/stable
ajtmccarty Aug 13, 2025
8273796
Use correct case for Infrahub
ogenstad Aug 15, 2025
90802de
Send in the correct and expected types
ogenstad Aug 15, 2025
8f1ab44
Merge pull request #498 from opsmill/pog-infrahub-case
ogenstad Aug 15, 2025
7905287
Merge pull request #499 from opsmill/pog-correct-parameter-types
ogenstad Aug 15, 2025
a93d013
Bump actions/checkout from 4 to 5
dependabot[bot] Aug 18, 2025
545956a
Merge pull request #501 from opsmill/dependabot/github_actions/develo…
ogenstad Aug 19, 2025
988b1d9
Update packages in local lock file
ogenstad Aug 21, 2025
076d477
Remove unnecessary call (rewrite as a literal)
ogenstad Aug 22, 2025
8310a3e
Merge pull request #505 from opsmill/pog-408
dgarros Aug 24, 2025
309e530
Merge pull request #503 from opsmill/pog-package-updates
dgarros Aug 24, 2025
b24a04d
Merge branch 'stable' into 'develop' with resolved conflicts
ogenstad Aug 25, 2025
c1a1fb0
Merge pull request #506 from opsmill/pog-stable-to-develop-20250825
ogenstad Aug 25, 2025
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
24 changes: 12 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:
github_workflows: ${{ steps.changes.outputs.github_workflows }}
steps:
- name: "Check out repository code"
uses: "actions/checkout@v4"
uses: "actions/checkout@v5"
- name: Check for file changes
uses: dorny/paths-filter@v3
id: changes
Expand All @@ -63,7 +63,7 @@ jobs:
timeout-minutes: 5
steps:
- name: "Check out repository code"
uses: "actions/checkout@v4"
uses: "actions/checkout@v5"
- name: "Setup environment"
run: "pip install yamllint==1.35.1"
- name: "Linting: yamllint"
Expand All @@ -76,7 +76,7 @@ jobs:
timeout-minutes: 5
steps:
- name: "Check out repository code"
uses: "actions/checkout@v4"
uses: "actions/checkout@v5"
- name: "Setup environment"
run: "pip install ruff==0.11.0"
- name: "Linting: ruff check"
Expand All @@ -94,7 +94,7 @@ jobs:
timeout-minutes: 5
steps:
- name: "Check out repository code"
uses: "actions/checkout@v4"
uses: "actions/checkout@v5"
- name: "Linting: markdownlint"
uses: DavidAnson/markdownlint-cli2-action@v20
with:
Expand All @@ -110,7 +110,7 @@ jobs:
timeout-minutes: 5
steps:
- name: "Check out repository code"
uses: "actions/checkout@v4"
uses: "actions/checkout@v5"
- name: Check workflow files
run: |
bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)
Expand All @@ -134,7 +134,7 @@ jobs:
timeout-minutes: 5
steps:
- name: "Check out repository code"
uses: "actions/checkout@v4"
uses: "actions/checkout@v5"
with:
submodules: true
- name: Install NodeJS
Expand All @@ -161,7 +161,7 @@ jobs:
timeout-minutes: 5
steps:
- name: "Check out repository code"
uses: "actions/checkout@v4"
uses: "actions/checkout@v5"
with:
submodules: true
- name: Set up Python
Expand Down Expand Up @@ -190,7 +190,7 @@ jobs:
timeout-minutes: 5
steps:
- name: "Check out repository code"
uses: "actions/checkout@v4"
uses: "actions/checkout@v5"
with:
submodules: true

Expand Down Expand Up @@ -227,7 +227,7 @@ jobs:
timeout-minutes: 30
steps:
- name: "Check out repository code"
uses: "actions/checkout@v4"
uses: "actions/checkout@v5"
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
Expand Down Expand Up @@ -276,7 +276,7 @@ jobs:
timeout-minutes: 30
steps:
- name: "Check out repository code"
uses: "actions/checkout@v4"
uses: "actions/checkout@v5"
- name: Set up Python
uses: actions/setup-python@v5
with:
Expand Down Expand Up @@ -316,14 +316,14 @@ jobs:
# timeout-minutes: 30
# steps:
# - name: "Check out repository code"
# uses: "actions/checkout@v4"
# uses: "actions/checkout@v5"

# - 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"
# uses: "actions/checkout@v5"
# with:
# repository: "opsmill/infrahub"
# path: "infrahub-server"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/labels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
-
name: Run Labeler
uses: crazy-max/ghaction-github-labeler@v5
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:
installer-parallel: true

- name: "Check out repository code"
uses: "actions/checkout@v4"
uses: "actions/checkout@v5"
with:
submodules: true

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
latest_tag: ${{ steps.release.outputs.latest_tag }}
steps:
- name: "Check out repository code"
uses: "actions/checkout@v4"
uses: "actions/checkout@v5"
with:
submodules: true

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/repository-dispatch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:

steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5

- name: Repository Dispatch
uses: peter-evans/repository-dispatch@v3
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/sync-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout source repository
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
path: source-repo

- name: Checkout target repository
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
repository: opsmill/infrahub-docs
token: ${{ secrets.PAT_TOKEN }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/update-submodule.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5

- name: Trigger submodule update
run: |
Expand Down
1 change: 1 addition & 0 deletions changelog/466.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added `infrahubctl repository init` command to allow the initialization of an Infrahub repository using [infrahub-template](https://github.com/opsmill/infrahub-template).
24 changes: 24 additions & 0 deletions docs/docs/infrahubctl/infrahubctl-repository.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -47,6 +48,29 @@ $ 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] DIRECTORY
```

**Arguments**:

* `DIRECTORY`: Directory path for the new project. [required]

**Options**:

* `--template TEXT`: Template to use for the new repository. Can be a local path or a git repository URL. [default: https://github.com/opsmill/infrahub-template.git]
* `--data PATH`: Path to YAML file containing answers to CLI prompt.
* `--vcs-ref TEXT`: VCS reference to use for the template. Defaults to HEAD. [default: HEAD]
* `--trust / --no-trust`: Trust the template repository. If set, the template will be cloned without verification. [default: no-trust]
* `--config-file TEXT`: [env var: INFRAHUBCTL_CONFIG; default: infrahubctl.toml]
* `--help`: Show this message and exit.

Comment on lines +51 to +73
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix grammar/style in the new repository init section; slight wording polish.

Several bullets read as fragments and trip style checks. Tightening phrasing improves clarity.

Apply the following diff within this block:

 ## `infrahubctl repository init`

 Initialize a new Infrahub repository.

 **Usage**:

 ```console
 $ infrahubctl repository init [OPTIONS] DIRECTORY

Arguments:

-* DIRECTORY: Directory path for the new project. [required]
+* DIRECTORY: Directory path for the new project. [required]

Options:

-* --template TEXT: Template to use for the new repository. Can be a local path or a git repository URL. [default: https://github.com/opsmill/infrahub-template.git]
-* --data PATH: Path to YAML file containing answers to CLI prompt.
+* --template TEXT: Template to use for the new repository; can be a local path or a Git repository URL. [default: https://github.com/opsmill/infrahub-template.git]
+* --data PATH: Path to a YAML file containing answers to the CLI prompts.

  • --vcs-ref TEXT: VCS reference to use for the template. Defaults to HEAD. [default: HEAD]
    -* --trust / --no-trust: Trust the template repository. If set, the template will be cloned without verification. [default: no-trust]
    +* --trust / --no-trust: Trust the template repository. If enabled, the template will be used without additional verification. [default: no-trust]
  • --config-file TEXT: [env var: INFRAHUBCTL_CONFIG; default: infrahubctl.toml]
  • --help: Show this message and exit.

Optional: add a short Validation subsection here showing expected files (e.g., `.copier-answers.yml`, `pyproject.toml`) after a successful run.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 LanguageTool</summary>

[style] ~67-~67: To form a complete sentence, be sure to include a subject or ‘there’.
Context: ...Template to use for the new repository. Can be a local path or a git repository URL...

(MISSING_IT_THERE)

---

[grammar] ~68-~68: There might be a mistake here.
Context: ...L file containing answers to CLI prompt. * `--vcs-ref TEXT`: VCS reference to use for the template....

(QB_NEW_EN)

---

[grammar] ~70-~70: There might be a mistake here.
Context: ...thout verification.  [default: no-trust] * `--config-file TEXT`: [env var: INFRAHUBCTL_CONFIG; default:...

(QB_NEW_EN)

---

[grammar] ~71-~71: There might be a mistake here.
Context: ...UBCTL_CONFIG; default: infrahubctl.toml] * `--help`: Show this message and exit.  ## `infra...

(QB_NEW_EN)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

In docs/docs/infrahubctl/infrahubctl-repository.mdx around lines 51 to 73,
polish wording and fix grammar in the infrahubctl repository init section:
change the --template description to "Template to use for the new repository;
can be a local path or a Git repository URL.", change --data to "Path to a
YAML file containing answers to the CLI prompts.", and update --trust / --no-trust to "Trust the template repository. If enabled, the template will be
used without additional verification." Keep the rest of the block intact and
optionally add a short "Validation" subsection listing expected files like
.copier-answers.yml and pyproject.toml after a successful run.


</details>

<!-- fingerprinting:phantom:poseidon:chinchilla -->

<!-- This is an auto-generated comment by CodeRabbit -->

## `infrahubctl repository list`

**Usage**:
Expand Down
51 changes: 51 additions & 0 deletions infrahub_sdk/ctl/repository.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from __future__ import annotations

import asyncio
from pathlib import Path
from typing import Optional

import typer
import yaml
from copier import run_copy
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid hard dependency on optional package "copier" (move import inside the command).

copier is added as an optional dependency in this PR. Importing it at module import time will break all repository subcommands (e.g., add/list) for users who installed the base SDK without extras. Defer the import to the init command and emit a helpful message if missing.

Apply this diff:

-from copier import run_copy

And see the follow-up diff in the init() block that adds a lazy import.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In infrahub_sdk/ctl/repository.py around line 9, the top-level import "from
copier import run_copy" creates a hard dependency on the optional "copier"
package; move this import into the init() command so other repository
subcommands don't fail when "copier" isn't installed. Edit init() to perform a
lazy import using try/except ImportError, and if ImportError is raised raise a
clear click/CLI error (or print a helpful message) instructing the user to
install the optional extras (e.g., infrahub-sdk[copier]) or to pip install
copier; ensure no other module-level references to run_copy remain so other
commands work without copier.

from pydantic import ValidationError
from rich.console import Console
from rich.table import Table
Expand Down Expand Up @@ -165,3 +167,52 @@ async def list(
)

console.print(table)


@app.command()
async def init(
directory: Path = typer.Argument(help="Directory path for the new project."),
template: str = typer.Option(
default="https://github.com/opsmill/infrahub-template.git",
help="Template to use for the new repository. Can be a local path or a git repository URL.",
),
data: Optional[Path] = typer.Option(default=None, help="Path to YAML file containing answers to CLI prompt."),
vcs_ref: Optional[str] = typer.Option(
default="HEAD",
help="VCS reference to use for the template. Defaults to HEAD.",
),
trust: Optional[bool] = typer.Option(
default=False,
help="Trust the template repository. If set, the template will be cloned without verification.",
),
_: str = CONFIG_PARAM,
) -> None:
"""Initialize a new Infrahub repository."""

config_data = None
if data:
try:
with Path.open(data, encoding="utf-8") as file:
config_data = yaml.safe_load(file)
typer.echo(f"Loaded config: {config_data}")
except Exception as exc:
typer.echo(f"Error loading YAML file: {exc}", err=True)
raise typer.Exit(code=1)

Comment on lines +192 to +201
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

YAML handling: avoid leaking secrets; validate structure; use idiomatic Path.open.

  • Printing the fully loaded config can leak credentials or tokens from answers files.
  • Ensure the YAML decodes into a mapping (dict) as expected by copier.
  • Prefer data.open() over Path.open(data, ...) for clarity.

Apply this diff:

-    config_data = None
-    if data:
-        try:
-            with Path.open(data, encoding="utf-8") as file:
-                config_data = yaml.safe_load(file)
-            typer.echo(f"Loaded config: {config_data}")
-        except Exception as exc:
-            typer.echo(f"Error loading YAML file: {exc}", err=True)
-            raise typer.Exit(code=1)
+    config_data = None
+    if data:
+        try:
+            with data.open("r", encoding="utf-8") as file:
+                config_data = yaml.safe_load(file) or {}
+            if not isinstance(config_data, dict):
+                raise typer.BadParameter("Expected a mapping in the YAML answers file (key: value).")
+        except (FileNotFoundError, yaml.YAMLError, typer.BadParameter) as exc:
+            typer.echo(f"Invalid YAML file: {exc}", err=True)
+            raise typer.Exit(code=1)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
config_data = None
if data:
try:
with Path.open(data, encoding="utf-8") as file:
config_data = yaml.safe_load(file)
typer.echo(f"Loaded config: {config_data}")
except Exception as exc:
typer.echo(f"Error loading YAML file: {exc}", err=True)
raise typer.Exit(code=1)
config_data = None
if data:
try:
with data.open("r", encoding="utf-8") as file:
config_data = yaml.safe_load(file) or {}
if not isinstance(config_data, dict):
raise typer.BadParameter("Expected a mapping in the YAML answers file (key: value).")
except (FileNotFoundError, yaml.YAMLError, typer.BadParameter) as exc:
typer.echo(f"Invalid YAML file: {exc}", err=True)
raise typer.Exit(code=1)
🤖 Prompt for AI Agents
In infrahub_sdk/ctl/repository.py around lines 192 to 201, avoid printing the
entire loaded YAML (which may leak secrets), use the idiomatic Path.open() call
on the Path object (data.open(...)), and validate that yaml.safe_load returns a
mapping: open the file with data.open(encoding="utf-8"), call yaml.safe_load,
check isinstance(config_data, dict) and raise typer.Exit(1) with a concise error
if not a mapping, and remove or replace the typer.echo(f"Loaded config:
{config_data}") with a non-sensitive acknowledgment (e.g., a single
informational message without contents) while keeping the existing exception
handling to log the error and exit.

# Allow template to be a local path or a URL
template_source = template or ""
if template and Path(template).exists():
template_source = str(Path(template).resolve())

try:
await asyncio.to_thread(
run_copy,
template_source,
str(directory),
data=config_data,
vcs_ref=vcs_ref,
unsafe=trust,
)
except Exception as e:
Comment on lines +208 to +216
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Lazy import copier and handle copier-specific errors; keep the event loop responsive.

Perform a local import with a clear guidance if copier is missing, and handle its specific exceptions for cleaner UX.

Apply this diff:

-    try:
-        await asyncio.to_thread(
-            run_copy,
-            template_source,
-            str(directory),
-            data=config_data,
-            vcs_ref=vcs_ref,
-            unsafe=trust,
-        )
-    except Exception as e:
-        typer.echo(f"Error running copier: {e}", err=True)
-        raise typer.Exit(code=1)
+    # Lazy import to keep 'copier' optional for users not using 'repository init'
+    try:
+        from copier import run_copy  # type: ignore
+        try:
+            from copier.errors import UserMessageError as CopierUserMessageError  # type: ignore
+        except Exception:  # older copier
+            CopierUserMessageError = Exception  # type: ignore
+    except Exception:
+        typer.echo(
+            "The 'copier' package is required for 'infrahubctl repository init'. "
+            "Install with: pip install 'infrahub-sdk[ctl]' or pip install copier",
+            err=True,
+        )
+        raise typer.Exit(code=1)
+
+    try:
+        await asyncio.to_thread(
+            run_copy,
+            template_source,
+            str(directory),
+            data=config_data,
+            vcs_ref=vcs_ref,
+            unsafe=trust,
+        )
+    except CopierUserMessageError as exc:
+        typer.echo(f"Template error: {exc}", err=True)
+        raise typer.Exit(code=1)
+    except Exception as exc:
+        typer.echo(f"Error running copier: {exc}", err=True)
+        raise typer.Exit(code=1)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await asyncio.to_thread(
run_copy,
template_source,
str(directory),
data=config_data,
vcs_ref=vcs_ref,
unsafe=trust,
)
except Exception as e:
# Lazy import to keep 'copier' optional for users not using 'repository init'
try:
from copier import run_copy # type: ignore
try:
from copier.errors import UserMessageError as CopierUserMessageError # type: ignore
except Exception: # older copier versions
CopierUserMessageError = Exception # type: ignore
except Exception:
typer.echo(
"The 'copier' package is required for 'infrahubctl repository init'. "
"Install with: pip install 'infrahub-sdk[ctl]' or pip install copier",
err=True,
)
raise typer.Exit(code=1)
try:
await asyncio.to_thread(
run_copy,
template_source,
str(directory),
data=config_data,
vcs_ref=vcs_ref,
unsafe=trust,
)
except CopierUserMessageError as exc:
typer.echo(f"Template error: {exc}", err=True)
raise typer.Exit(code=1)
except Exception as exc:
typer.echo(f"Error running copier: {exc}", err=True)
raise typer.Exit(code=1)
🤖 Prompt for AI Agents
In infrahub_sdk/ctl/repository.py around lines 208 to 216, do a local import of
copier right before calling run_copy so we fail fast with a clear message if
copier is not installed, and catch copier-specific exceptions to provide a
cleaner error to the user: add "try: import copier except ModuleNotFoundError:
raise RuntimeError('copier is required for this command; please install package
\"copier\"')", then wrap the asyncio.to_thread call in a try/except that catches
copier.CopierException (or copier.CopierError / the public base exception from
copier) and re-raises or raises a RuntimeError with the copier error details,
leaving other exceptions unchanged so the event loop remains responsive.

typer.echo(f"Error running copier: {e}", err=True)
raise typer.Exit(code=1)
18 changes: 9 additions & 9 deletions infrahub_sdk/ctl/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def validate_schema_content_and_exit(client: InfrahubClient, schemas: list[Schem
has_error: bool = False
for schema_file in schemas:
try:
client.schema.validate(data=schema_file.content)
client.schema.validate(data=schema_file.payload)
except ValidationError as exc:
console.print(f"[red]Schema not valid, found '{len(exc.errors())}' error(s) in {schema_file.location}")
has_error = True
Expand All @@ -48,7 +48,7 @@ def validate_schema_content_and_exit(client: InfrahubClient, schemas: list[Schem
raise typer.Exit(1)


def display_schema_load_errors(response: dict[str, Any], schemas_data: list[dict]) -> None:
def display_schema_load_errors(response: dict[str, Any], schemas_data: list[SchemaFile]) -> None:
console.print("[red]Unable to load the schema:")
if "detail" not in response:
handle_non_detail_errors(response=response)
Expand Down Expand Up @@ -87,7 +87,7 @@ def handle_non_detail_errors(response: dict[str, Any]) -> None:
if "error" in response:
console.print(f" {response.get('error')}")
elif "errors" in response:
for error in response.get("errors"):
for error in response["errors"]:
console.print(f" {error.get('message')}")
else:
console.print(f" '{response}'")
Expand All @@ -97,9 +97,9 @@ def valid_error_path(loc_path: list[Any]) -> bool:
return len(loc_path) >= 6 and loc_path[0] == "body" and loc_path[1] == "schemas"


def get_node(schemas_data: list[dict], schema_index: int, node_index: int) -> dict | None:
if schema_index < len(schemas_data) and node_index < len(schemas_data[schema_index].content["nodes"]):
return schemas_data[schema_index].content["nodes"][node_index]
def get_node(schemas_data: list[SchemaFile], schema_index: int, node_index: int) -> dict | None:
if schema_index < len(schemas_data) and node_index < len(schemas_data[schema_index].payload["nodes"]):
return schemas_data[schema_index].payload["nodes"][node_index]
return None
Comment on lines +100 to 103
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Guard against missing nodes key and out-of-range indices.

Using payload["nodes"] can raise a KeyError for malformed payloads and your bounds check doesn't cover that. Prefer .get("nodes", []).

Apply this diff:

-def get_node(schemas_data: list[SchemaFile], schema_index: int, node_index: int) -> dict | None:
-    if schema_index < len(schemas_data) and node_index < len(schemas_data[schema_index].payload["nodes"]):
-        return schemas_data[schema_index].payload["nodes"][node_index]
-    return None
+def get_node(schemas_data: list[SchemaFile], schema_index: int, node_index: int) -> dict | None:
+    if schema_index >= len(schemas_data):
+        return None
+    payload = schemas_data[schema_index].payload or {}
+    nodes = payload.get("nodes", [])
+    if isinstance(nodes, list) and 0 <= node_index < len(nodes):
+        return nodes[node_index]
+    return None
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def get_node(schemas_data: list[SchemaFile], schema_index: int, node_index: int) -> dict | None:
if schema_index < len(schemas_data) and node_index < len(schemas_data[schema_index].payload["nodes"]):
return schemas_data[schema_index].payload["nodes"][node_index]
return None
def get_node(schemas_data: list[SchemaFile], schema_index: int, node_index: int) -> dict | None:
if schema_index >= len(schemas_data):
return None
payload = schemas_data[schema_index].payload or {}
nodes = payload.get("nodes", [])
if isinstance(nodes, list) and 0 <= node_index < len(nodes):
return nodes[node_index]
return None
🤖 Prompt for AI Agents
In infrahub_sdk/ctl/schema.py around lines 100 to 103, the function accesses
payload["nodes"] which can raise KeyError for malformed payloads and the current
length checks don't guard against missing or non-list nodes; update the function
to use payload.get("nodes", []) (or retrieve and validate nodes into a local
variable), verify nodes is a sequence, and then check indices against its length
before returning nodes[node_index], otherwise return None.



Expand All @@ -122,7 +122,7 @@ async def load(
validate_schema_content_and_exit(client=client, schemas=schemas_data)

start_time = time.time()
response = await client.schema.load(schemas=[item.content for item in schemas_data], branch=branch)
response = await client.schema.load(schemas=[item.payload for item in schemas_data], branch=branch)
loading_time = time.time() - start_time

if response.errors:
Expand Down Expand Up @@ -170,10 +170,10 @@ async def check(
client = initialize_client()
validate_schema_content_and_exit(client=client, schemas=schemas_data)

success, response = await client.schema.check(schemas=[item.content for item in schemas_data], branch=branch)
success, response = await client.schema.check(schemas=[item.payload for item in schemas_data], branch=branch)

if not success:
display_schema_load_errors(response=response, schemas_data=schemas_data)
display_schema_load_errors(response=response or {}, schemas_data=schemas_data)
else:
for schema_file in schemas_data:
console.print(f"[green] schema '{schema_file.location}' is Valid!")
Expand Down
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 @@ -16,7 +16,7 @@

class InfrahubGraphQLQueryItem(InfrahubItem):
def validate_resource_config(self) -> None:
# Resource name does not need to match against infrahub repo config
# Resource name does not need to match against Infrahub repository configuration
return

def execute_query(self) -> Any:
Expand Down
2 changes: 1 addition & 1 deletion infrahub_sdk/schema/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@


class InfrahubRepositoryConfigElement(BaseModel):
"""Class to regroup all elements of the infrahub configuration for a repository for typing purpose."""
"""Class to regroup all elements of the Infrahub configuration for a repository for typing purpose."""


class InfrahubRepositoryArtifactDefinitionConfig(InfrahubRepositoryConfigElement):
Expand Down
2 changes: 1 addition & 1 deletion infrahub_sdk/testing/docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

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.
Check if a test should be skipped depending on Infrahub version.
"""
if INFRAHUB_VERSION is None:
return True
Expand Down
4 changes: 2 additions & 2 deletions infrahub_sdk/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def decode_json(response: httpx.Response) -> dict:
try:
return response.json()
except json.decoder.JSONDecodeError as exc:
raise JsonDecodeError(content=response.text, url=response.url) from exc
raise JsonDecodeError(content=response.text, url=str(response.url)) from exc


def generate_uuid() -> str:
Expand Down Expand Up @@ -232,7 +232,7 @@ def get_branch(branch: str | None = None, directory: str | Path = ".") -> str:
if branch:
return branch

repo = GitRepoManager(directory)
repo = GitRepoManager(root_directory=str(directory))
return str(repo.active_branch)


Expand Down
Loading