Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
85502b4
feat: add end-user `infrahub` CLI for CRUD operations and schema disc…
petercrocker Mar 28, 2026
8fba90d
fix: resolve CI failures in markdown lint, integration tests, and docs
petercrocker Mar 28, 2026
94e3780
test: increase unit test coverage for end-user CLI
petercrocker Mar 28, 2026
e71acb1
fix: resolve remaining CI failures
petercrocker Mar 28, 2026
52c2b05
fix: resolve CI failures in integration tests, markdown lint, and docs
petercrocker Mar 28, 2026
a941e57
refactor: move end-user CLI commands under infrahubctl
petercrocker Mar 28, 2026
b8d30b4
fix: resolve doc generation and integration test failures
petercrocker Mar 28, 2026
4f465f9
fix: resolve vale spelling errors in generated CLI docs
petercrocker Mar 28, 2026
0c2ecc5
feat: hide empty columns by default in table and CSV output
petercrocker Mar 28, 2026
36332e3
feat: improve empty results UX for infrahubctl get
petercrocker Mar 28, 2026
7b7fb9f
feat: resolve nodes by UUID, default filter, or HFID
petercrocker Mar 28, 2026
c9119ea
fix: make YAML output round-trippable with infrahubctl object load
petercrocker Mar 28, 2026
ee6b73f
fix: clean error output for missing --set/--file arguments
petercrocker Mar 28, 2026
233ac0b
feat: resolve relationship values by name in create/update commands
petercrocker Mar 28, 2026
aca8c89
fix: show name instead of None in create confirmation message
petercrocker Mar 28, 2026
9f2813e
feat: distinguish create vs upsert in confirmation message
petercrocker Mar 28, 2026
0c730be
feat: skip save and show no-op message when update values unchanged
petercrocker Mar 28, 2026
db23a7d
docs: regenerate get command docs (--all-columns flag added)
petercrocker Mar 28, 2026
4633713
docs: add usage examples to all command help text
petercrocker Mar 28, 2026
0a3a408
fix: add 'yaml' to vale spelling exceptions
petercrocker Mar 28, 2026
4caff14
docs: clarify exit code 80 applies only to list mode, not detail lookups
petercrocker Mar 28, 2026
1b4b2bd
docs: document HFID support in identifier argument help text
petercrocker Mar 28, 2026
ff5c177
fix: compare relationship IDs not display strings for change detection
petercrocker Mar 28, 2026
419b13b
fix: warn that kind/identifier are ignored in update --file mode
petercrocker Mar 28, 2026
f1b73c0
fix: narrow exception handling in resolve_relationship_values
petercrocker Mar 28, 2026
93634c6
test: remove redundant @pytest.mark.anyio from resolve_node tests
petercrocker Mar 28, 2026
fa3688f
docs: fix contract to show exit code 80 for empty list results
petercrocker Mar 28, 2026
52f77ce
docs: update spec to reference infrahubctl instead of infrahub command
petercrocker Mar 28, 2026
432ba94
docs: update research.md to reflect infrahubctl integration decision
petercrocker Mar 28, 2026
2a8fb43
fix: preserve leading zeros in --set value coercion
petercrocker Mar 28, 2026
03714b7
test: add relationship no-op test for update command
petercrocker Mar 28, 2026
bc4bfb6
fix: ruff formatting for update.py console.print line
petercrocker Mar 28, 2026
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
1 change: 1 addition & 0 deletions .vale/styles/spelling-exceptions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ validators
Version Control
Vitest
VLANs
yaml
Yaml
yamllint
YouTube
Expand Down
32 changes: 32 additions & 0 deletions docs/docs/infrahubctl/infrahubctl-create.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# `infrahubctl create`

Create a new object in Infrahub.

Provide field values with repeatable --set flags or supply a
JSON/YAML object file via --file. The two modes are mutually exclusive.


Examples:
infrahubctl create InfraDevice --set name=spine01 --set status=active
infrahubctl create InfraDevice --set name=spine01 --set location=DC1
infrahubctl create InfraDevice --file devices.yml

**Usage**:

```console
$ infrahubctl create [OPTIONS] KIND
```

**Arguments**:

* `KIND`: Infrahub schema kind to create [required]

**Options**:

* `--set TEXT`: Field value in key=value format
* `-f, --file PATH`: JSON or YAML file with object data
* `-b, --branch TEXT`: Target branch
* `--config-file TEXT`: [env var: INFRAHUBCTL_CONFIG; default: infrahubctl.toml]
* `--install-completion`: Install completion for the current shell.
* `--show-completion`: Show completion for the current shell, to copy it or customize the installation.
* `--help`: Show this message and exit.
31 changes: 31 additions & 0 deletions docs/docs/infrahubctl/infrahubctl-delete.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# `infrahubctl delete`

Delete an Infrahub object.

Fetches the object by KIND and IDENTIFIER, then deletes it.
Unless --yes is provided, a confirmation prompt is shown first.


Examples:
infrahubctl delete InfraDevice spine01
infrahubctl delete InfraDevice spine01 --yes

**Usage**:

```console
$ infrahubctl delete [OPTIONS] KIND IDENTIFIER
```

**Arguments**:

* `KIND`: Infrahub schema kind [required]
* `IDENTIFIER`: UUID, name, or HFID (use / for multi-part, e.g. Cisco/NX-OS) [required]

**Options**:

* `-y, --yes`: Skip confirmation prompt
* `-b, --branch TEXT`: Target branch
* `--config-file TEXT`: [env var: INFRAHUBCTL_CONFIG; default: infrahubctl.toml]
* `--install-completion`: Install completion for the current shell.
* `--show-completion`: Show completion for the current shell, to copy it or customize the installation.
* `--help`: Show this message and exit.
42 changes: 42 additions & 0 deletions docs/docs/infrahubctl/infrahubctl-get.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# `infrahubctl get`

Query and display Infrahub objects.

When IDENTIFIER is omitted the command lists all objects of the given
KIND. When IDENTIFIER is provided it displays a single object in
detail view. Empty columns are hidden by default (use --all-columns).


Examples:
infrahubctl get InfraDevice
infrahubctl get InfraDevice spine01
infrahubctl get InfraDevice --filter name__value=spine01
infrahubctl get InfraDevice --output json
infrahubctl get InfraDevice --output yaml > backup.yml

Exit codes: 0 = results found, 1 = error (including not found in detail
mode), 80 = list query succeeded but returned zero objects.

**Usage**:

```console
$ infrahubctl get [OPTIONS] KIND [IDENTIFIER]
```

**Arguments**:

* `KIND`: Infrahub schema kind to query [required]
* `[IDENTIFIER]`: UUID, name, or HFID (use / for multi-part, e.g. Cisco/NX-OS)

**Options**:

* `--filter TEXT`: Filter in attr__value=x format
* `-o, --output [table|json|csv|yaml]`: Output format
* `-b, --branch TEXT`: Target branch
* `--limit INTEGER`: Maximum results
* `--offset INTEGER`: Skip first N results
* `--all-columns`: Show all columns including empty ones
* `--config-file TEXT`: [env var: INFRAHUBCTL_CONFIG; default: infrahubctl.toml]
* `--install-completion`: Install completion for the current shell.
* `--show-completion`: Show completion for the current shell, to copy it or customize the installation.
* `--help`: Show this message and exit.
54 changes: 54 additions & 0 deletions docs/docs/infrahubctl/infrahubctl-schema.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ $ infrahubctl schema [OPTIONS] COMMAND [ARGS]...

* `check`: Check if schema files are valid and what...
* `export`: Export the schema from Infrahub as YAML...
* `list`: List all available schema kinds.
* `load`: Load one or multiple schema files into...
* `show`: Show details for a specific schema kind.

## `infrahubctl schema check`

Expand Down Expand Up @@ -60,6 +62,31 @@ $ infrahubctl schema export [OPTIONS]
* `--config-file TEXT`: [env var: INFRAHUBCTL_CONFIG; default: infrahubctl.toml]
* `--help`: Show this message and exit.

## `infrahubctl schema list`

List all available schema kinds.

Displays a table of all node schema entries. Use --filter to narrow
results by a case-insensitive match on the kind name.


Examples:
infrahubctl schema list
infrahubctl schema list --filter Device

**Usage**:

```console
$ infrahubctl schema list [OPTIONS]
```

**Options**:

* `--filter TEXT`: Filter kinds by name
* `-b, --branch TEXT`: Target branch
* `--config-file TEXT`: [env var: INFRAHUBCTL_CONFIG; default: infrahubctl.toml]
* `--help`: Show this message and exit.

## `infrahubctl schema load`

Load one or multiple schema files into Infrahub.
Expand All @@ -81,3 +108,30 @@ $ infrahubctl schema load [OPTIONS] SCHEMAS...
* `--wait INTEGER`: Time in seconds to wait until the schema has converged across all workers [default: 0]
* `--config-file TEXT`: [env var: INFRAHUBCTL_CONFIG; default: infrahubctl.toml]
* `--help`: Show this message and exit.

## `infrahubctl schema show`

Show details for a specific schema kind.

Displays metadata, attributes, and relationships for the requested
schema kind in a human-readable format.


Examples:
infrahubctl schema show InfraDevice

**Usage**:

```console
$ infrahubctl schema show [OPTIONS] KIND
```

**Arguments**:

* `KIND`: Schema kind to display [required]

**Options**:

* `-b, --branch TEXT`: Target branch
* `--config-file TEXT`: [env var: INFRAHUBCTL_CONFIG; default: infrahubctl.toml]
* `--help`: Show this message and exit.
33 changes: 33 additions & 0 deletions docs/docs/infrahubctl/infrahubctl-update.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# `infrahubctl update`

Update an existing object in Infrahub.

Fetches the object by KIND and IDENTIFIER, applies the requested
changes, and saves back to the server. Use --set or --file.


Examples:
Comment on lines +1 to +9
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add required frontmatter and remove the stray control character.

This MDX page is missing a title frontmatter block, and there is a non-printable character before the examples section that should be removed.

🛠️ Proposed fix
+---
+title: infrahubctl update
+---
+
 # `infrahubctl update`
@@
-
+
 Examples:

As per coding guidelines: docs/**/{python-sdk,infrahubctl}/**/*.mdx must include frontmatter with title, and **/*.{md,mdx} changes should be markdownlint-clean.

📝 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
# `infrahubctl update`
Update an existing object in Infrahub.
Fetches the object by KIND and IDENTIFIER, applies the requested
changes, and saves back to the server. Use --set or --file.
Examples:
---
title: infrahubctl update
---
# `infrahubctl update`
Update an existing object in Infrahub.
Fetches the object by KIND and IDENTIFIER, applies the requested
changes, and saves back to the server. Use --set or --file.
Examples:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/docs/infrahubctl/infrahubctl-update.mdx` around lines 1 - 9, Add a YAML
frontmatter block with a title (e.g., title: "infrahubctl update") at the very
top of the MDX page and remove the stray non-printable/control character that
appears before the "Examples:" line; ensure the resulting file begins with the
frontmatter block followed by the heading and that the file passes markdownlint
rules for docs/**/{python-sdk,infrahubctl}/**/*.mdx.

infrahubctl update InfraDevice spine01 --set status=active
infrahubctl update InfraDevice spine01 --set location=DC1
infrahubctl update InfraDevice spine01 --file updates.yml

**Usage**:

```console
$ infrahubctl update [OPTIONS] KIND IDENTIFIER
```

**Arguments**:

* `KIND`: Infrahub schema kind [required]
* `IDENTIFIER`: UUID, name, or HFID (use / for multi-part, e.g. Cisco/NX-OS) [required]

**Options**:

* `--set TEXT`: Field value in key=value format
* `-f, --file PATH`: JSON or YAML file with update data
* `-b, --branch TEXT`: Target branch
* `--config-file TEXT`: [env var: INFRAHUBCTL_CONFIG; default: infrahubctl.toml]
* `--install-completion`: Install completion for the current shell.
* `--show-completion`: Show completion for the current shell, to copy it or customize the installation.
* `--help`: Show this message and exit.
31 changes: 23 additions & 8 deletions infrahub_sdk/ctl/cli_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@
from pathlib import Path
from typing import TYPE_CHECKING, Any

import typer
import ujson
from rich.console import Console
from rich.layout import Layout
from rich.logging import RichHandler
from rich.panel import Panel
from rich.pretty import Pretty
from rich.table import Table
import typer # pyright: ignore[reportMissingImports]
import ujson # pyright: ignore[reportMissingModuleSource]
from rich.console import Console # pyright: ignore[reportMissingImports]
from rich.layout import Layout # pyright: ignore[reportMissingImports]
from rich.logging import RichHandler # pyright: ignore[reportMissingImports]
from rich.panel import Panel # pyright: ignore[reportMissingImports]
from rich.pretty import Pretty # pyright: ignore[reportMissingImports]
from rich.table import Table # pyright: ignore[reportMissingImports]

from .. import __version__ as sdk_version
from ..async_typer import AsyncTyper
Expand Down Expand Up @@ -51,6 +51,10 @@
from ..template.exceptions import JinjaTemplateError
from ..utils import write_to_file
from ..yaml import SchemaFile
from .commands.create import create_command
from .commands.delete import delete_command
from .commands.get import get_command
from .commands.update import update_command
from .exporter import dump
from .importer import load
from .parameters import CONFIG_PARAM
Expand All @@ -71,6 +75,17 @@

app.command(name="dump")(dump)
app.command(name="load")(load)
app.command(name="get")(get_command)
app.command(name="create")(create_command)
app.command(name="update")(update_command)
app.command(name="delete")(delete_command)

# Expose command functions under their command names for typer doc generation
# (typer --func <name> looks up module-level names)
get = get_command
create = create_command
update = update_command
delete = delete_command

console = Console()

Expand Down
3 changes: 3 additions & 0 deletions infrahub_sdk/ctl/commands/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Command modules for the ``infrahub`` end-user CLI."""

from __future__ import annotations
81 changes: 81 additions & 0 deletions infrahub_sdk/ctl/commands/create.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"""Command implementation for ``infrahub create``.

Creates a new object in Infrahub either from inline ``--set`` key=value
arguments or from a JSON/YAML object file specified via ``--file``.
"""

from __future__ import annotations

import contextlib
from pathlib import Path

import typer # pyright: ignore[reportMissingImports]
from rich.console import Console # pyright: ignore[reportMissingImports]

from infrahub_sdk.ctl.client import initialize_client
from infrahub_sdk.ctl.commands.utils import resolve_node, resolve_relationship_values
from infrahub_sdk.ctl.parameters import CONFIG_PARAM
from infrahub_sdk.ctl.parsers import parse_set_args, validate_set_fields
from infrahub_sdk.ctl.utils import catch_exception
from infrahub_sdk.spec.object import ObjectFile

console = Console()


@catch_exception(console=console)
async def create_command(
kind: str = typer.Argument(..., help="Infrahub schema kind to create"),
set_args: list[str] | None = typer.Option(None, "--set", help="Field value in key=value format"),
file: Path | None = typer.Option(None, "--file", "-f", help="JSON or YAML file with object data"),
branch: str | None = typer.Option(None, "--branch", "-b", help="Target branch"),
_: str = CONFIG_PARAM,
Comment on lines +27 to +31
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Validate --file against the positional kind.

kind is required by the CLI but ignored in this branch, so create Foo --file bar.yml will still process whatever obj_file.spec.kind declares. That makes target-kind typos invisible and can create the wrong objects.

🛠️ Suggested fix
     if file:
         files = ObjectFile.load_from_disk(paths=[file])
         for obj_file in files:
+            if obj_file.spec.kind != kind:
+                console.print(
+                    f"[red]Error: --file contains kind '{obj_file.spec.kind}', expected '{kind}'."
+                )
+                raise typer.Exit(code=1)
             await obj_file.validate_format(client=client, branch=branch)
             await obj_file.process(client=client, branch=branch)
             object_count = len(obj_file.spec.data)
             console.print(f"[green]Created {object_count} objects of kind {obj_file.spec.kind}")

Also applies to: 54-60

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrahub_sdk/ctl/commands/create.py` around lines 27 - 31, Validate that when
a file is provided the file's declared kind matches the required positional
kind: in the command handler that receives the typer Argument "kind" and Option
"file", after loading the file (the object parsed into obj_file or similar with
obj_file.spec.kind), compare obj_file.spec.kind to the positional "kind" and
raise a user-facing error (typer.Exit or typer.BadParameter) if they differ;
apply the same validation in the other create code path referenced around the
other handler block (the code handling the --file branch at the second
occurrence) so a mismatched kind (e.g., create Foo --file bar.yml where bar.yml
declares a different kind) is rejected rather than silently honored.

) -> None:
"""Create a new object in Infrahub.

Provide field values with repeatable --set flags or supply a
JSON/YAML object file via --file. The two modes are mutually exclusive.

\b
Examples:
infrahubctl create InfraDevice --set name=spine01 --set status=active
infrahubctl create InfraDevice --set name=spine01 --set location=DC1
infrahubctl create InfraDevice --file devices.yml
"""
if set_args and file:
console.print("[red]Error: --set and --file are mutually exclusive.")
raise typer.Exit(code=1)
if not set_args and not file:
console.print("[red]Error: provide --set key=value or --file <path>.")
console.print("Example: infrahubctl create MyKind --set name=foo --set status=active")
raise typer.Exit(code=1)

client = initialize_client(branch=branch)

if file:
files = ObjectFile.load_from_disk(paths=[file])
for obj_file in files:
await obj_file.validate_format(client=client, branch=branch)
await obj_file.process(client=client, branch=branch)
object_count = len(obj_file.spec.data)
console.print(f"[green]Created {object_count} objects of kind {obj_file.spec.kind}")
else:
data = parse_set_args(set_args) # type: ignore[arg-type]
schema = await client.schema.get(kind=kind, branch=branch)
validate_set_fields(data, schema.attribute_names, schema.relationship_names)
data = await resolve_relationship_values(client, data, schema, branch=branch)

# Check if node already exists to distinguish create from upsert
existing = None
name_value = data.get("name")
if name_value is not None:
with contextlib.suppress(Exception):
existing = await resolve_node(client, kind, str(name_value), schema=schema, branch=branch)

node = await client.create(kind=kind, data=data, branch=branch)
await node.save(allow_upsert=True)
label = node.display_label or name_value or node.id

if existing:
console.print(f"[yellow]Updated {kind} '{label}' (id: {node.id}) — already existed")
else:
console.print(f"[green]Created {kind} '{label}' (id: {node.id})")
Loading