Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 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
1d36d8d
rework
Apr 1, 2026
3c3f628
linter
Apr 1, 2026
8b314ea
sanitize
Apr 2, 2026
b74c893
coderabbit
Apr 2, 2026
9af2752
cubic
Apr 2, 2026
56e5a0c
filter by NodeSchemaAPI type before accessing .kind in schema list
petercrocker Apr 3, 2026
5554787
Merge pull request #900 from opsmill/001-end-user-cli
petercrocker Apr 3, 2026
2c82fa9
move specs/ to dev/specs/ and replace with symlink
petercrocker Apr 3, 2026
55788b2
remove specs symlink, update all references to dev/specs
petercrocker Apr 3, 2026
a9a6893
Merge pull request #915 from opsmill/fix/move-specs-to-dev-symlink
petercrocker Apr 3, 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
8 changes: 4 additions & 4 deletions .specify/scripts/bash/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ get_current_branch() {

# For non-git repos, try to find the latest feature directory
local repo_root=$(get_repo_root)
local specs_dir="$repo_root/specs"
local specs_dir="$repo_root/dev/specs"

if [[ -d "$specs_dir" ]]; then
local latest_feature=""
Expand Down Expand Up @@ -81,14 +81,14 @@ check_feature_branch() {
return 0
}

get_feature_dir() { echo "$1/specs/$2"; }
get_feature_dir() { echo "$1/dev/specs/$2"; }

# Find feature directory by numeric prefix instead of exact branch match
# This allows multiple branches to work on the same spec (e.g., 004-fix-bug, 004-add-feature)
find_feature_dir_by_prefix() {
local repo_root="$1"
local branch_name="$2"
local specs_dir="$repo_root/specs"
local specs_dir="$repo_root/dev/specs"

# Extract numeric prefix from branch (e.g., "004" from "004-whatever")
if [[ ! "$branch_name" =~ ^([0-9]{3})- ]]; then
Expand All @@ -99,7 +99,7 @@ find_feature_dir_by_prefix() {

local prefix="${BASH_REMATCH[1]}"

# Search for directories in specs/ that start with this prefix
# Search for directories in dev/specs/ that start with this prefix
local matches=()
if [[ -d "$specs_dir" ]]; then
for dir in "$specs_dir"/"$prefix"-*; do
Expand Down
4 changes: 2 additions & 2 deletions .specify/templates/plan-template.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Implementation Plan: [FEATURE]

**Branch**: `[###-feature-name]` | **Date**: [DATE] | **Spec**: [link]
**Input**: Feature specification from `/specs/[###-feature-name]/spec.md`
**Input**: Feature specification from `/dev/specs/[###-feature-name]/spec.md`

**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/templates/commands/plan.md` for the execution workflow.

Expand Down Expand Up @@ -38,7 +38,7 @@
### Documentation (this feature)

```text
specs/[###-feature]/
dev/specs/[###-feature]/
├── plan.md # This file (/speckit.plan command output)
├── research.md # Phase 0 output (/speckit.plan command)
├── data-model.md # Phase 1 output (/speckit.plan command)
Expand Down
2 changes: 1 addition & 1 deletion .specify/templates/tasks-template.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: "Task list template for feature implementation"

# Tasks: [FEATURE NAME]

**Input**: Design documents from `/specs/[###-feature-name]/`
**Input**: Design documents from `/dev/specs/[###-feature-name]/`
**Prerequisites**: plan.md (required), spec.md (required for user stories), research.md, data-model.md, contracts/

**Tests**: The examples below include test tasks. Tests are OPTIONAL - only include them if explicitly requested in the feature specification.
Expand Down
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
2 changes: 1 addition & 1 deletion dev/commands/speckit.specify.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Given that feature description, do this:
b. Find the highest feature number across all sources for the short-name:
- Remote branches: `git ls-remote --heads origin | grep -E 'refs/heads/[0-9]+-<short-name>$'`
- Local branches: `git branch | grep -E '^[* ]*[0-9]+-<short-name>$'`
- Specs directories: Check for directories matching `specs/[0-9]+-<short-name>`
- Specs directories: Check for directories matching `dev/specs/[0-9]+-<short-name>`

c. Determine the next available number:
- Extract all numbers from all three sources
Expand Down
36 changes: 36 additions & 0 deletions dev/specs/001-end-user-cli/checklists/requirements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Specification Quality Checklist: End-User CLI (`infrahubctl` command)

**Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: 2026-03-28
**Feature**: [spec.md](../spec.md)

## Content Quality

- [x] No implementation details (languages, frameworks, APIs)
- [x] Focused on user value and business needs
- [x] Written for non-technical stakeholders
- [x] All mandatory sections completed

## Requirement Completeness

- [x] No [NEEDS CLARIFICATION] markers remain
- [x] Requirements are testable and unambiguous
- [x] Success criteria are measurable
- [x] Success criteria are technology-agnostic (no implementation details)
- [x] All acceptance scenarios are defined
- [x] Edge cases are identified
- [x] Scope is clearly bounded
- [x] Dependencies and assumptions identified

## Feature Readiness

- [x] All functional requirements have clear acceptance criteria
- [x] User scenarios cover primary flows
- [x] Feature meets measurable outcomes defined in Success Criteria
- [x] No implementation details leak into specification

## Notes

- All items pass validation. Spec is ready for `/speckit.clarify` or `/speckit.plan`.
- Assumptions section documents reasonable defaults for unspecified details.
- CLI command examples in acceptance scenarios use generic syntax (not framework-specific).
64 changes: 64 additions & 0 deletions dev/specs/001-end-user-cli/contracts/cli-commands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# CLI Command Contracts

## Global Options

All commands accept:

- `--branch TEXT` — Target Infrahub branch (default: from config)
- `--config-file PATH` — Configuration file path (default: infrahubctl.toml)
- `--output [table|json|csv|yaml]` — Output format (default: table if TTY, json if piped)

## `infrahubctl get <kind> [identifier]`

**List mode** (no identifier):

- Input: kind (positional), --filter (repeatable), --limit INT, --offset INT
- Output: Table with columns for each attribute + relationship (display names)
- Exit 0: results found | Exit 80: no results (empty list) | Exit 1: invalid kind

**Detail mode** (with identifier):

- Input: kind (positional), identifier (positional — UUID or display name)
- Output: Key-value display of all attributes, relationships, metadata
- Exit 0: found | Exit 1: not found

**Filters**: `--filter name__value="spine01"` (repeatable)

## `infrahubctl create <kind>`

- Input: kind (positional), --set key=value (repeatable), --file PATH
- --set and --file are mutually exclusive
- Output: Confirmation with created object ID and display label
- Exit 0: created | Exit 1: validation error | Exit 1: server error

**File input**: JSON or YAML in Infrahub Object format
(`apiVersion: infrahub.app/v1`)

## `infrahubctl update <kind> <identifier>`

- Input: kind (positional), identifier (positional), --set key=value
(repeatable), --file PATH
- --set and --file are mutually exclusive
- Output: Confirmation with old → new values for changed fields
- Exit 0: updated | Exit 1: not found | Exit 1: validation error

## `infrahubctl delete <kind> <identifier>`

- Input: kind (positional), identifier (positional), --yes (skip confirmation)
- Output: Confirmation prompt (unless --yes), then success message
- Exit 0: deleted | Exit 1: not found | Exit 1: dependency conflict

## `infrahubctl schema list`

- Input: --filter TEXT (substring match on kind name)
- Output: Table with columns: Namespace, Name, Kind, Description
- Exit 0: always (empty table if no matches)

## `infrahubctl schema show <kind>`

- Input: kind (positional)
- Output: Formatted display of:
- Kind metadata (namespace, label, description, display_labels, HFID)
- Attributes table (name, type, required, default, description)
- Relationships table (name, peer kind, cardinality, optional)
- Exit 0: found | Exit 1: invalid kind
62 changes: 62 additions & 0 deletions dev/specs/001-end-user-cli/data-model.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Data Model: End-User CLI

This feature does not introduce new persistent data entities. It operates on
Infrahub's existing data model (Kinds, Nodes, Attributes, Relationships) via
the SDK client.

The CLI introduces transient structures for formatting and serialization:

## Output Format Envelope

Used when serializing query results to YAML output format.

**Fields**:

- `apiVersion` (str): Always `"infrahub.app/v1"`
- `kind` (str): Always `"Object"`
- `spec.kind` (str): The Infrahub Kind being exported (e.g., `"InfraDevice"`)
- `spec.data` (list[dict]): Array of serialized node objects

Each node in `spec.data` contains:

- Attribute fields as `key: value` pairs
- Relationship fields as `key: display_name` (single) or
`key: {data: [list]}` (many)

This structure matches the existing `InfrahubObjectFileData` model in
`infrahub_sdk/spec/object.py` and is round-trippable with `ObjectFile`.

## Set Flag Parser

Parses `--set key=value` arguments into a dict suitable for SDK calls.

**Input**: List of `"key=value"` strings from CLI
**Output**: `dict[str, str | list[str]]`

Validation rules:

- Key MUST exist as an attribute or relationship name in the target Kind's schema
- Value is a string; the SDK handles type coercion
- For relationships (cardinality ONE), value is the HFID or UUID of the target node (e.g., `--set site=DC1`)
- For relationships (cardinality MANY), value is a JSON array of HFID arrays (e.g., `--set tags=[["blue"], ["red"]]`). Each inner array is an HFID supporting multi-component keys (e.g., `[["Cisco", "NX-OS"]]`). The parser detects `[...]` and parses as JSON.

**Relationship resolution**: The CLI passes relationship values through to the
SDK as HFID references. The SDK/server is responsible for resolving HFIDs to
internal IDs. The CLI MUST NOT perform its own lookup round-trips.

**SDK dependencies**:

- [opsmill/infrahub-sdk-python#267](https://github.com/opsmill/infrahub-sdk-python/issues/267) — `rebuild_hfid_from_data()`: reconstruct HFID from flat user data based on schema definition
- [opsmill/infrahub-sdk-python#272](https://github.com/opsmill/infrahub-sdk-python/issues/272) — `node.update(data)`: update attributes and relationships from a dict (eliminates manual per-field mutation)

## Filter Parser

Parses `--filter key=value` arguments into kwargs for `client.filters()`.

**Input**: List of `"attribute__value=x"` strings from CLI
**Output**: `dict[str, Any]` passed as `**kwargs`

Validation rules:

- Key MUST follow the `attribute__value` or `relationship__id` pattern
- Invalid keys produce a validation error with available field names
Loading
Loading