Skip to content

About

Modern NetBox toolkit with an SDK, CLI and TUI (terminal UI) for faster automation.

Topics

Resources

Stars

18 stars

Watchers

0 watching

Forks

Latest commit

 

History

547 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

netbox-sdk

SDK-first NetBox integration package for Python automation, terminal workflows, and Textual UIs.

netbox-sdk is an SDK-first NetBox toolkit with four public surfaces built on one shared runtime:

  • netbox_cli — Typer command-line interface
  • netbox_tui — Textual terminal applications
  • netbox_mcp — schema-driven Model Context Protocol server
  • netbox_sdk — standalone REST API SDK shared by the other three surfaces

Published package name: netbox-sdk. netbox-console was a legacy alias published in earlier releases and is no longer shipped from this project.

Integration Package Details

netbox-sdk is an integration package, not a NetBox plugin. It is not installed into NetBox with PLUGINS, does not add Django models or views, and does not need a plugin config name. Its certification evidence therefore focuses on the same quality criteria that apply to ecosystem packages: open-source licensing, package metadata, API compatibility, tests, documentation, support channels, and release maintainability.

Area Evidence
License Apache-2.0 in LICENSE.txt and pyproject.toml package metadata
Package netbox-sdk on PyPI, with netbox_sdk, netbox_cli, netbox_tui, and netbox_mcp import packages
Python Python 3.11, 3.12, and 3.13
NetBox API compatibility Stable typed clients for NetBox 4.7, 4.6, 4.5, 4.4, and 4.3; the default is the official NetBox v4.7.0 GA schema; live CI against v4.7.0, v4.6.6, v4.6.3, v4.6.2, and v4.5.10
Tests Mock API suite, live NetBox suite, security tests, type checks, package metadata checks, and strict docs builds in GitHub Actions
Support GitHub issues for bugs/features/docs requests; docs at https://emersonfelipesp.github.io/netbox-sdk/

Quick Start with the Demo Instance

Install:

pip install 'netbox-sdk[all]'

Authenticate against the public demo instance:

nbx demo init

Try a few commands:

nbx demo dcim devices list
nbx demo ipam prefixes list
nbx demo tui
nbx demo dev tui

Install

The source candidate documented on the site matches docs/snippets/package-version.txt (aligned with pyproject.toml). The latest final release available from the default PyPI index is tracked separately in docs/snippets/published-package-version.txt. Omit a pin for the latest published final, or use that published-version value for a reproducible install.

Minimal SDK only:

pip install netbox-sdk

CLI:

pip install 'netbox-sdk[cli]'

TUI:

pip install 'netbox-sdk[tui]'

MCP server:

pip install 'netbox-sdk[mcp]'

OpenTelemetry tracing:

pip install 'netbox-sdk[otel]'

Everything:

pip install 'netbox-sdk[all]'

Pinned to the latest final release on the default package index:

pip install 'netbox-sdk[all]==0.0.13'

With uv as a user tool:

uv tool install --force 'netbox-sdk[cli]'

Developer checkout:

git clone https://github.com/emersonfelipesp/netbox-sdk.git
cd netbox-sdk
uv sync --dev --extra cli --extra tui --extra demo --extra mcp
uv run nbx --help

Common Commands

# Basic CRUD
nbx init
nbx dcim devices list
nbx dcim devices get --id 1
nbx dcim devices create --body-json '{"name":"sw01","site":1}' --confirm
nbx dcim devices patch --id 1 --body-json '{"status":"active"}' --confirm
nbx dcim devices delete --id 1 --confirm

# NetBox version selection
# Default command discovery uses the bundled 4.7 GA schema; execution detects configured instances.
nbx dcim cable-bundles list --help
nbx --netbox-version 4.5 dcim devices list
NETBOX_SDK_NETBOX_VERSION=4.5 nbx resources dcim

# Auto-pagination — fetch every page in one call
nbx dcim devices list --all
nbx dcim devices list --all --max-records 500

# Filtering
nbx dcim devices list -q status=active -q site=nyc01
nbx dcim devices list -q tag=prod -q tag=edge

# Discover available filter parameters (no HTTP call)
nbx dcim devices filters

# HTTP headers for ETag / conditional update workflows
nbx dcim devices patch --id 1 -H 'If-Match: "etag-value"' --body-json '{"status":"active"}' --confirm
nbx call PATCH /api/dcim/devices/1/ -H 'If-Match: "etag-value"' --body-json '{"status":"active"}' --dry-run
nbx call PATCH /api/dcim/devices/1/ -H 'If-Match: "etag-value"' --body-json '{"status":"active"}' --confirm
nbx dev http patch --path /api/dcim/devices/ --id 1 --body-json '{"status":"active"}' --confirm

# NetBox Branching writes use the same confirmation gate
nbx branching create --name feature-x --confirm
nbx branching sync 7 --confirm

# Bulk operations (array body to list path)
nbx extras tags bulk-patch --body-json '[{"id":1,"color":"aa1409"},{"id":2,"color":"0c7a00"}]' --confirm
nbx extras tags bulk-update --body-json '[{"id":1,"name":"tag-a","slug":"tag-a","color":"ff0000"}]' --confirm
nbx extras tags bulk-delete --body-json '[{"id":1},{"id":2}]' --confirm

# Proxbox plugin catalog, CRUD, TUI, and sync jobs
nbx proxbox resources
nbx proxbox ops firewall/rules
nbx proxbox endpoints proxmox list -q name=pve-prod
nbx proxbox firewall rules patch --id 7 --dry-run --body-json '{"enabled":false}'
nbx proxbox tui --theme
nbx proxbox tui --theme dracula --confirm
nbx proxbox sync --confirm
nbx proxbox sync pve-prod -t virtual-machines -t storage --confirm
nbx proxbox sync-types

# Official netbox-rpc catalog and execution lifecycle
nbx rpc procedures available --target-type dcim.device
nbx rpc executions create --body-json '{"procedure_id":1,"assigned_object_type":"dcim.device","assigned_object_id":42,"params":{}}' --confirm
nbx rpc executions wait --id 100 --json
nbx rpc executions events --id 100 --json
nbx rpc executions cancel --id 100 --confirm

# Official netbox-openbao catalog, credential lifecycle, and administration
nbx openbao resources
nbx openbao credentials create --body-json '{"name":"router","generate_ssh_key":true,"ssh_key_type":"ed25519"}' --confirm
nbx openbao actions credential-reveal --id 12 --method POST --body-json '{"reason":"maintenance"}' --confirm --json
nbx openbao actions cluster-auth-method --id 3 --path-param mount_path=userpass
nbx openbao snapshot download --id 3 --output cluster.snap --confirm
nbx openbao snapshot upload --id 3 --input cluster.snap --reason "disaster recovery" --confirmation "RESTORE SNAPSHOT primary" --openbao-cluster-id raft-cluster-a --raft-index 17 --confirm

# TUI and developer tools
nbx tui
nbx dev tui
nbx cli tui
nbx logs

The maintained contract, complete collection/action matrix, SDK examples, and automation safety rules are documented in the dedicated RPC CLI and RPC SDK guides. The official OpenBao contract and its material-handling rules are documented in the OpenBao CLI and OpenBao SDK guides.

MCP Server and Agent Safety

Install the mcp extra and run the server over stdio (the default):

pip install 'netbox-sdk[mcp]'
nbx-mcp

Streamable HTTP is also available at /mcp:

NETBOX_MCP_AUTH_TOKEN="$NETBOX_MCP_AUTH_TOKEN" nbx-mcp --transport streamable-http --host 127.0.0.1 --port 8000

Every Streamable HTTP bind requires a shared-secret bearer token via --auth-token or NETBOX_MCP_AUTH_TOKEN; the server refuses to start without one, including on loopback hosts (127.0.0.1/localhost/::1). Binding to loopback only restricts reachability to this machine — it does not authenticate other local processes or users, who could otherwise reach the server's loaded NetBox credential (and any active --allow-mutations window) unauthenticated. Prefer NETBOX_MCP_AUTH_TOKEN over --auth-token on any shared host: a CLI argument is visible to other local users through ps and /proc/<pid>/cmdline, while the environment variable is not:

NETBOX_MCP_AUTH_TOKEN="$NETBOX_MCP_AUTH_TOKEN" nbx-mcp --transport streamable-http --host 0.0.0.0

Every request must then send Authorization: Bearer <token>; requests without it receive 401.

The server exposes a narrow schema-driven tool set for introspection, reads, mutations, plugin discovery, and guarded raw calls. It reads the existing netbox_sdk.config profile for stdio credentials and accepts an optional per-call bearer token. Live mutations are denied by default; enable them only for a reviewed execution window with NETBOX_MCP_ALLOW_MUTATIONS=1 or --allow-mutations. A mutation dry_run=true only resolves the local request and does not validate it against NetBox. NETBOX_SDK_NETBOX_VERSION pins the same bundled release line for nbx-mcp that it pins for nbx; without a pin, live MCP discovery uses the shared connected-instance resolution policy.

NetBox plugins can also advertise semantic operations through a versioned manifest under their existing REST API root. The stable plugin_list_tools and plugin_call_tool MCP tools discover and invoke those operations through the same NetBoxApiClient; plugin DRF permissions remain authoritative and no parallel credential or MCP server is created. Plugin-tool dry-runs perform only the live GETs required for manifest discovery and never dispatch the advertised write endpoint. Bridge version 1 is a generic descriptor protocol; each plugin owns its tool-payload snapshot. Validation rejects date-time normalization overflow and floating-point integers above the lossless JSON safe range so a rounded number cannot select another object identity. See the plugin bridge contract.

Agents can inspect the same JSON capability contract through the CLI:

nbx groups --json
nbx resources dcim --json
nbx ops dcim devices --json
nbx capabilities --json

The nbx process itself refuses every dynamic action resolving to a write method (including raw POST/PUT/PATCH/DELETE action spellings), write-method nbx call and nbx dev http requests, mutating nbx branching/nbx branch verbs, Proxbox CRUD, and Proxbox sync scheduling unless the invocation includes --confirm or its environment contains NETBOX_SDK_CONFIRM_WRITE=1. Dry runs remain available without confirmation. Repository-local Claude Code and Codex hooks provide an earlier defense-in-depth denial for recognizable Bash commands, but arbitrary decoded/generated shell input is ultimately enforced by the CLI gate. The mirrored netbox-sdk-operations Skill in .claude/skills/ and .codex/skills/ documents introspect → preview → execute → verify.

See Agent Client Setup for the step-by-step guide to wiring nbx-mcp, the hooks, and the Skill into Claude Code and Codex CLI.

Architecture

  • netbox_sdk owns config, auth, caching, the release-line registry, shared bundled/live schema resolution, request resolution, the versioned plugin bridge, shared formatting, and demo helpers.
  • netbox_cli owns the nbx command tree and lazy-loads netbox_tui where needed.
  • netbox_tui owns all Textual apps, themes, widgets, and TCSS.
  • netbox_mcp owns the stable validated MCP tools, including plugin bridge discovery/invocation, and imports only netbox_sdk.

Runtime Dependencies

Base SDK installs depend on aiohttp, pydantic, jsonschema, email-validator, rich, and pyyaml. Optional extras add the terminal surfaces and local test tools:

  • cli: Typer-powered nbx command tree
  • tui: Textual terminal applications
  • mcp: official Python MCP SDK and the nbx-mcp server
  • mock: FastAPI/uvicorn mock NetBox API for integration tests
  • demo: Playwright-powered demo setup automation
  • branching: semantic marker for NetBox Branching workflows; no extra runtime dependency is required today
  • otel: OpenTelemetry API, SDK, and OTLP HTTP/protobuf exporter for opt-in request tracing

External services are optional at runtime. The Python SDK can target any NetBox instance reachable over HTTPS/HTTP, and the local mock API can be used for offline tests.

OpenTelemetry Request Metrics

Metrics activate on the presence of an OTLP endpoint alone — no SDK-specific toggle. Install the otel extra and set either OTEL_EXPORTER_OTLP_METRICS_ENDPOINT or OTEL_EXPORTER_OTLP_ENDPOINT, and every NetBoxApiClient.request() records against a counter and a duration histogram. A deployment that already exports telemetry therefore gets request rates and latencies with no per-service wiring, which is the difference from tracing: tracing emits one record per request and asks for an explicit opt-in, metrics aggregate and do not.

Instrument Name Unit
Counter netbox.client.request.count {request}
Histogram netbox.client.request.duration s

Attributes are the HTTP method, a templated operation path, the response status, and the server address. The template is what keeps cardinality bounded: /api/dcim/devices/17/ is recorded as /api/dcim/devices/{id}/, so a metric attribute never carries an object id. Numeric and UUID segments are both templated.

A request that raises is still counted, with no status attribute — the failure path is the one an operator most needs to see. Recording never raises into the caller: a telemetry failure must not break the call it observes.

An existing global MeterProvider configured by the host application is reused, never replaced. With the otel extra absent, or with OTEL_SDK_DISABLED=true or OTEL_METRICS_EXPORTER=none, everything degrades to a no-op.

Variable Purpose
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT Metrics-specific OTLP endpoint; activates metrics
OTEL_EXPORTER_OTLP_ENDPOINT Shared OTLP endpoint; also activates metrics
OTEL_METRICS_EXPORTER Use none to disable metrics while leaving tracing alone
OTEL_SDK_DISABLED Standard kill switch; disables metrics when true

OpenTelemetry Request Tracing

Tracing is disabled by default. Install the otel extra and enable it with NETBOX_OTEL_ENABLED=true or Config(otel_enabled=True) to emit one OpenTelemetry CLIENT span for each NetBoxApiClient.request() call. Spans use HTTP-client semantic attributes for method, server address, URL path, and final response status; authorization headers, tokens, and query strings are never added as span attributes.

The SDK uses an existing global OpenTelemetry provider when the host application has configured one. Otherwise, when tracing is explicitly enabled, it installs a provider with a BatchSpanProcessor and the OTLP HTTP/protobuf exporter. Collector endpoint, headers, service name, resource attributes, sampler, and exporter selection are controlled by the standard OpenTelemetry environment variables.

Variable Purpose
NETBOX_OTEL_ENABLED SDK-specific opt-in toggle (true/false)
OTEL_SDK_DISABLED Standard OpenTelemetry kill switch; disables SDK tracing when true
OTEL_EXPORTER_OTLP_ENDPOINT OTLP collector endpoint for the HTTP exporter
OTEL_EXPORTER_OTLP_PROTOCOL Must be http/protobuf for the bundled HTTP exporter
OTEL_EXPORTER_OTLP_HEADERS Standard OTLP exporter headers
OTEL_SERVICE_NAME Service name, defaulting to netbox-sdk
OTEL_RESOURCE_ATTRIBUTES Additional OpenTelemetry resource attributes
OTEL_TRACES_SAMPLER Standard OpenTelemetry sampler selection
OTEL_TRACES_EXPORTER Use otlp or none for the SDK-installed provider

netbox-sdk vs pynetbox

pynetbox vs netbox-sdk comparison table

Contributor Workflow

uv sync --dev --extra cli --extra tui --extra demo --extra mcp
uv run pre-commit install --hook-type pre-commit --hook-type pre-push
uv run pre-commit run --all-files
uv run ty check netbox_sdk netbox_cli netbox_tui netbox_mcp tests
uv run pytest

Gitea pull requests targeting main also run a secret-free quality gate on the isolated ci-untrusted-python312 runner. It covers workflow policy, both type checkers, pre-commit, the complete offline mocked suite, SDK/CLI/TUI/MCP security regressions, strict MkDocs, and wheel/sdist metadata plus an installed-wheel smoke. GitHub retains the Python 3.11–3.13 and live-NetBox matrices. Do not merge on queued or missing Gitea evidence; runner_id: 0 means no eligible runner has accepted the job.

IDE Support

Open the repository in VS Code. When prompted, install the recommended extensions (ms-python.vscode-pylance, ms-python.python, charliermarsh.ruff). Pylance picks up types from all four packages automatically — each ships a py.typed PEP 561 marker.

Type checking uses two gates: ty (Astral, fast, pre-commit + CI) and pyright (Pylance-compatible, pre-commit). Both run at typeCheckingMode = "basic". To run them manually:

uv run ty check netbox_sdk netbox_cli netbox_tui netbox_mcp tests
uv run pyright netbox_sdk netbox_cli netbox_tui netbox_mcp

Release Process

Release candidates use an annotated RC tag on the canonical source repository, while final and post releases are authorized only through a GitHub Release. The repository-owned workflow publishes an immutable candidate to an access-controlled package registry; GitHub Actions remains the only authority that publishes to TestPyPI or the default PyPI index:

The private Gitea workflow pins the maintained Node 20 backports actions/upload-artifact v3.2.2-node20 and actions/download-artifact v3.1.0-node20 by full commit SHA. The installed Gitea Actions runtime does not implement the artifact service required by v4+, so upgrading those actions requires an explicit runner-compatibility proof. .gitea/workflows/artifact-v3-compatibility.yml exercises the exact pinned upload/download pair across separate isolated untrusted jobs on every pull request; that check must pass before any candidate tag is created. PR code must never target the credential-bearing mirror-host label. Trusted jobs fetch public canonical source anonymously at exact refs. Every private publisher downloads the pinned uv 0.11.28 archive directly, verifies its reviewed SHA-256 before extraction, and uses only that absolute executable with empty per-run managed-Python and cache directories. Before both install and sync it clears inherited UV_* state, disables discovered uv configuration, and passes the managed-Python, install, and cache choices explicitly. The package-write credential comes from the repository PACKAGE_WRITE_TOKEN secret and is introduced only in the final sealed publish step; the upstream Gitea Actions job token is not a supported package-registry credential. All three release stages run as separate disposable ci-untrusted-python312 jobs. The built-in job token stays package-read-only throughout; package write authority exists only in the repository secret mapped into the final publish step. Neither release nor PR code may target persistent mirror-host.

# Static, operator-reviewed release oracle: do not derive this tag from event input.
nms git api GET /repos/emersonfelipesp/netbox-sdk/tag_protections \
  --output /tmp/netbox-sdk-tag-protections.json
python -m scripts.gitea_release validate-tag-protection \
  --policy-file .gitea/release-tag-policy.json \
  --evidence-file /tmp/netbox-sdk-tag-protections.json

The evidence command and validator are a mandatory preflight before creating any release tag. The repository contract requires one server-side protection for every v* tag, with only emersonfelipesp allowlisted and no teams. This is external repository state: the tag-triggered workflow cannot and does not self-verify the protection that authorized its own trigger.

Release-candidate procedure

A direct annotated tag push belongs only to a release candidate. The following commands show the already validated candidate for this release line; they do not authorize the final release:

git tag -a v0.0.13rc1 -m "Release v0.0.13rc1"
git push gitea v0.0.13rc1

Do not create a GitHub Release for an RC because that event authorizes publication to the default PyPI index.

Final-release procedure for the current tree

The current source candidate is the final 0.0.13 tree. Do not authorize this final with a direct tag push. Confirm that the intended final tag is absent, then publish through the GitHub Release event with the title pattern netbox-sdk vX.Y.Z. --target is ignored when the tag already exists, so any failed query or nonempty result must stop release creation. The generic procedure is:

git ls-remote origin refs/tags/vX.Y.Z > /tmp/netbox-sdk-vX.Y.Z-tag-refs && \
test ! -s /tmp/netbox-sdk-vX.Y.Z-tag-refs && \
gh release create vX.Y.Z \
  --title "netbox-sdk vX.Y.Z" \
  --target <canonical-main-sha>

For this reviewed final tree, use the exact version and bind the release to the reviewed default-branch commit:

git ls-remote origin refs/tags/v0.0.13 > /tmp/netbox-sdk-v0.0.13-tag-refs && \
test ! -s /tmp/netbox-sdk-v0.0.13-tag-refs && \
gh release create v0.0.13 \
  --title "netbox-sdk v0.0.13" \
  --target <canonical-main-sha>

When cutting a source candidate, bump pyproject.toml and netbox_sdk.__version__, then keep the candidate surfaces in sync: docs/snippets/package-version.txt, mkdocs.yml → extra.package_version, metadata.json, and llms.txt. Keep normal-index install examples aligned separately with docs/snippets/published-package-version.txt and update that value only after a PEP 440 final or post-release package is verifiably available on PyPI. Prerelease, development, and local versions remain source/TestPyPI-only. uv lock must reflect the source candidate. scripts/release_policy.py and tests/test_docs_alignment.py guard both version contracts and registry routing.

The published v0.0.10 annotated tag object is immutable at e104bdd554ac2becf7abd38b238d8fb5509651f4 and must peel to 3bcc86481f60f0f2d6fb1913c42d1561f5d5b77e. Preserve its unique history with a merge with exactly two parents and the published commit as its second parent, then use a merge-commit (or fast-forward the already-created merge commit) when integrating the reviewed Gitea PR. Squash, rebase, and octopus merge methods are forbidden. Verify the exact object and commit, then confirm the release commit is already an ancestor of explicitly fetched canonical Gitea main before creating any candidate tag or publishing any registry artifact. The release workflow also fetches Gitea's v0.0.10 ref into an isolated validation ref and requires its annotated tag-object SHA and peeled commit to match exactly.

All credentialed publishing workflows pin every action to a reviewed full commit SHA. Every private-publisher stage also requires the complete pinned uv identity, including the Linux x86-64 target triple; a shortened, suffixed, wrong-version, or wrong-platform rendering fails before candidate processing. The private-registry workflow builds twice in independent, credential-free source worktrees, derives canonical archive metadata from the validated source-commit timestamp, and requires both wheel/sdist pairs to be byte-identical. A separate credential-free job binds the archives and complete distribution metadata to the exact canonical Git source, then emits only a private, read-only seal and its two exact files. The package-write job checks out the helper at the immutable tag-event SHA, rejects any upstream source-SHA mismatch, parses only the small seal, re-hashes the two regular files, and accepts only an absent or already-exact remote set associated with this repository. The public package workflow uses the publish dependency group from uv.lock for build and Twine execution, validates exactly one correctly named wheel plus one sdist, and captures that closed set before any network-installed smoke dependency can run. Smoke testing consumes a downstream artifact copy. Immediately before a final PyPI upload, the workflow re-fetches canonical Gitea, reruns the ancestry/tag policy, matches TestPyPI's exact filename and SHA-256 set, then compares PyPI's current exact filename/hash set and copies only verified-missing production files into a fresh directory. Twine receives only that approved directory after its filename/digest manifest is revalidated in the upload step, so a partial PyPI upload can resume without --skip-existing. A bounded post-upload check then requires PyPI to expose the exact wheel/sdist pair and hashes. Publisher jobs install only the audited, locked publish dependency group. Metadata generation runs without credentials and records an authoritative content identity. A dedicated credential-free workflow validates the committed metadata. The serialized Gitea-to-GitHub mirror confirms that the event SHA is still the latest canonical tip and pushes that exact commit without executing repository code, generating metadata, or committing a GitHub-only metadata update. Canonical fetch and GitHub push credentials are confined to separate steps. The one-time historical rewrite is permitted only when the observed GitHub main tip is exactly d0b46101d3d91d6755b6a419e9095577b66a443d, and its force-with-lease remains fixed to that reviewed SHA. Every other update requires the observed GitHub tip to be an ancestor of the canonical commit and uses an exact force-with-lease fixed to the tip inspected by that ancestry check. A rejected push aborts without refreshing the observed tip or retrying, so concurrent rewinds and other concurrent writes are never overwritten. Regular GitHub validation workflows run when metadata changes, and the dedicated read-only workflow calls the same content-identity verifier.

Private-registry versions are immutable. A partial remote version is a terminal collision for that version: never delete files, overwrite them, or retry the same version. Diagnose and fix the release source or workflow, advance every candidate-version surface to the next unused rcN, repeat the external release-tag protection preflight, and publish only the new candidate tag.

Release metadata uses source.content_id as its authoritative provenance. The value is the SHA-256 digest of the UTF-8 bytes of sorted git ls-tree -r --full-tree lines for the candidate tree, excluding the metadata.json entry so the digest does not contain itself. The generator uses a temporary index and object database to stage the current checkout without changing the real index. Empty subtrees do not appear in recursive ls-tree output and therefore do not affect the identity. Content equality authenticates the tree, not the repository origin; release workflows must bind the commit to a trusted canonical fetch separately. The exact metadata schema pins source.repo, derives python and netbox from the same project sources as generation, and requires generated_at in RFC 3339 UTC form. Commit the candidate changes, run python scripts/build_metadata.py, stage metadata.json, and amend that same commit rather than creating a mirror-side or metadata-only follow-up. The digest remains stable across the amendment because metadata.json is excluded. source.version must match project.version; source.commit is informational. If that commit object remains available, its version and tree outside metadata.json must match the exact candidate tree during generation as well as verification. Run python scripts/build_metadata.py --verify in a clean checkout to validate the committed content identity against HEAD.

About

Modern NetBox toolkit with an SDK, CLI and TUI (terminal UI) for faster automation.

Topics

Resources

Stars

18 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages