# Infrahub by OpsMill - Documentation > Infrahub by OpsMill - the modern infrastructure data management platform. Infrahub is the open-source product built by OpsMill (https://opsmill.com). - [Infrahub by OpsMill - Documentation](/index.md) ## search - [Search the documentation](/search.md) ## ansible Collection version 1.8.3 - [opsmill.infrahub Ansible collection](/ansible.md): Collection version 1.8.3 ### guides - [Manage branches in Infrahub](/ansible/guides/branch.md): Infrahub lets you manage branches directly via the opsmill.infrahub.branch module. With this module you can create, confirm, and delete branches—using the state parameter to control the desired operation. - [Use Infrahub for Ansible dynamic inventory](/ansible/guides/dynamic-inventory.md): Overview - [Install opsmill.infrahub Ansible collection](/ansible/guides/installation.md): This guide assumes you have basic knowledge of Ansible and its ecosystem. For more information on using Ansible, please refer to the official Ansible documentation. - [Create nodes in Infrahub](/ansible/guides/node.md): Overview - [Retrieve data from Infrahub](/ansible/guides/query-and-lookup.md): Overview ### references - [Modules modules](/ansible/references/plugins/artifact_fetch_module.md): Fetch the content of an artifact from Infrahub - [Modules modules](/ansible/references/plugins/artifact_generate_module.md): Trigger artifact regeneration in Infrahub - [Modules modules](/ansible/references/plugins/branch_module.md): Creates, Updates or Deletes a branch in Infrahub - [Inventory inventory](/ansible/references/plugins/inventory_inventory.md): Infrahub inventory source (using GraphQL) - [Lookup lookup](/ansible/references/plugins/lookup_lookup.md): Queries and returns elements from Infrahub (using GraphQL) - [Modules modules](/ansible/references/plugins/node_module.md): Creates, Updates or Deletes a node in Infrahub - [Modules modules](/ansible/references/plugins/object_file_fetch_module.md): Fetch file content from a CoreFileObject node in Infrahub - [Modules modules](/ansible/references/plugins/query_graphql_module.md): Queries and returns elements from Infrahub GraphQL API - [Modules modules](/ansible/references/plugins/schema_module.md): Load, check, or export schemas in Infrahub - [Overview](/ansible/references/roles/install.md): The Infrahub Installation Role in the OpsMill Infrahub Ansible Collection allows you to install and configure Infrahub using Docker and systemd. ## backup Overview - [Infrahub Backup](/backup.md): Overview ### guides - [Back up your Infrahub instance](/backup/guides/backup-instance.md): This guide walks you through creating a comprehensive backup of your Infrahub instance. If you need to protect your data, prepare for upgrades, or establish disaster recovery procedures, follow these steps to create reliable backups. - [Collect a troubleshooting bundle](/backup/guides/collect-troubleshooting-bundle.md): Use infrahub-collect to gather everything OpsMill support needs to investigate an issue — service logs, diagnostic status, configuration, and metrics — into a single local archive. Run it when support asks for logs, or to attach diagnostic data to a support request. The same command produces the same bundle on Docker Compose and Kubernetes deployments. - [Install Infrahub Backup](/backup/guides/install.md): This guide shows you how to install Infrahub Backup on your system. - [Install Infrahub Collect](/backup/guides/install-collect.md): This guide shows you how to install Infrahub Collect on your system. - [Back up Infrahub on Kubernetes](/backup/guides/kubernetes-backup.md): This guide walks you through backing up your Infrahub instance running on Kubernetes using the infrahub-backup Helm chart. If you deploy Infrahub through GitOps tools like ArgoCD or Flux and don't have direct kubectl access, this approach lets you manage backups declaratively through Helm values. - [Restore Infrahub on Kubernetes](/backup/guides/kubernetes-restore.md): This guide walks you through restoring your Infrahub instance on Kubernetes from a backup stored in S3-compatible storage. The restore process uses the same infrahub-backup Helm chart and runs as a Kubernetes Job. - [Restore from a backup](/backup/guides/restore-backup.md): This guide shows you how to restore your Infrahub instance from a backup file. If you need to recover from data loss, roll back changes, or migrate to new infrastructure, follow these steps to safely restore your system. - [How to update the CLI](/backup/guides/self-update.md): This guide shows you how to update the infrahub-backup and infrahub-collect ### reference - [CLI command reference](/backup/reference/commands.md): Complete reference for the Infrahub Ops CLI tools (infrahub-backup, infrahub-collect), their commands, flags, and options. - [Configuration reference](/backup/reference/configuration.md): Complete reference for configuring the Infrahub Ops CLI tools through environment variables and runtime flags. ### tutorials - [Get started with Infrahub Backup](/backup/tutorials/getting-started.md): This tutorial walks you through your first experience with Infrahub Backup, teaching you the fundamentals by creating a backup of your Infrahub instance and then restoring it. By the end of this tutorial, you'll understand the basic workflow and be confident in using infrahub-backup for essential maintenance tasks. ## demo-dc Welcome to the Infrahub demo-dc example. This repository showcases how Infrahub serves as an infrastructure data management platform for managing modern network infrastructure with design-driven automation. It demonstrates Infrahub's core capabilities including: - [Infrahub demo-dc example](/demo-dc.md): Welcome to the Infrahub demo-dc example. This repository showcases how Infrahub serves as an infrastructure data management platform for managing modern network infrastructure with design-driven automation. It demonstrates Infrahub's core capabilities including: ### cloud-management This tutorial shows an example of cloud resource management schema, which provides a vendor-agnostic way to model cloud infrastructure across AWS, GCP, and Azure. You'll load sample cloud data and explore how Infrahub can serve as a unified inventory for multi-cloud environments. - [Cloud resource management](/demo-dc/cloud-management.md): This tutorial shows an example of cloud resource management schema, which provides a vendor-agnostic way to model cloud infrastructure across AWS, GCP, and Azure. You'll load sample cloud data and explore how Infrahub can serve as a unified inventory for multi-cloud environments. ### concepts This document explains the architectural patterns, design decisions, and core Infrahub concepts demonstrated in this demo. Read this to understand the "why" behind design-driven automation, composable topologies, generators, and the overall approach to infrastructure management. - [Understanding the concepts](/demo-dc/concepts.md): This document explains the architectural patterns, design decisions, and core Infrahub concepts demonstrated in this demo. Read this to understand the "why" behind design-driven automation, composable topologies, generators, and the overall approach to infrastructure management. ### containerlab-deployment This tutorial walks you through deploying a virtual data center topology using Containerlab. You'll take the configurations generated by Infrahub and spin up a fully functional network lab with Arista cEOS virtual switches. - [Deploy a virtual lab with Containerlab](/demo-dc/containerlab-deployment.md): This tutorial walks you through deploying a virtual data center topology using Containerlab. You'll take the configurations generated by Infrahub and spin up a fully functional network lab with Arista cEOS virtual switches. ### developer-guide This guide provides a technical deep-dive into how the Infrahub demo works under the hood. Use this when you want to extend functionality, troubleshoot issues, customize the demo, or understand implementation details. - [Developer guide](/demo-dc/developer-guide.md): This guide provides a technical deep-dive into how the Infrahub demo works under the hood. Use this when you want to extend functionality, troubleshoot issues, customize the demo, or understand implementation details. ### enterprise This guide explains how to configure the demo environment to use Infrahub Enterprise edition instead of the Community edition. Enterprise edition provides additional features including increased performance, enhanced security, and enterprise support. - [Using Infrahub Enterprise](/demo-dc/enterprise.md): This guide explains how to configure the demo environment to use Infrahub Enterprise edition instead of the Community edition. Enterprise edition provides additional features including increased performance, enhanced security, and enterprise support. ### install This guide walks you through setting up the Infrahub demo environment on your local system. By the end, you'll have a fully functional Infrahub instance with pre-loaded schemas, data, and generators ready to explore. - [Installation guide](/demo-dc/install.md): This guide walks you through setting up the Infrahub demo environment on your local system. By the end, you'll have a fully functional Infrahub instance with pre-loaded schemas, data, and generators ready to explore. ### security-management This tutorial walks you through Infrahub's security management capabilities. You'll explore how security policies are modeled as structured data, how they relate to firewalls, and how Infrahub transforms security policy objects into vendor-specific firewall configurations. - [Working with security management](/demo-dc/security-management.md): This tutorial walks you through Infrahub's security management capabilities. You'll explore how security policies are modeled as structured data, how they relate to firewalls, and how Infrahub transforms security policy objects into vendor-specific firewall configurations. ### service-catalog This tutorial walks you through using Infrahub's Service Catalog web interface to manage data center and colocation center deployments. The Service Catalog provides a user-friendly web application built with Streamlit that simplifies infrastructure provisioning by automating branch creation, data loading, and generator execution. - [Using the service catalog](/demo-dc/service-catalog.md): This tutorial walks you through using Infrahub's Service Catalog web interface to manage data center and colocation center deployments. The Service Catalog provides a user-friendly web application built with Streamlit that simplifies infrastructure provisioning by automating branch creation, data loading, and generator execution. ### user-walkthrough This tutorial guides you through the complete workflow of creating a data center topology using Infrahub's design-driven automation. You'll create a new branch, load a topology design, run a generator to create infrastructure, review the changes, and merge them to the main branch. - [User walkthrough](/demo-dc/user-walkthrough.md): This tutorial guides you through the complete workflow of creating a data center topology using Infrahub's design-driven automation. You'll create a new branch, load a topology design, run a generator to create infrastructure, review the changes, and merge them to the main branch. ## demo-service-catalog A proof of concept for a service catalog using Infrahub and Streamlit. - [Home](/demo-service-catalog.md): A proof of concept for a service catalog using Infrahub and Streamlit. ### getting-started - [Developer Walkthrough](/demo-service-catalog/getting-started/developer-walkthrough.md): A step-by-step guide to running the demo from a developer perspective. - [How to Install the Service Catalog Demo](/demo-service-catalog/getting-started/installation.md): Set up the Service Catalog demo environment with Infrahub and Streamlit to enable self-service network provisioning. - [How to Use the Service Catalog Demo](/demo-service-catalog/getting-started/user-walkthrough.md): Experience the complete service delivery workflow from request to provisioning using both the Service Catalog portal and Infrahub interface. ## emma Emma is an experimental AI-powered assistant designed to help you interact with Infrahub, OpsMill's next-generation infrastructure management platform. Emma helps you manage infrastructure schemas and data through an intuitive web interface. - [Welcome to Emma](/emma.md): Emma is an experimental AI-powered assistant designed to help you interact with Infrahub, OpsMill's next-generation infrastructure management platform. Emma helps you manage infrastructure schemas and data through an intuitive web interface. ### features - [Data Import and Export](/emma/features/data-import-export.md): Emma provides powerful tools for importing data into Infrahub from CSV files and exporting Infrahub data back to CSV format. This enables efficient migration from existing systems and data sharing between tools. - [AI Schema Builder](/emma/features/schema-builder.md): Emma's Schema Builder is an AI-powered tool that helps you create Infrahub schemas using natural language descriptions. Instead of manually writing YAML schema definitions, you can describe what you need and let Emma's AI assistant generate the appropriate schema structure. - [Schema Management](/emma/features/schema-management.md): Emma provides comprehensive tools for managing Infrahub schemas throughout their lifecycle. From loading and visualizing schemas to organizing them in libraries, Emma makes schema management accessible and efficient. ### getting-started - [Configuration](/emma/getting-started/configuration.md): Emma connects to Infrahub using standard environment variables and configuration options. This guide covers all the ways to configure Emma for your environment. - [First Steps](/emma/getting-started/first-steps.md): Welcome to Emma! This guide will walk you through your first experience using Emma to interact with Infrahub. - [Installation](/emma/getting-started/installation.md): Emma is an experimental AI-powered assistant for Infrahub that helps you manage infrastructure schemas and data through an intuitive web interface. ### guides - [Building Your First Schema](/emma/guides/building-your-first-schema.md): This guide walks you through creating your first Infrahub schema using Emma's AI Schema Builder. We'll create a practical schema for modeling network devices. ### reference - [Configuration Reference](/emma/reference/configuration.md): This reference covers all configuration options available in Emma, including environment variables, feature flags, and runtime settings. - [Feature Flags](/emma/reference/feature-flags.md): Emma uses feature flags to enable experimental and beta functionality. This allows you to try new features while maintaining stability in production environments. - [Troubleshooting](/emma/reference/troubleshooting.md): This guide covers common issues you might encounter while using Emma and how to resolve them. ## exporter Infrahub Exporter is a service that exports metrics and service discovery information from Infrahub to monitoring systems like Prometheus and OpenTelemetry. - [Infrahub Exporter](/exporter.md): Infrahub Exporter is a service that exports metrics and service discovery information from Infrahub to monitoring systems like Prometheus and OpenTelemetry. ### guides - [Configure Infrahub Exporter](/exporter/guides/configuration.md): Configuration - [Install and run Infrahub Exporter](/exporter/guides/installation.md): This guide walks you through installing and running the Infrahub Exporter on your system. ## infrahub-demo-sp Welcome to the Infrahub demo-sp example — a service-provider bundle that - [Infrahub demo-sp example](/infrahub-demo-sp.md): Welcome to the Infrahub demo-sp example — a service-provider bundle that ### architecture This page summarises the data flow and the role of each directory. - [Architecture](/infrahub-demo-sp/architecture.md): This page summarises the data flow and the role of each directory. ### lab - [Containerlab](/infrahub-demo-sp/lab/containerlab.md): This page covers the optional containerlab integration: what runs, which PEs ### quickstart This page gets you from clone to a running demo in about 10 minutes. For an - [Quick start](/infrahub-demo-sp/quickstart.md): This page gets you from clone to a running demo in about 10 minutes. For an ### schema-reference This page covers the SP-demo-specific schema nodes defined in schemas/sp/. - [Schema reference](/infrahub-demo-sp/schema-reference.md): This page covers the SP-demo-specific schema nodes defined in schemas/sp/. ### services - [L3VPN service](/infrahub-demo-sp/services/l3vpn.md): This page documents the L3VPN service end-to-end: how an operator creates - [SD-WAN service](/infrahub-demo-sp/services/sdwan.md): The SD-WAN service models a customer's SD-WAN overlay across one or more ### troubleshooting Common failure modes and their fixes. If your problem is not here, check - [Troubleshooting](/infrahub-demo-sp/troubleshooting.md): Common failure modes and their fixes. If your problem is not here, check ### validation - [Batfish validation](/infrahub-demo-sp/validation/batfish.md): BatfishBackboneCheck runs the Batfish query ## infrahub-solution-ai-dc ### solution-ai-dc - [Demo guide](/infrahub-solution-ai-dc/solution-ai-dc/demo-guide.md): Run the AI/DC solution end to end — from loading design data to generating a complete data center fabric. - [Design-driven automation](/infrahub-solution-ai-dc/solution-ai-dc/design-driven-automation.md): How the AI/DC solution separates design intent from infrastructure implementation using Infrahub Generators. - [EVPN/VXLAN overlay](/infrahub-solution-ai-dc/solution-ai-dc/evpn-vxlan-overlay.md): Multi-tenant EVPN/VXLAN overlay services layered on top of the AI/DC solution's OSPF-underlay Clos fabric. - [Generator patterns](/infrahub-solution-ai-dc/solution-ai-dc/generator-patterns.md): Implementation patterns used in the AI/DC solution Generators — queries, device creation, IP allocation, cabling, and checksum propagation. - [Installation & Setup](/infrahub-solution-ai-dc/solution-ai-dc/installation-setup.md): Prerequisites, environment setup, and repository structure for the AI/DC solution. - [Modular Generator architecture](/infrahub-solution-ai-dc/solution-ai-dc/modular-generator-architecture.md): How the AI/DC solution chains Generators through checksum triggers, validates parent completion, and allocates interfaces deterministically. - [Multivendor configuration](/infrahub-solution-ai-dc/solution-ai-dc/multivendor-config.md): How the AI/DC solution renders per-vendor (Cisco, Arista, Dell) device configuration from a single design model using vendor device groups. - [AI/DC solution](/infrahub-solution-ai-dc/solution-ai-dc/overview.md): Design-driven automation for large-scale AI data center fabrics using Infrahub Generators. ## infrahubctl ### infrahubctl infrahubctl is a command line utility designed to help with the day to day management of an Infrahub installation. - [infrahubctl](/infrahubctl/infrahubctl.md): infrahubctl is a command line utility designed to help with the day to day management of an Infrahub installation. ### infrahubctl-branch Manage the branches in a remote Infrahub instance. - [infrahubctl branch](/infrahubctl/infrahubctl-branch.md): Manage the branches in a remote Infrahub instance. ### infrahubctl-check Execute user-defined checks. - [infrahubctl check](/infrahubctl/infrahubctl-check.md): Execute user-defined checks. ### infrahubctl-dump Export nodes and their relationships out of the database. - [infrahubctl dump](/infrahubctl/infrahubctl-dump.md): Export nodes and their relationships out of the database. ### infrahubctl-generator Run a generator script. - [infrahubctl generator](/infrahubctl/infrahubctl-generator.md): Run a generator script. ### infrahubctl-graphql Various GraphQL related commands. - [infrahubctl graphql](/infrahubctl/infrahubctl-graphql.md): Various GraphQL related commands. ### infrahubctl-info Display the status of the Python SDK. - [infrahubctl info](/infrahubctl/infrahubctl-info.md): Display the status of the Python SDK. ### infrahubctl-load Import nodes and their relationships into the database. - [infrahubctl load](/infrahubctl/infrahubctl-load.md): Import nodes and their relationships into the database. ### infrahubctl-marketplace Browse and download schemas from the Infrahub Marketplace. - [infrahubctl marketplace](/infrahubctl/infrahubctl-marketplace.md): Browse and download schemas from the Infrahub Marketplace. ### infrahubctl-menu Manage the menu in a remote Infrahub instance. - [infrahubctl menu](/infrahubctl/infrahubctl-menu.md): Manage the menu in a remote Infrahub instance. ### infrahubctl-object Manage objects in a remote Infrahub instance. - [infrahubctl object](/infrahubctl/infrahubctl-object.md): Manage objects in a remote Infrahub instance. ### infrahubctl-protocols Export Python protocols corresponding to a schema. - [infrahubctl protocols](/infrahubctl/infrahubctl-protocols.md): Export Python protocols corresponding to a schema. ### infrahubctl-render Render a local Jinja2 Transform for debugging purpose. - [infrahubctl render](/infrahubctl/infrahubctl-render.md): Render a local Jinja2 Transform for debugging purpose. ### infrahubctl-repository Manage the repositories in a remote Infrahub instance. - [infrahubctl repository](/infrahubctl/infrahubctl-repository.md): Manage the repositories in a remote Infrahub instance. ### infrahubctl-run Execute a script. - [infrahubctl run](/infrahubctl/infrahubctl-run.md): Execute a script. ### infrahubctl-schema Manage the schema in a remote Infrahub instance. - [infrahubctl schema](/infrahubctl/infrahubctl-schema.md): Manage the schema in a remote Infrahub instance. ### infrahubctl-task Manage Infrahub tasks. - [infrahubctl task](/infrahubctl/infrahubctl-task.md): Manage Infrahub tasks. ### infrahubctl-telemetry Usage: - [infrahubctl telemetry](/infrahubctl/infrahubctl-telemetry.md): Usage: ### infrahubctl-transform Render a local transform (TransformPython) for debugging purpose. - [infrahubctl transform](/infrahubctl/infrahubctl-transform.md): Render a local transform (TransformPython) for debugging purpose. ### infrahubctl-validate Helper to validate the format of various files. - [infrahubctl validate](/infrahubctl/infrahubctl-validate.md): Helper to validate the format of various files. ### infrahubctl-version Display the version of Python and the version of the Python SDK in use. - [infrahubctl version](/infrahubctl/infrahubctl-version.md): Display the version of Python and the version of the Python SDK in use. ## integrations Infrahub integrates with a wide range of infrastructure management and automation tools to help you synchronize data, automate workflows, and manage your infrastructure. This page provides an overview of all available integrations and their capabilities. - [Infrahub Integrations](/integrations.md): Infrahub integrates with a wide range of infrastructure management and automation tools to help you synchronize data, automate workflows, and manage your infrastructure. This page provides an overview of all available integrations and their capabilities. ## mcp Infrahub MCP Server connects AI assistants and IDE agents to Infrahub using the open Model Context Protocol standard — so agents can query, create, update, and propose changes to your infrastructure data through a consistent, audited interface. It works with any MCP-compatible client (Claude Desktop, VS Code, Cursor, CLI agents, and more) with no custom glue code required. - [Infrahub MCP Server](/mcp.md): Infrahub MCP Server connects AI assistants and IDE agents to Infrahub using the open Model Context Protocol standard — so agents can query, create, update, and propose changes to your infrastructure data through a consistent, audited interface. It works with any MCP-compatible client (Claude Desktop, VS Code, Cursor, CLI agents, and more) with no custom glue code required. ### getting-started - [Set up authentication](/mcp/getting-started/authentication.md): This guide walks through configuring each authentication mode. For a deeper explanation of how the auth architecture works — sequence diagrams, the two-layer model, audit logs, and branch-placeholder resolution — see Authentication architecture. - [Your first agent run](/mcp/getting-started/first-agent-run.md): This walkthrough connects an LLM client to your running Infrahub MCP server and asks it a read-only question. By the end you'll have confirmed the server is reachable, the agent can discover your schema, and the built-in infrahub_agent system prompt is guiding the conversation correctly. - [Install the Infrahub MCP server](/mcp/getting-started/installation.md): This guide gets you from a fresh checkout to a running Infrahub MCP server in a few minutes. Once it's running, continue with Authentication to pick a credential model, then Your first agent run to connect an LLM. - [Make a change through an agent](/mcp/getting-started/make-a-change.md): This walkthrough takes you through the full write path: ask an agent to update something in Infrahub, watch it auto-create an isolated session branch, and open a Proposed Change for human review. No data on your default branch is modified until a human approves the merge. ### guides - [Running with Docker](/mcp/guides/docker.md): This guide covers deploying the Infrahub MCP server as a container, including running it alongside Infrahub in the same Docker Compose stack. - [Docker Compose deployment](/mcp/guides/docker-compose.md): This guide covers running the Infrahub MCP server as a container — standalone for development, or as a sidecar alongside Infrahub in the same Docker Compose stack for production. - [Installing Infrahub MCP](/mcp/guides/installation.md): This guide provides step-by-step instructions for installing and configuring different MCP clients to connect to the Infrahub MCP server. ### integrations - [Claude Agent SDK](/mcp/integrations/claude-agent-sdk.md): The Claude Agent SDK is Anthropic's framework for building custom agents on top of the Claude API. It has native MCP support, so connecting to the Infrahub MCP server is a short wiring exercise. - [Claude Code](/mcp/integrations/claude-code.md): Claude Code is Anthropic's CLI/IDE agent. It auto-detects .mcp.json files at project roots and loads the servers on startup — a perfect fit for the Infrahub MCP server when you're working inside a specific Infrahub consumer repository (IaC, Ansible, Python clients, etc.). - [Claude Desktop](/mcp/integrations/claude-desktop.md): Claude Desktop is the default starting point for most users — it speaks MCP over stdio and can spawn the Infrahub MCP server locally without any infrastructure. - [Cursor](/mcp/integrations/cursor.md): Cursor supports MCP servers via its Tools & Integrations panel. This page shows the Infrahub MCP server configuration and notes specific to Cursor's tool-approval UX. - [OpenAI Agents SDK](/mcp/integrations/openai-agents-sdk.md): The OpenAI Agents SDK has first-class support for MCP servers. This page shows how to build a custom infrastructure-automation agent that talks to the Infrahub MCP server over Streamable HTTP. - [VS Code](/mcp/integrations/vscode.md): VS Code supports MCP through its extension API — the Infrahub MCP server shows up as a tool provider alongside Copilot. This page covers the code --add-mcp setup and the VS Code Copilot compatibility flag. ### references - [Authentication architecture](/mcp/references/authentication.md): For the step-by-step configuration recipes, see Set up authentication. This page explains how each mode works end-to-end. - [Configuration reference](/mcp/references/configuration.md): The Infrahub MCP server is configured entirely through environment variables. All middleware features are opt-in — the server works out of the box with only the required connection variables set. - [Infrahub MCP methods](/mcp/references/methods.md): Response formats ### release-notes - [Release Notes](/mcp/release-notes.md) - [Release 1.0.0](/mcp/release-notes/release-1_0_0.md): The first stable release — query Infrahub and propose changes for review from any MCP-compatible client. - [Release 1.0.1](/mcp/release-notes/release-1_0_1.md): Error messages now include schema hints so AI agents can correct invalid queries and retry. - [Release 1.1.1](/mcp/release-notes/release-1_1_1.md): Run the MCP server as a shared service with pass-through authentication, read-only mode, and observability. - [Release 1.1.2](/mcp/release-notes/release-1_1_2.md): Dependency and internal tooling maintenance, with no changes to tools or runtime behavior. - [Release 1.1.3](/mcp/release-notes/release-1_1_3.md): Fix startup for non-HTTP transports, plus pipeline and tooling maintenance. - [Release 1.1.4](/mcp/release-notes/release-1_1_4.md): Align with current Infrahub SDK releases, plus routine dependency maintenance. - [Release 1.1.5](/mcp/release-notes/release-1_1_5.md): Validate a session branch before reuse so an unusable branch fails with a clear reason. - [Release 1.1.6](/mcp/release-notes/release-1_1_6.md): Keep an AI conversation going after its session branch is merged or deleted, with automatic recovery and a reset tool to start the next change set. - [Release 1.1.7](/mcp/release-notes/release-1_1_7.md): Trace how two infrastructure objects are connected and find what a change could reach, with two read-only graph-traversal tools backed by Infrahub 1.10. ### use-cases - [Brownfield network onboarding](/mcp/use-cases/brownfield-onboarding.md): Onboarding an existing ("brownfield") network into Infrahub is a long tail of small writes: one device per rack, one interface per device, one cable per interface. Agents shine here — they can read source-of-truth data from CSV exports, SNMP, LLDP neighbours, or NetBox, and turn it into idempotent Infrahub upserts on a session branch you can review all at once. - [Compliance analysis](/mcp/use-cases/compliance-analysis.md): Agents are a natural fit for ad-hoc compliance questions: "do all edge routers have dual uplinks to different spines?", "are all production VLANs documented?", "which devices are missing an owner?". This recipe shows how to run those checks as read-only agent workflows against Infrahub. - [Cross-system correlation](/mcp/use-cases/cross-system-correlation.md): Infrastructure data doesn't live in one place. A ticket in Jira mentions a device, a Git commit touches a configuration file, an Ansible run fails against a hostname. Connecting those dots — "which device is this about, what depends on it, what changed recently?" — is a natural fit for an agent that can read Infrahub through MCP and your other systems through their own tools. - [Natural language to GraphQL](/mcp/use-cases/natural-language-graphql.md): getnodes and searchnodes handle most everyday queries, but some questions need the full expressive power of GraphQL: aggregates, deep joins, multi-kind filters, and computed fields. This recipe shows how an agent goes from a free-form English question to a working query_graphql call, using the server's own schema as the grounding. - [Safe changes via branch isolation](/mcp/use-cases/safe-changes-branch-isolation.md): Branch isolation is the single most important safety property of the Infrahub MCP server: an agent can never write directly to your default branch. Every mutation lives on an ephemeral session branch that only becomes part of the default branch once a human reviews and merges a Proposed Change. This page explains the model end-to-end and shows how to tune it. - [Troubleshooting data queries](/mcp/use-cases/troubleshooting-queries.md): When an agent answers "I couldn't find that device" — it's almost always a data-query problem, not a data problem. This recipe walks through the diagnostic flow for "why is my query returning nothing?" ## nornir This integration bridges Nornir's powerful automation framework with Infrahub's next-generation infrastructure data management platform. - [Nornir Integration with Infrahub](/nornir.md): This integration bridges Nornir's powerful automation framework with Infrahub's next-generation infrastructure data management platform. ### getting-started This guide helps you set up your first integration between Nornir and Infrahub. By the end, you'll have a working automation setup that demonstrates the power of dynamic inventory management. - [Getting Started](/nornir/getting-started.md): This guide helps you set up your first integration between Nornir and Infrahub. By the end, you'll have a working automation setup that demonstrates the power of dynamic inventory management. ### guides - [How to Configure Schema Mappings](/nornir/guides/configuring-schema-mappings.md): This guide shows you how to configure schema mappings to control how Infrahub data translates into Nornir inventory properties. Schema mappings are the bridge between your Infrahub data model and Nornir's expected host attributes. - [How to Create Your First Automation Workflow](/nornir/guides/your-first-automation-workflow.md): This guide walks you through building a complete network automation workflow using Infrahub as your inventory source and artifact management system. You'll retrieve device configurations managed by Infrahub. ### references - [Artifact management plugin](/nornir/references/plugins/artifact_tasks.md): regeneratehostartifact - [CoreFileObject management tasks](/nornir/references/plugins/file_object_tasks.md): uploadfileobject - [Inventory plugin](/nornir/references/plugins/infrahub_inventory.md): InfrahubInventory ### topics - [Understanding Artifact Lifecycle Management](/nornir/topics/artifact-lifecycle-management.md): This topic explores how Infrahub's artifact system works with Nornir to enable sophisticated configuration management workflows. Understanding artifact lifecycle management is crucial for implementing robust, auditable network automation. - [Infrahub Inventory Concepts](/nornir/topics/infrahub-inventory-concepts.md): This topic provides a deep understanding of how Infrahub's graph database model maps to Nornir's inventory concepts. Understanding these mappings is essential for designing effective automation workflows. - [Understanding the Nornir-Infrahub Integration](/nornir/topics/understanding-nornir-infrahub-integration.md): This topic explains the core concepts behind the Nornir-Infrahub integration, how it works, and why it represents a paradigm shift in network automation. ## python-sdk ### guides - [Executing queries in a batch](/python-sdk/guides/batch.md): The Python SDK client allows you to group the execution of multiple queries in a batch. - [Branch management](/python-sdk/guides/branches.md): The Python SDK provides multiple methods to manage the branches in an Infrahub instance. - [How to create and configure an Infrahub client](/python-sdk/guides/client.md): This guide shows you how to create and configure an Infrahub client using the Python SDK. You'll learn how to set up authentication, configure proxy settings, and customize client behavior for your infrastructure automation workflows. - [Create, update and deleting nodes](/python-sdk/guides/create_update_delete.md): We will be using the following schema in this guide: - [Traversing the graph](/python-sdk/guides/graph_traversal.md): Graph traversal lets you discover how nodes are connected without knowing the relationship path in advance. The SDK exposes two client methods: - [Installing infrahub-sdk](/python-sdk/guides/installation.md): The Infrahub SDK for Python is available on PyPI and can be installed using the pip package installer. It is recommended to install the SDK into a virtual environment. - [Using the object-storage](/python-sdk/guides/object-storage.md): The Python SDK can be used to interface with Infrahub's object-storageartifact-file-storage/overview). - [Overview](/python-sdk/guides/python-typing.md): This guide explains how to use Python's type system effectively with the Infrahub SDK, focusing on the use of Protocols for type-safe development. - [Querying data in Infrahub](/python-sdk/guides/query_data.md): We can query data in 3 ways using the SDK: - [Using Resource Managers](/python-sdk/guides/resource-manager.md): The goal of this guide is to show you how to create a resource pool using the Python SDK, and how you can allocated resources with them. - [Using the client store](/python-sdk/guides/store.md): The client in the SDK contains a store that is used to store objects in a local cache. - [Using the client tracking mode](/python-sdk/guides/tracking.md): The Python SDK provides a feature known as Tracking Mode. This mode allows for the aggregation and tracking of operations performed during a session, enhancing efficiency and data management. ### introduction The Infrahub Python SDK is a client library for interacting with Infrahub programmatically. It handles authentication, query construction, and data serialization so you can work with infrastructure data using native Python objects instead of raw API calls. - [Infrahub Python SDK](/python-sdk/introduction.md): The Infrahub Python SDK is a client library for interacting with Infrahub programmatically. It handles authentication, query construction, and data serialization so you can work with infrastructure data using native Python objects instead of raw API calls. ### reference - [Compatibility matrix](/python-sdk/reference/compatibility.md): This page documents which versions of the Infrahub Python SDK are compatible with each version of Infrahub. - [Python SDK Configuration](/python-sdk/reference/config.md): The Python SDK (Async or Sync) client can be configured using an instance of the Config class. - [Python SDK Templating](/python-sdk/reference/templating.md): Filters can be used when defining computed attributes or Jinja2 Transforms within Infrahub. ### sdk_ref - [infrahub_sdk.client](/python-sdk/sdk_ref/infrahub_sdk/client.md): Classes - [infrahub_sdk.graph_traversal.models](/python-sdk/sdk_ref/infrahub_sdk/graph_traversal/models.md): Pydantic models for the Infrahub graph-traversal queries (Infrahub 1.10+). - [infrahub_sdk.graph_traversal.query](/python-sdk/sdk_ref/infrahub_sdk/graph_traversal/query.md): GraphQL query strings and variable builders for graph traversal (Infrahub 1.10+). - [infrahub_sdk.node.node](/python-sdk/sdk_ref/infrahub_sdk/node.md): Classes - [infrahub_sdk.node.attribute](/python-sdk/sdk_ref/infrahub_sdk/node/attribute.md): Classes - [infrahub_sdk.node.constants](/python-sdk/sdk_ref/infrahub_sdk/node/constants.md): This module is empty or contains only private/internal implementations. - [infrahub_sdk.node.metadata](/python-sdk/sdk_ref/infrahub_sdk/node/metadata.md): Classes - [infrahub_sdk.node.parsers](/python-sdk/sdk_ref/infrahub_sdk/node/parsers.md): Functions - [infrahub_sdk.node.property](/python-sdk/sdk_ref/infrahub_sdk/node/property.md): Classes - [infrahub_sdk.node.related_node](/python-sdk/sdk_ref/infrahub_sdk/node/related_node.md): Classes - [infrahub_sdk.node.relationship](/python-sdk/sdk_ref/infrahub_sdk/node/relationship.md): Classes ### topics - [Manage data with Object files](/python-sdk/topics/object_file.md): Introduction - [Understanding tracking in the Python SDK](/python-sdk/topics/tracking.md): Introduction ## schema-library Welcome to the Schema Library for Infrahub! This repository offers a collection of schemas designed to streamline and standardize infrastructure-related data structures. - [Schema library for Infrahub](/schema-library.md): Welcome to the Schema Library for Infrahub! This repository offers a collection of schemas designed to streamline and standardize infrastructure-related data structures. ### contributing We welcome contributions and feedback! Please open an issue or submit a pull request to suggest additions, improvements, or to report bugs. - [Contributing](/schema-library/contributing.md): We welcome contributions and feedback! Please open an issue or submit a pull request to suggest additions, improvements, or to report bugs. ### reference - [Azure](/schema-library/reference/azure.md): This schema extension introduces cloud support for Microsoft Azure. - [Cable](/schema-library/reference/cable.md): This schema extension contains a basic Cable model allowing you to connect two endpoints. - [Circuit](/schema-library/reference/circuit.md): This schema extension contains Circuits and ways to connect them with your infrastructure! The circuit could be a fiber connecting two sites, you would then have two endpoints, one on each site. - [Circuit Contract](/schema-library/reference/circuit_contract.md): This schema extension provides models for managing Circuit Contracts, enabling structured representation of service agreements with network providers. Compatible with Infrahub 1.8. - [Circuit Service](/schema-library/reference/circuit_service.md): This schema extension contains model coming on top of circuit to capture a single service shared across multiple circuits. - [Cluster](/schema-library/reference/cluster.md): This schema extension contains the foundations to capture clusters. With this one in place you can unlock various clusters flavors (hosting cluster able to host VMs, firewall clusters built with specific appliances ...) - [Compute](/schema-library/reference/compute.md): With this schema extension in place you will be able to capture all your physical servers. It also gives you the baseline to build virtualization. You might consider HostingCluster extension to go with! - [Cross Connect](/schema-library/reference/cross_connect.md): This extension contains schema to capture Cross Connect. You can see it as "a cable operated by a provider". You will be able to attach it to a location and then connect endpoints to it (e.g. rear interface of a patch panel, circuit endpoint ...) - [DCIM](/schema-library/reference/dcim.md): Basic DCIM schema to capture devices, racks, interfaces, and related information. - [DWDM](/schema-library/reference/dwdm.md): This schema extension contains models for OADM (Optical Add Drop Multiplexer) supporting various WDM (Wavelength Division Multiplexing) technologies such as DWDM (Dense Wavelength Division Multiplexing) or CWDM (Coarse Wavelength Division Multiplexing). With some vendors, the tunable optics are not configured via the channel number but via the wavelength and/or the frequency. This model provides a unique entry in Infrahub for those. - [Firewall Policer](/schema-library/reference/firewall_policer.md): This schema extension contains models for VMs. You might consider Cluster or/and Hypervisor extension to go with! - [Hosting Cluster](/schema-library/reference/hosting_cluster.md): A rather generic cluster built with compute units (e.g. servers) and able to host VMs. - [Infiniband](/schema-library/reference/infiniband.md): This schema extension adds support for InfiniBand switches. - [Interface Breakout](/schema-library/reference/interface_breakout.md): This schema extension introduces relationships to support breakout interfaces, enabling you to document the breakout of a physical interface into smaller physical interfaces. - [IPAM](/schema-library/reference/ipam.md): Basic IPAM schema to capture IP addresses, subnets, and related information. - [Lag](/schema-library/reference/lag.md): This schema extension includes models for Link Aggregation Groups (LAGs), enabling you to link physical interfaces as building blocs of your LAG interface. It can be used in standard networking environments as well as in compute scenarios, such as capturing bond interfaces. - [Locations](/schema-library/reference/location.md): Basic Location schema to capture locations, sites, and related information. - [Location Extended](/schema-library/reference/location_extended.md): This schema extension is the most detailed when it comes to location, you'll find all the layers you can think of. - [Location Minimal](/schema-library/reference/location_minimal.md): This schema extension is minimal but will provide you with basic items to store location related data. - [MLAG](/schema-library/reference/mlag.md): This schema extension contains the foundations to capture Multi-Chassis Link Aggregation Group (MLAG). It comes on top of the LAG extension. - [Modules](/schema-library/reference/modules.md): This schema extension allows you to capture Device Modules related information like the serial number or the status. You can insert the Module into a Dcim Physical Device. - [Modules Linecards](/schema-library/reference/modules_linecards.md): This schema extension allows you to capture Linecard related information like the version. You can insert the Linecard into a Dcim Physical Device and leverage the Linecard type model. The Linecard can accept PIC to help configure PORT information like breakout-capabilities and configurations. - [Modules Routing Engine](/schema-library/reference/modules_routing_engine.md): This schema extension allows you to capture Routing Engine related information like the version. You can insert the Routing Engine into a Dcim Physical Device and leverage the Routing Engine type model. - [Optical Transport](/schema-library/reference/optical_transport.md): Comprehensive optical transport network schemas for DWDM/WDM systems (ADVA FSP 3000 and similar platforms). Covers four layers: wavelength (ITU-T G.694.1 grid, optical bands, DWDM channels), topology (logical optical nodes, passive multiplexers, fiber links), equipment (transponder/amplifier/ROADM modules, ROADM degrees, WSS cross-connects), and service (end-to-end optical services, optical paths, path segments). Not designed to be loaded together with extensions/dwdm. - [Organization](/schema-library/reference/organization.md): Basic Organization schema to capture organizations, vendors, and related information. - [Patch Panel](/schema-library/reference/patch_panel.md): This schema extension allows you to capture patch panel related information like rear/front interfaces and mapping between them. You can insert the patch panel into a rack and leverage the device type model. Finally you can also capture information about potential modules you would insert into your patch panel. - [Peering IXP](/schema-library/reference/peering_ixp.md): This schema extension contains all you need to model anything revolving around internet peering (Exchange points ...)! - [Physical Disk](/schema-library/reference/physical_disk.md): Simple schema allowing you to capture physical disks information for the sake of inventory and lifecycle management. - [QinQ](/schema-library/reference/qinq.md): This schema extension brings extensions to VLAN model in order to support QinQ. - [QoS](/schema-library/reference/qos.md): This schema extension contains models for Quality of Service (QoS) - [Routing](/schema-library/reference/routing.md): This schema extension contains generics to create Routing Protocol "Instance". The idea is to create one Routing Protocol instance per IpamVRF + DcimDevice pair. - [Routing Aggregate](/schema-library/reference/routing_aggregate.md): This schema extension contains all you need to model the Aggregate Routing Protocol. - [Routing BGP](/schema-library/reference/routing_bgp.md): This schema extension contains all you need to model your BGP platform. - [Routing BGP Community](/schema-library/reference/routing_bgp_community.md): This schema extension adds the BGP Communities models. - [Routing BGP RR](/schema-library/reference/routing_bgp_rr.md): This schema extension extend the BGP extension to add BGP Route Reflector Clustering. - [Routing OSPF](/schema-library/reference/routing_ospf.md): This schema extension contains all you need to model the OSPF Routing Protocol. - [Routing PIM](/schema-library/reference/routing_pim.md): This schema extension contains all you need to model the PIM Protocol. - [Routing Policies](/schema-library/reference/routing_policies.md): This schema extension contains a generic to create Routing Policies. This Generic can be extend for each Routing Protocols you may want to use. - [Routing Policies Aggregate](/schema-library/reference/routing_policies_aggregate.md): This extension is using the Routing Policies extensions and the Routing Aggregate one together. - [Routing Policies BGP](/schema-library/reference/routing_policies_bgp.md): This extension is using the Routing Policies extensions and the Routing BGP one together. - [Routing Policies OSPF](/schema-library/reference/routing_policies_ospf.md): This extension is using the Routing Policies extensions and the Routing OSPF one together. - [Routing Policies (PIM)](/schema-library/reference/routing_policies_pim.md): This schema inherits the RoutingPolicy schema and removes importpolicies and exportpolicies attributes. However it adds a number of relationships to RoutingPIM. - [Security](/schema-library/reference/security.md): This schema extension contains models for implementing detailed security. - [SFP](/schema-library/reference/sfp.md): This schema extension gives you all the models you need to document Small Form-factor Pluggable (SFP). - [SNMP](/schema-library/reference/snmp.md): This schema extension contains models for SNMP Communities and SNMP Clients. As you can see this extension is not linked to Tenancy or Device, as you could decide to link the Community to different models based on your use case. - [Tenancy](/schema-library/reference/tenancy.md): This schema extension introduces tenancy for some of the schema nodes (circuits...) - [Topology](/schema-library/reference/topology.md): This schema extension introduces abstract network pods and services running in the pods, such as MPLS and EVPN. - [Users](/schema-library/reference/users.md): This schema extension contains models for account management. - [VLAN](/schema-library/reference/vlan.md): This schema extension contains models to support VLANs in you network. - [VLAN Translation](/schema-library/reference/vlan-translation.md): This schema extension is based on Juniper VLAN MAP, and not yet test out for other vendors. - [VRF](/schema-library/reference/vrf.md): This schema extension contains models to support VRF in your network. - [VRRP](/schema-library/reference/vrrp.md): This schema extension contains models for VRRP. ## skills Infrahub Skills is an open-source AI skills package that gives your AI coding assistant built-in knowledge of Infrahub's data model, conventions, and workflow patterns. Instead of learning the platform from documentation alone, describe what you want to build in plain language and the skills produce valid, best-practice Infrahub resources — schemas, object data, generators, transforms, checks, and menus — ready to load into a running instance. - [Infrahub Skills](/skills.md): Infrahub Skills is an open-source AI skills package that gives your AI coding assistant built-in knowledge of Infrahub's data model, conventions, and workflow patterns. Instead of learning the platform from documentation alone, describe what you want to build in plain language and the skills produce valid, best-practice Infrahub resources — schemas, object data, generators, transforms, checks, and menus — ready to load into a running instance. ### how-it-works Infrahub Skills gives an AI assistant domain-specific knowledge about the Infrahub platform. Each skill is a structured set of Markdown files — rules, examples, and references — that the AI reads at the point it's needed. The assistant matches each user request to the relevant skill automatically, loads only the context required for that task, and applies the rules embedded in the skill. - [How It Works](/skills/how-it-works.md): Infrahub Skills gives an AI assistant domain-specific knowledge about the Infrahub platform. Each skill is a structured set of Markdown files — rules, examples, and references — that the AI reads at the point it's needed. The assistant matches each user request to the relevant skill automatically, loads only the context required for that task, and applies the rules embedded in the skill. ### installation-setup Infrahub Skills can be installed into any AI tool that supports skills or custom context files. Installation takes one command for most setups — install into a specific Infrahub repository to work in that project, or install globally to use the skills across all your projects. - [Installation & Setup](/skills/installation-setup.md): Infrahub Skills can be installed into any AI tool that supports skills or custom context files. Installation takes one command for most setups — install into a specific Infrahub repository to work in that project, or install globally to use the skills across all your projects. ### release-notes What changed in each release of Infrahub Skills, and what is different about your workflow after upgrading. Newest first. - [Release Notes](/skills/release-notes.md): What changed in each release of Infrahub Skills, and what is different about your workflow after upgrading. Newest first. - [Release 0.0.1](/skills/release-notes/release-0_0_1.md): The first release of Infrahub Skills — eight skills that build, query, and audit Infrahub resources from natural language. - [Release 1.2.0](/skills/release-notes/release-1_2_0.md): Version realignment from 0.0.1 to 1.2.0 ahead of the automated release tooling. No skill changes. - [Release 1.2.1](/skills/release-notes/release-1_2_1.md): A documentation site for the skills package — installation, how-it-works, and a per-skill reference. - [Release 1.2.2](/skills/release-notes/release-1_2_2.md): New managing-schemas rules for production patterns, plus relationship and check-registration fixes. - [Release 1.2.3](/skills/release-notes/release-1_2_3.md): Every managing-* skill appears again after a fresh install; a frontmatter field had hidden six of them. - [Release 1.2.4](/skills/release-notes/release-1_2_4.md): analyzing-data now answers the question you asked instead of a built-in example when none is passed. - [Release 1.2.5](/skills/release-notes/release-1_2_5.md): Report Infrahub bugs and feature requests with the reporting-issues skill, sanitized and routed automatically. - [Release 1.2.6](/skills/release-notes/release-1_2_6.md): A diagnostics skill, CoreFileObject schema support, and a branch-first default for every data change. - [Release 1.2.7](/skills/release-notes/release-1_2_7.md): Build generators that reuse query results instead of requesting each related object separately. ### skills-reference - [Data Analyzer](/skills/skills-reference/analyzing-data.md): The Data Analyzer skill queries and analyzes data from a live Infrahub instance using the Infrahub MCP server. It answers operational questions that span multiple node types — correlating data, detecting drift, tracing service impact, auditing data quality — without requiring the user to write GraphQL queries manually. - [Repo Auditor](/skills/skills-reference/auditing-repo.md): The Repo Auditor performs a comprehensive audit of an Infrahub repository against all Infrahub best practices. It checks schema files, object files, Python components (checks, generators, transforms), .infrahub.yml registration, and project structure — then generates a report identifying issues and explaining what to fix and why. - [Data Importer](/skills/skills-reference/importing-data.md): The Data Importer turns CSV or TSV inputs into Infrahub object YAML and loads it onto a fresh branch. It introspects the live schema, maps columns to attributes by heuristic plus an up-front interview, splits denormalized inputs across the right kinds with the correct load order, and fails closed when a column has no schema home — without ever proposing a schema edit. - [Check Manager](/skills/skills-reference/managing-checks.md): The Check Manager produces Python validation checks that run in Infrahub's proposed change pipeline. Each check consists of three components: a GraphQL query file, a Python class inheriting from InfrahubCheck, and a .infrahub.yml registration entry. The skill generates all three and ensures they are correctly wired together. - [Generator Manager](/skills/skills-reference/managing-generators.md): The Generator Manager produces Python generators that query Infrahub for design objects and automatically create or update the infrastructure objects derived from them. Generators are idempotent: re-running them updates existing objects rather than creating duplicates. The skill generates the Python class, GraphQL query, and .infrahub.yml registration. - [Menu Manager](/skills/skills-reference/managing-menus.md): The Menu Manager produces YAML menu definition files that customize the Infrahub web UI sidebar. Menus can be flat or deeply nested, with group headers, icons, and ordering. The skill generates syntactically correct menu YAML and handles the nesting structure that is easy to get wrong by hand. - [Object Manager](/skills/skills-reference/managing-objects.md): The Object Manager produces YAML data files for Infrahub infrastructure objects — devices, locations, organizations, interfaces, modules, and any custom node type. It applies the correct file structure, value mappings, and load order so files are ready to load into a running Infrahub instance. - [Schema Manager](/skills/skills-reference/managing-schemas.md): The Schema Manager produces valid Infrahub schema YAML from natural language descriptions. It applies Infrahub's naming conventions, relationship rules, attribute type requirements, and display property patterns automatically — without requiring manual study of the schema format. - [Transform Manager](/skills/skills-reference/managing-transforms.md): The Transform Manager produces data transforms that convert Infrahub data into other formats — device configurations, JSON exports, CSV reports, and more. Transforms can be implemented as Python classes, Jinja2 templates, or a hybrid of both. The skill generates the transform code, GraphQL query, optional Jinja2 templates, and .infrahub.yml registration. ### spec-driven-development Spec-Driven Development (SDD) is a structured planning mode for complex or multi-part Infrahub builds. Instead of generating files immediately, the AI reasons through requirements with you first — capturing what needs to be built, validating the approach against Infrahub conventions, breaking the work into discrete tasks, and only generating once the plan is approved. - [Spec-Driven Development](/skills/spec-driven-development.md): Spec-Driven Development (SDD) is a structured planning mode for complex or multi-part Infrahub builds. Instead of generating files immediately, the AI reasons through requirements with you first — capturing what needs to be built, validating the approach against Infrahub conventions, breaking the work into discrete tasks, and only generating once the plan is approved. ## sync Infrahub Sync synchronizes infrastructure data between Infrahub and external systems — NetBox, Nautobot, IP Fabric, Slurp’it, Cisco ACI, Peering Manager, ServiceNow-style CMDBs, and any system with a REST/JSON API. Use it to migrate from a legacy system of record, run two systems side-by-side, or keep Infrahub in sync with another source of truth. Define a sync project declaratively in YAML, and the CLI generates the adapter code, calculates the diff, and applies only the deltas on each run. - [Infrahub Sync](/sync.md): Infrahub Sync synchronizes infrastructure data between Infrahub and external systems — NetBox, Nautobot, IP Fabric, Slurp’it, Cisco ACI, Peering Manager, ServiceNow-style CMDBs, and any system with a REST/JSON API. Use it to migrate from a legacy system of record, run two systems side-by-side, or keep Infrahub in sync with another source of truth. Define a sync project declaratively in YAML, and the CLI generates the adapter code, calculates the diff, and applies only the deltas on each run. ### adapters - [Cisco ACI adapter](/sync/adapters/aci.md): What is Cisco ACI? - [Choose an adapter](/sync/adapters/choosing-an-adapter.md): Infrahub Sync includes adapters for the most common infrastructure systems and a Generic REST API adapter for everything else. For systems with non-standard APIs, build a custom adapter. - [Device42 adapter](/sync/adapters/device42.md): What is Device42? - [GenericRestAPI adapter](/sync/adapters/genericrestapi.md): The GenericRestAPI adapter is a flexible, configurable adapter that can connect to any REST API following common patterns. It's designed to reduce code duplication and provide a foundation for creating adapters for new systems without having to build them from scratch. - [Infrahub adapter](/sync/adapters/infrahub.md): What is Infrahub? - [IP Fabric adapter](/sync/adapters/ipfabric.md): What is IP Fabric? - [LibreNMS adapter](/sync/adapters/librenms.md): What is LibreNMS? - [Local Adapters](/sync/adapters/local-adapters.md): Using local adapters - [Nautobot adapter](/sync/adapters/nautobot.md): What is Nautobot? - [NetBox adapter](/sync/adapters/netbox.md): What is NetBox? - [Observium adapter](/sync/adapters/observium.md): What is Observium? - [Peering Manager adapter](/sync/adapters/peering-manager.md): What is Peering Manager? - [PeeringDB adapter](/sync/adapters/peeringdb.md): What is PeeringDB? - [Prometheus adapter](/sync/adapters/prometheus.md): What is Prometheus? - [Slurp’it adapter](/sync/adapters/slurpit.md): What is Slurp’it? ### contributing This guide covers how to set up a development environment for infrahub-sync and contribute to the project. For the release runbook, see RELEASING.md at the repository root — that's maintainer-only. - [Contributing](/sync/contributing.md): This guide covers how to set up a development environment for infrahub-sync and contribute to the project. For the release runbook, see RELEASING.md at the repository root — that's maintainer-only. ### creating-a-sync-project A sync project is a directory containing a config.yml file that defines one synchronization between two systems. Configure four parts: source and destination, sync order, schema mapping, and sync behavior. A Nautobot → Infrahub example runs through each. - [Create a sync project](/sync/creating-a-sync-project.md): A sync project is a directory containing a config.yml file that defines one synchronization between two systems. Configure four parts: source and destination, sync order, schema mapping, and sync behavior. A Nautobot → Infrahub example runs through each. ### custom-certificates For sync sources or destinations that use TLS certificates signed by an internal CA — common in enterprise networks — install the CA in the system trust store, then point Python's HTTP client at that store. - [Use custom CA certificates](/sync/custom-certificates.md): For sync sources or destinations that use TLS certificates signed by an internal CA — common in enterprise networks — install the CA in the system trust store, then point Python's HTTP client at that store. ### installation Infrahub Sync is available on PyPI and can be installed using the pip package installer. It is recommended to install the Sync into a virtual environment. - [Install Infrahub Sync](/sync/installation.md): Infrahub Sync is available on PyPI and can be installed using the pip package installer. It is recommended to install the Sync into a virtual environment. ### migrating-from-netbox-or-nautobot Migrate data from NetBox or Nautobot into Infrahub one model at a time. The existing system keeps running throughout, and you decide when — or whether — to retire it. - [Migrate from NetBox or Nautobot](/sync/migrating-from-netbox-or-nautobot.md): Migrate data from NetBox or Nautobot into Infrahub one model at a time. The existing system keeps running throughout, and you decide when — or whether — to retire it. ### orchestration Infrahub Sync runs as a single CLI command. Schedule it with whatever tooling already runs scheduled jobs in your environment — cron, CI, Prefect, Dagster, or a homegrown runner. - [Schedule sync runs](/sync/orchestration.md): Infrahub Sync runs as a single CLI command. Schedule it with whatever tooling already runs scheduled jobs in your environment — cron, CI, Prefect, Dagster, or a homegrown runner. ### reference - [Cache layout](/sync/reference/cache-layout.md): infrahub-sync diff and infrahub-sync apply persist run state under: - [infrahub-sync](/sync/reference/cli.md): Infrahub-sync: synchronize data between infrastructure sources and destinations. - [Sync instance configuration](/sync/reference/config.md): The configuration file allows you to define the resources needs for the sync. - [Incremental Extraction](/sync/reference/incremental-extraction.md): infrahub-sync can skip re-extracting unchanged data on warm runs by - [Schema mapping reference](/sync/reference/schema-mapping.md): The schema mapping section of a sync project's config.yml defines how data translates between the source system and the destination system. Worked examples for NetBox → Infrahub and Nautobot → Infrahub appear at the end. ### release-notes - [Release Notes](/sync/release-notes.md) - [Infrahub Sync](/sync/release-notes/infrahub-sync.md) - [Release 1.5.6](/sync/release-notes/infrahub-sync/release-1_5_6.md): Release Number - [Release 1.6.0](/sync/release-notes/infrahub-sync/release-1_6_0.md): Release Number - [Release 2.0.0](/sync/release-notes/infrahub-sync/release-2_0_0.md): Release Number ### running-a-sync Learn how to use Infrahub Sync's commands to calculate differences, synchronize data, and apply previously cached plans against your destination. - [Run a sync](/sync/running-a-sync.md): Learn how to use Infrahub Sync's commands to calculate differences, synchronize data, and apply previously cached plans against your destination. ### using-netbox-or-nautobot-with-infrahub If you're adopting Infrahub but already have an established system of record in NetBox or Nautobot — with accumulated data, custom fields, and automation built around it — run Infrahub side-by-side with the existing tool and use Infrahub Sync to keep both systems holding the same data. - [Use NetBox or Nautobot with Infrahub](/sync/using-netbox-or-nautobot-with-infrahub.md): If you're adopting Infrahub but already have an established system of record in NetBox or Nautobot — with accumulated data, custom fields, and automation built around it — run Infrahub side-by-side with the existing tool and use Infrahub Sync to keep both systems holding the same data. ## vscode Infrahub VSCode extension - your development companion for infrastructure automation - [VSCode Extension](/vscode.md): Infrahub VSCode extension - your development companion for infrastructure automation ### guides - [How to Configure Multiple Infrahub Servers](/vscode/guides/configure-multiple-servers.md): Set up connections to development, staging, and production Infrahub servers for seamless environment switching - [How to Execute GraphQL Queries](/vscode/guides/execute-graphql-queries.md): Run GraphQL queries against Infrahub servers with variable support and branch selection - [How to Manage Branches](/vscode/guides/manage-branches.md): Create, delete, and work with Infrahub branches directly from VSCode - [How to Run Transforms and Artifacts](/vscode/guides/running-transforms.md): Execute Jinja2 and Python transforms directly from VSCode with automatic command selection and variable support - [How to use Infrahub snippets in VSCode](/vscode/guides/snippets.md): Step-by-step guide to inserting and customizing Infrahub YAML and automation snippets in Visual Studio Code - [How to Visualize Your Schema](/vscode/guides/visualize-schema.md): Explore and understand your Infrahub schema structure using the interactive graph visualizer ### reference - [Extension Commands and Settings Reference](/vscode/reference/commands-settings.md): Complete reference for all commands, settings, and configuration options in the Infrahub VSCode extension ### topics - [Understanding the Extension Architecture](/vscode/topics/extension-architecture.md): Deep dive into how the Infrahub VSCode extension is designed and how its components work together - [Schema Validation and YAML Intelligence](/vscode/topics/schema-validation.md): Understanding how the extension provides intelligent YAML editing and schema validation for Infrahub - [Security Configuration and Best Practices](/vscode/topics/security-configuration.md): Comprehensive guide to security settings, TLS configuration, and best practices for the Infrahub VSCode extension ### tutorials - [Getting Started with Infrahub VSCode Extension](/vscode/tutorials/getting-started.md): Learn how to install and configure the Infrahub VSCode extension for your first infrastructure automation project ## academy Infrahub Academy offers a collection of practical, hands-on tutorials designed to help you master Infrahub's powerful capabilities. These step-by-step guides will walk you through key features and workflows, providing real-world experience with the platform. - [Welcome to Infrahub academy](/academy.md): Infrahub Academy offers a collection of practical, hands-on tutorials designed to help you master Infrahub's powerful capabilities. These step-by-step guides will walk you through key features and workflows, providing real-world experience with the platform. ### getting-started - [Generate and deploy your first network configuration using Infrahub](/academy/getting-started/deploy-first-configuration.md): Learn how to design, build and deploy infrastructure configurations using Infrahub's artifact feature. - [Infrahub introduction](/academy/getting-started/infrahub-introduction.md): A comprehensive introduction to Infrahub, its core concepts, and how it revolutionizes infrastructure management. ### tutorials - [Build a check in Infrahub](/academy/tutorials/build-a-check.md): By the end of this tutorial you will have built, deployed, and validated a custom check that enforces a naming convention on tags. You will set up sample data, write a GraphQL query, implement the check logic in Python, configure it in .infrahub.yml, deploy it via a connected Git repository, and verify it runs against a proposed change. - [Build your first schema](/academy/tutorials/build-your-first-schema.md): By the end of this tutorial you will have a working schema for network devices and interfaces, covering nodes, attributes, relationships, and generic abstractions. You'll load each version into a branch and verify the results. - [Build chained generators](/academy/tutorials/generators/build-chained-generators.md): By the end of this tutorial you will have wired two layers of modular Generators together — fabric → pod → rack — using a checksum attribute and Infrahub's event framework. You will have added a GeneratorTarget generic with a checksum attribute to your downstream node kinds, implemented a GeneratorMixin that writes the checksum to downstream targets as the last step of generate(), built downstream Generators that validate upstream completeness before doing any work, and created CoreGeneratorAction and CoreNodeTriggerRule objects so the chain runs automatically when an upstream layer finishes. You will leave with a concrete model of how a checksum makes Generator chaining idempotent — re-runs that produce the same output do not re-trigger the next layer. - [Build your first generator](/academy/tutorials/generators/build-your-first-generator.md): By the end of this tutorial you will have built a working Generator end-to-end: modeled two object kinds, written a GraphQL query, implemented a Python Generator class, registered it in .infrahub.yml, run it locally with infrahubctl, and verified it runs automatically as part of a proposed change. You will leave with a Widget and Resource schema loaded into Infrahub and a Generator that creates Resource objects from each Widget's count attribute. - [Organize objects with groups](/academy/tutorials/groups.md): By the end of this tutorial you will have created a group, added two objects to it, and queried the result end-to-end. You'll leave with a concrete mental model of how groups work and where to go next. - [Build a Jinja2 Transformation](/academy/tutorials/transformations/build-a-jinja2-transformation.md): By the end of this tutorial you will have built a working Jinja2 Transformation end-to-end: loaded a small network-device schema, created a few sample devices, written a GraphQL query that filters by device name, written a Jinja template that renders a configuration snippet from the result, registered it in .infrahub.yml, tested it locally with infrahubctl render, added the repository to Infrahub, and called the render API. You'll leave with a deviceconfigtransform you can call against any device by name. - [Build a Python Transformation](/academy/tutorials/transformations/build-a-python-transformation.md): By the end of this tutorial you will have built a working Python Transformation end-to-end: loaded a small network-device schema, created a few sample devices, written a GraphQL query that filters by device name, implemented a DeviceConfigTransform Python class that returns a JSON object derived from the response, registered it in .infrahub.yml, tested it locally with infrahubctl transform, and called the Transformation via the REST API. You'll leave with both the raw-dictionary and SDK-converted patterns side by side so you can pick the one that fits the next Transformation you write. ## artifact-file-storage ### configure The storage API provides direct access to Infrahub's object storage layer for uploading and retrieving content by identifier. - [Configure storage](/artifact-file-storage/configure.md): The storage API provides direct access to Infrahub's object storage layer for uploading and retrieving content by identifier. ### overview Infrahub uses an object storage layer to persist binary and text content outside of the graph database. This layer stores the raw bytes of file objects and rendered artifacts. Separating file content from graph data allows Infrahub to keep the graph database focused on relationships, metadata, and version control while delegating bulk storage to a system optimized for that purpose. - [About storage](/artifact-file-storage/overview.md): Infrahub uses an object storage layer to persist binary and text content outside of the graph database. This layer stores the raw bytes of file objects and rendered artifacts. Separating file content from graph data allows Infrahub to keep the graph database focused on relationships, metadata, and version control while delegating bulk storage to a system optimized for that purpose. ## artifacts ### content-composition This guide shows you how to build a composite artifact whose Transformation pulls in content from other artifacts or file objects. This enables modular configuration pipelines where each artifact generates one section of a configuration, and a composite artifact assembles the final result. - [Composing artifact content](/artifacts/content-composition.md): This guide shows you how to build a composite artifact whose Transformation pulls in content from other artifacts or file objects. This enables modular configuration pipelines where each artifact generates one section of a configuration, and a composite artifact assembles the final result. ### overview An artifact is the result of a Transformation for a specific context and/or object. - [Artifacts](/artifacts/overview.md): An artifact is the result of a Transformation for a specific context and/or object. ### use Generate configuration files and other artifacts by combining Infrahub data with templates. This guide shows you how to create artifacts that automatically update when your infrastructure data changes. - [Use artifacts](/artifacts/use.md): Generate configuration files and other artifacts by combining Infrahub data with templates. This guide shows you how to create artifacts that automatically update when your infrastructure data changes. ## automation-and-outputs - [Design & Integrate](/automation-and-outputs.md) ## branches-and-change-control - [Branches & Change Control](/branches-and-change-control.md) ## branches ### create Start a new branch from the default branch (typically main or master). This creates a snapshot of the current state that can be modified independently. Creating a new branch is almost instantaneous because Infrahub uses copy-on-write semantics — no data is duplicated, only a pointer to the base timestamp and the delta of subsequent changes. - [Create a branch](/branches/create.md): Start a new branch from the default branch (typically main or master). This creates a snapshot of the current state that can be modified independently. Creating a new branch is almost instantaneous because Infrahub uses copy-on-write semantics — no data is duplicated, only a pointer to the base timestamp and the delta of subsequent changes. ### delete After a branch has served its purpose — whether it's been merged, abandoned, or superseded — it can be deleted. Deleting a branch that has not been merged will permanently discard all changes accumulated on it. - [Delete a branch](/branches/delete.md): After a branch has served its purpose — whether it's been merged, abandoned, or superseded — it can be deleted. Deleting a branch that has not been merged will permanently discard all changes accumulated on it. ### merge Once changes are complete, they merge back into the parent branch through a controlled process — almost always via a Proposed Change. This ensures that all modifications are validated and conflicts are resolved before integration. - [Merge a branch](/branches/merge.md): Once changes are complete, they merge back into the parent branch through a controlled process — almost always via a Proposed Change. This ensures that all modifications are validated and conflicts are resolved before integration. ### overview Infrahub's branching model allows teams to work on features, fixes, or experiments in isolated environments. Each branch represents a separate line of development, enabling parallel work streams without interference. Unlike traditional version control systems, Infrahub's approach combines the familiar Git branching concepts with the unique data management capabilities of its graph database foundation. - [Branches](/branches/overview.md): Infrahub's branching model allows teams to work on features, fixes, or experiments in isolated environments. Each branch represents a separate line of development, enabling parallel work streams without interference. Unlike traditional version control systems, Infrahub's approach combines the familiar Git branching concepts with the unique data management capabilities of its graph database foundation. ### rebase Rebasing is a powerful operation that updates a branch with the latest changes from its parent branch. This is essential for maintaining branch health and ensuring smooth integration of changes. - [Rebase a branch](/branches/rebase.md): Rebasing is a powerful operation that updates a branch with the latest changes from its parent branch. This is essential for maintaining branch health and ensuring smooth integration of changes. ### resolve-conflicts Conflicts occur when the same data changes in both a feature branch and the main branch. Infrahub's conflict management system is designed to identify these conflicts with precision and provide tools for effective resolution. - [Resolve conflicts](/branches/resolve-conflicts.md): Conflicts occur when the same data changes in both a feature branch and the main branch. Infrahub's conflict management system is designed to identify these conflicts with precision and provide tools for effective resolution. ## category ### advanced-schema-features - [Advanced schema features](/category/advanced-schema-features.md) ### authentication - [Authentication](/category/authentication.md) ### configuration-files - [Configuration Files](/category/configuration-files.md) ### display--presentation - [Display & presentation](/category/display--presentation.md) ### getting-started - [Getting Started](/category/getting-started.md) ### install--configure - [Install & configure](/category/install--configure.md) ### maintain--upgrade - [Maintain & upgrade](/category/maintain--upgrade.md) ### permissions - [Permissions](/category/permissions.md) ### run--observe - [Run & observe](/category/run--observe.md) ### schema-operations - [Schema operations](/category/schema-operations.md) ### tutorials - [Tutorials](/category/tutorials.md) ### user-management--security - [User Management & Security](/category/user-management--security.md) ## change-approval ### change-approval-workflow This guide walks you through implementing a change approval workflow in Infrahub. By the end, you'll have a structured process ensuring all infrastructure changes are properly reviewed and approved before being merged into production, enhancing your governance and safety practices. - [How to implement a change approval workflow](/change-approval/change-approval-workflow.md): This guide walks you through implementing a change approval workflow in Infrahub. By the end, you'll have a structured process ensuring all infrastructure changes are properly reviewed and approved before being merged into production, enhancing your governance and safety practices. ## checks ### overview Checks are user-defined logic, stored in an external repository linked to Infrahub, that run as part of a proposed change. They let users perform any kind of data validation logic during a proposed change. If a check does not complete successfully, the proposed change cannot be merged. - [Checks & Validation](/checks/overview.md): Checks are user-defined logic, stored in an external repository linked to Infrahub, that run as part of a proposed change. They let users perform any kind of data validation logic during a proposed change. If a check does not complete successfully, the proposed change cannot be merged. ## computed-attributes ### overview The computed attributes feature enables the dynamic calculation of attribute values based on user-defined logic. - [Computed attributes](/computed-attributes/overview.md): The computed attributes feature enables the dynamic calculation of attribute values based on user-defined logic. ## deploy-manage ### install-configure - [How to configure Infrahub](/deploy-manage/install-configure/configure-infrahub.md): This guide explains how to configure your Infrahub instance by setting environment variables that control various aspects of the system, including timeouts, security settings, and integration parameters. - [Hardware requirements](/deploy-manage/install-configure/hardware-requirements.md): This page outlines the hardware requirements for running Infrahub, including minimum and recommended specifications, enterprise sizing, cloud provider machine types, task manager database storage, and a utility for benchmarking your system's performance. - [Install Infrahub Community](/deploy-manage/install-configure/install/community.md): Infrahub Community is deployed as a container-based architecture and can be installed using several methods. - [Install Infrahub Enterprise Enterprise Edition](/deploy-manage/install-configure/install/enterprise.md): Infrahub Enterprise is based on the Community version, with several enhancements for: - [Observability stack](/deploy-manage/install-configure/install/observability-stack.md): Infrahub ships an observability stack that runs alongside Infrahub: - [Installation](/deploy-manage/install-configure/install/overview.md): Infrahub Community and Enterprise are deployed as container-based architectures. The installation methods below are for non-resilient deployments suitable for development, testing, and single-node production environments. - [High availability](/deploy-manage/install-configure/production-deployment/high-availability.md): This guide covers how to deploy Infrahub in a high availability configuration that eliminates single points of failure. - [Production deployment](/deploy-manage/install-configure/production-deployment/overview.md): This guide walks you through deploying Infrahub in a production environment with enhanced security, reliability, and maintainability. By following these steps, you'll set up a production-ready Infrahub instance that follows industry best practices. ### maintain-upgrade - [Backup and restore](/deploy-manage/maintain-upgrade/database-backup/backup-and-restore.md): This guide shows you how to create comprehensive backups of your Infrahub deployment and restore them when needed. You'll learn to backup the Neo4j graph database, object storage, and task management data to ensure complete data recovery capabilities. - [Cluster backup and restore Enterprise Edition](/deploy-manage/maintain-upgrade/database-backup/cluster-backup-and-restore.md): If you're running Infrahub with a Neo4j cluster, follow these steps to backup from one node and restore to another while maintaining cluster integrity. - [Understanding database backup and restore](/deploy-manage/maintain-upgrade/database-backup/overview.md): This topic explains how Infrahub's database backup and restore system works, the architectural decisions behind it, and the various approaches available for protecting your data. Understanding these concepts helps you make informed decisions about your backup strategy and troubleshoot issues when they arise. - [Upgrade Community](/deploy-manage/maintain-upgrade/upgrade/community.md): The process to migrate your instance of Infrahub to the latest version will vary depending on your deployment of Infrahub. However, at a high-level, it will involve getting the latest version and performing any needed Database Migrations and Schema updates. - [Upgrade Enterprise Enterprise Edition](/deploy-manage/maintain-upgrade/upgrade/enterprise.md): The process to migrate your instance of Infrahub Enterprise to the latest version will vary depending on your deployment of Infrahub Enterprise. However, at a high-level, it will involve getting the latest version and performing any needed Database Migrations and Schema updates. - [Upgrade the observability stack](/deploy-manage/maintain-upgrade/upgrade/observability-stack.md): Docker Compose - [Upgrade](/deploy-manage/maintain-upgrade/upgrade/overview.md): Upgrading Infrahub involves pulling the latest container images, running database and schema migrations, and restarting services. The specific steps vary by edition (Community vs Enterprise) and deployment method (Docker Compose vs Helm). ### run-observe - [Activity log](/deploy-manage/run-observe/activity-log.md): Changes (events) in Infrahub are documented in the Activity log. It helps you see which objects were impacted, when a change was made, and who made it. It can be used to troubleshoot unforeseen changes, audit previous operations, and comprehend the order of updates across various branches. - [Configure log forwarding Enterprise Edition](/deploy-manage/run-observe/log-forwarding/configure-log-forwarding.md): This guide walks through configuring Infrahub to forward audit events and application logs to external SIEM or syslog systems. For conceptual background on how log forwarding works, see the log forwarding overview. - [Log forwarding Enterprise Edition](/deploy-manage/run-observe/log-forwarding/overview.md): Log forwarding enables Infrahub to stream audit events and application logs to external systems via the syslog protocol. This supports compliance, security monitoring, and operational visibility by integrating with SIEM platforms and centralized log management. - [Tasks](/deploy-manage/run-observe/tasks.md): The Tasks system in Infrahub is designed to manage and control various backend operations with robust error reporting, improved supervision, and enhanced logging capabilities. - [Local Telemetry Storage](/deploy-manage/run-observe/telemetry.md): Infrahub stores a daily telemetry snapshot locally in the Neo4j database, regardless of whether remote telemetry reporting is enabled. This ensures all deployments - including air-gapped and opted-out environments - retain usage data for support, auditing, and license compliance. ### user-management - [Authentication](/deploy-manage/user-management/authentication.md): Infrahub provides flexible authentication options to fit various organizational needs. - [Advanced LDAP configuration Enterprise Edition](/deploy-manage/user-management/ldap/advanced-ldap.md): These options extend a working LDAP setup with directory group resolution, nested-group handling, server failover, and stricter TLS. Configure the connection and user lookup first — see Configure LDAP authentication. - [Configure LDAP authentication Enterprise Edition](/deploy-manage/user-management/ldap/configure-ldap.md): This guide explains how to connect Infrahub to your LDAP directory so users can sign in with their directory credentials. - [LDAP authentication Enterprise Edition](/deploy-manage/user-management/ldap/overview.md): With LDAP authentication, users sign in to Infrahub with the credentials from your existing directory — Active Directory, OpenLDAP, or any RFC 4510-compliant LDAP server. Use it when your organization manages identities in a central directory and does not run an OIDC or OAuth2 identity provider. - [Managing API tokens](/deploy-manage/user-management/managing-api-tokens.md): API tokens can be used as an authentication mechanism for Infrahub's REST- and GraphQL API, the Python SDK and infrahubctl. - [Manage accounts and permissions](/deploy-manage/user-management/permissions-roles/manage-accounts-and-permissions.md): In Infrahub, managing access and control starts with creating accounts, assigning them to groups, and managing their roles and permissions. - [Permissions and roles](/deploy-manage/user-management/permissions-roles/overview.md): Roles and permissions are essential for controlling user access and behavior in Infrahub. Within the platform, they provide precise control over what users can see, modify, or manage. - [Advanced SSO configuration](/deploy-manage/user-management/sso/advanced-sso.md): Multiple identity providers - [Configure SSO (Single sign-on)](/deploy-manage/user-management/sso/configure-sso.md): This guide walks you through configuring single sign-on (SSO) in Infrahub using OpenID Connect (OIDC) or OAuth2 authentication protocols. - [Single sign-on (SSO)](/deploy-manage/user-management/sso/overview.md): Single sign-on (SSO) allows users to authenticate once with an external identity provider and gain access to Infrahub without needing separate credentials. Infrahub integrates with popular identity providers — Microsoft Entra ID, Okta, Google Workspace, and others — through industry-standard protocols. ## deployment-and-management - [Deployment & Management](/deployment-and-management.md) ## development - [Contributing](/development.md) ### backend In order start developing on Infrahub backend, it is recommended to have a decent knowledge about topics such as Docker, Python and generally UNIX systems. Tools such as Docker and Python virtual environment help us in isolating the development work without interfering with the system itself. In this guide, we will use: - [Backend guide](/development/backend.md): In order start developing on Infrahub backend, it is recommended to have a decent knowledge about topics such as Docker, Python and generally UNIX systems. Tools such as Docker and Python virtual environment help us in isolating the development work without interfering with the system itself. In this guide, we will use: ### changelog Infrahub utilizes a tool called towncrier for Changelog management and generation. - [Changelog guide](/development/changelog.md): Infrahub utilizes a tool called towncrier for Changelog management and generation. ### docs Welcome to the Infrahub documentation guide. This document aims to answer any questions that may come up when creating or updating documentation. - [Documentation guide](/development/docs.md): Welcome to the Infrahub documentation guide. This document aims to answer any questions that may come up when creating or updating documentation. ### editor More details coming soon - [Visual Studio Code](/development/editor.md): More details coming soon ### frontend Welcome to the Infrahub frontend guide! This guide details the technologies and steps required to contribute effectively to the Infrahub frontend. - [Frontend guide](/development/frontend.md): Welcome to the Infrahub frontend guide! This guide details the technologies and steps required to contribute effectively to the Infrahub frontend. - [Getting set up with frontend](/development/frontend/getting-set-up.md): Make sure Infrahub Backend is up and running. If not, in your terminal execute: - [Running & writing tests for frontend](/development/frontend/testing-guidelines.md): If you have never run Infrahub tests before, we highly suggest following the frontend guide. ### git-best-practices This guide explains Git workflows and best practices when contributing to Infrahub, covering submodule management, branching strategies, and pull request workflows. - [How to work with Git when developing Infrahub](/development/git-best-practices.md): This guide explains Git workflows and best practices when contributing to Infrahub, covering submodule management, branching strategies, and pull request workflows. ### style-guide This guide defines writing style, capitalization, and grammar rules for Infrahub documentation. As a general rule, prefer consistency and simplicity when possible. For anything not answered below, reference the Microsoft Style Guide. - [Documentation style guide](/development/style-guide.md): This guide defines writing style, capitalization, and grammar rules for Infrahub documentation. As a general rule, prefer consistency and simplicity when possible. For anything not answered below, reference the Microsoft Style Guide. ## development-resources - [Development Resources](/development-resources.md) ### developer-guide Infrahub support various form of extensibility that rely on users providing their own code that then will be executed by Infrahub. - [Developer guide](/development-resources/developer-guide.md): Infrahub support various form of extensibility that rely on users providing their own code that then will be executed by Infrahub. ### graphql-fragments GraphQL fragments are reusable field selections that can be shared across multiple stored queries. Instead of duplicating the same nested field selections in every query, you define them once as a fragment and reference them using the standard GraphQL spread syntax (...fragmentName). - [Using GraphQL fragments](/development-resources/graphql-fragments.md): GraphQL fragments are reusable field selections that can be shared across multiple stored queries. Instead of duplicating the same nested field selections in every query, you define them once as a fragment and reference them using the standard GraphQL spread syntax (...fragmentName). ### graphql - [Working with groups](/development-resources/graphql/groups.md): Groups are first-class objects in Infrahub that can be queried and manipulated through GraphQL. Groups provide powerful ways to organize and operate on collections of infrastructure objects. - [GraphQL](/development-resources/graphql/overview.md): The GraphQL interface is the main interface to interact with Infrahub. The GraphQL schema is automatically generated based on the core models and the user-defined schema models. - [Queries & mutations](/development-resources/graphql/queries-and-mutations.md): In GraphQL, a query is used to fetch data and mutations are used to create/update or delete data. In Infrahub, a GraphQL query and 4 mutations will be generated for each model you define in the schema. The name of the query or mutation is based on the namespace and name of the model. - [Single-target queries](/development-resources/graphql/single-target-queries.md): When writing GraphQL queries for transformations, generators, artifacts, and computed attributes, it's critical to use a single-target query pattern to ensure proper tracking by the system. - [Stored queries](/development-resources/graphql/stored-queries.md): Infrahub can store GraphQL queries in the database to simplify execution and associate them with other internal objects such as Transformations. ### local-demo-environment A local environment based on Docker Compose is available for demo and testing. - [Local demo environment](/development-resources/local-demo-environment.md): A local environment based on Docker Compose is available for demo and testing. ### testcontainers The infrahub-testcontainers package starts a complete Infrahub stack in Docker during your test session. Use it to write integration tests that validate schemas, SDK usage, or GraphQL queries against a live Infrahub instance. - [Integration testing with Testcontainers](/development-resources/testcontainers.md): The infrahub-testcontainers package starts a complete Infrahub stack in Docker during your test session. Use it to write integration tests that validate schemas, SDK usage, or GraphQL queries against a live Infrahub instance. ## events ### event-actions An event action is a node you create in Infrahub that defines an outcome: add a device to a group, run a Generator definition. Actions on their own do nothing — they fire only when an event rule matches and points at them. - [Event actions](/events/event-actions.md): An event action is a node you create in Infrahub that defines an outcome: add a device to a group, run a Generator definition. Actions on their own do nothing — they fire only when an event rule matches and points at them. ### event-rules An event rule is a node you create in Infrahub that names a set of conditions and ties one or more actions to fire when those conditions match. Rules turn raw events into useful automation: instead of reacting to every infrahub.node.updated event, you describe the precise update you care about — "an InfraDevice whose status changed to active" — and Infrahub runs your action only when that pattern shows up. - [Event rules](/events/event-rules.md): An event rule is a node you create in Infrahub that names a set of conditions and ties one or more actions to fire when those conditions match. Rules turn raw events into useful automation: instead of reacting to every infrahub.node.updated event, you describe the precise update you care about — "an InfraDevice whose status changed to active" — and Infrahub runs your action only when that pattern shows up. ### event-system Infrahub emits a structured event every time a significant mutation happens in the system: a node is created, updated, or deleted; a branch is created, merged, rebased, or deleted; a group gains or loses a member; and many more. Events are the foundation of every event-driven feature — automation, webhook delivery, the activity log, and external SIEM forwarding all consume them. - [Event system](/events/event-system.md): Infrahub emits a structured event every time a significant mutation happens in the system: a node is created, updated, or deleted; a branch is created, merged, rebased, or deleted; a group gains or loses a member; and many more. Events are the foundation of every event-driven feature — automation, webhook delivery, the activity log, and external SIEM forwarding all consume them. ### overview Infrahub emits events on every significant mutation in the system — node creates, branch operations, group membership changes, and more. The event system lets you react to those events automatically: when a device is updated, when a circuit joins a provisioning group, when a branch merges. Common uses include keeping groups in sync as data changes, triggering Generators on attribute updates, and notifying external systems through webhooks. - [Events](/events/overview.md): Infrahub emits events on every significant mutation in the system — node creates, branch operations, group membership changes, and more. The event system lets you react to those events automatically: when a device is updated, when a circuit joins a provisioning group, when a branch merges. Common uses include keeping groups in sync as data changes, triggering Generators on attribute updates, and notifying external systems through webhooks. ## faq Here are the key details about Infrahub: - [Frequently asked questions](/faq.md): Here are the key details about Infrahub: ## generators ### build A Generator queries data and creates new nodes and relationships from the result. The steps below cover how to create one. - [Build a generator](/generators/build.md): A Generator queries data and creates new nodes and relationships from the result. The steps below cover how to create one. ### modular A Generator reads data from Infrahub and creates new objects based on the result. A single Generator works well when the scope is contained: one input kind, one set of outputs, no intermediate dependencies. - [Modular Generators](/generators/modular.md): A Generator reads data from Infrahub and creates new objects based on the result. A single Generator works well when the scope is contained: one input kind, one set of outputs, no intermediate dependencies. ### modular-best-practices The patterns below come from real-world experience building and operating modular Generator cascades in Infrahub. They address problems that are not obvious until you have built a multi-layer cascade and run it in production. - [Best practices for modular Generators](/generators/modular-best-practices.md): The patterns below come from real-world experience building and operating modular Generator cascades in Infrahub. They address problems that are not obvious until you have built a multi-layer cascade and run it in production. ### overview A Generator is a generic plugin that queries data and creates new nodes and relationships based on the result. - [Generators](/generators/overview.md): A Generator is a generic plugin that queries data and creates new nodes and relationships based on the result. ## git-integration ### branch-synchronization Infrahub automatically creates all branches from a connected Git repository by default. While this ensures full visibility, it can create unnecessary noise when many Git branches are unrelated to Infrahub data or workflows. Selective branch synchronization introduces an optional configuration that allows teams to control which Git branches are imported and synchronized into Infrahub based on custom naming patterns. - [Selective branch synchronization](/git-integration/branch-synchronization.md): Infrahub automatically creates all branches from a connected Git repository by default. While this ensures full visibility, it can create unnecessary noise when many Git branches are unrelated to Infrahub data or workflows. Selective branch synchronization introduces an optional configuration that allows teams to control which Git branches are imported and synchronized into Infrahub based on custom naming patterns. ### connect-repository Connecting an external Git repository will enable many features in Infrahub, such as Transformations, Generators, Checks ... that rely on the repository to store code files. - [How to connect external Git repositories](/git-integration/connect-repository.md): Connecting an external Git repository will enable many features in Infrahub, such as Transformations, Generators, Checks ... that rely on the repository to store code files. ### infrahub-yml The .infrahub.yml file serves as the central manifest that defines how Infrahub integrates with external Git repositories. This topic explains the role of this configuration file and the design philosophy behind its structure. - [Understanding the .infrahub.yml configuration file](/git-integration/infrahub-yml.md): The .infrahub.yml file serves as the central manifest that defines how Infrahub integrates with external Git repositories. This topic explains the role of this configuration file and the design philosophy behind its structure. ### overview Git Integration in Infrahub connects your data layer to external Git repositories, letting you store and version-control the code that drives Infrahub — transformations, generators, checks, and other automation resources — alongside your infrastructure data. This page covers the underlying concepts; specific tasks like connecting a repository, configuring .infrahub.yml, and filtering which branches sync live in the pages below. - [Git Integration](/git-integration/overview.md): Git Integration in Infrahub connects your data layer to external Git repositories, letting you store and version-control the code that drives Infrahub — transformations, generators, checks, and other automation resources — alongside your infrastructure data. This page covers the underlying concepts; specific tasks like connecting a repository, configuring .infrahub.yml, and filtering which branches sync live in the pages below. ## graph-traversal ### analyze-dependencies Use Dependency mode to find every object of a given kind that is reachable from a source object. - [Analyze dependencies](/graph-traversal/analyze-dependencies.md): Use Dependency mode to find every object of a given kind that is reachable from a source object. ### overview Use Graph Traversal to navigate the relationships in your infrastructure data — trace every path - [Graph Traversal](/graph-traversal/overview.md): Use Graph Traversal to navigate the relationships in your infrastructure data — trace every path ### query-with-graphql Both Graph Traversal modes are available programmatically — as GraphQL queries - [Query traversal with GraphQL](/graph-traversal/query-with-graphql.md): Both Graph Traversal modes are available programmatically — as GraphQL queries ### trace-a-path Use Path mode to find the paths between two objects in your infrastructure graph. - [Trace a path](/graph-traversal/trace-a-path.md): Use Path mode to find the paths between two objects in your infrastructure graph. ## groups ### add-members Attach existing objects to a Standard group. - [Add members to a group](/groups/add-members.md): Attach existing objects to a Standard group. ### create Create a Standard group when you want to hand-pick the objects it contains. - [Create a group](/groups/create.md): Create a Standard group when you want to hand-pick the objects it contains. ### delete Remove a group without affecting its member objects. - [Delete a group](/groups/delete.md): Remove a group without affecting its member objects. ### overview Groups are containers that establish relationships between objects in your infrastructure. They let you create logical collections that span different object types — you might group together devices, locations, and configurations that share a common purpose or characteristic. - [Groups](/groups/overview.md): Groups are containers that establish relationships between objects in your infrastructure. They let you create logical collections that span different object types — you might group together devices, locations, and configurations that share a common purpose or characteristic. ### query-members Read what's in a group, or find what groups an object belongs to. Use these patterns for auditing, verification, or feeding automation. - [Query group membership](/groups/query-members.md): Read what's in a group, or find what groups an object belongs to. Use these patterns for auditing, verification, or feeding automation. ### remove-members Detach objects from a Standard group without deleting the objects themselves. - [Remove members from a group](/groups/remove-members.md): Detach objects from a Standard group without deleting the objects themselves. ### use-in-automation Target a group from an artifact definition, Transformation, or Check so that the automation applies to every member without hard-coding a list. - [Use groups in automation](/groups/use-in-automation.md): Target a group from an artifact definition, Transformation, or Check so that the automation applies to every member without hard-coding a list. ## immutable-history ### overview At its foundation, Infrahub implements data immutability—a principle where information in the database cannot be deleted or modified in place. Instead, every change creates a new version while preserving all previous states. This approach mirrors version control systems like Git, providing a robust history of all infrastructure changes. - [Understanding immutable history in Infrahub](/immutable-history/overview.md): At its foundation, Infrahub implements data immutability—a principle where information in the database cannot be deleted or modified in place. Instead, every change creates a new version while preserving all previous states. This approach mirrors version control systems like Git, providing a robust history of all infrastructure changes. ## integrations-overview - [Integrations](/integrations-overview.md) ## ipam ### automate-with-resource-manager IPAM and Resource Manager are complementary layers. IPAM defines the data model — your prefix and address objects live in the graph, organized into namespaces and hierarchies. Resource Manager is the allocation engine — it draws from those prefix objects as pool sources to assign addresses and subnets automatically, without manual tracking. - [Automate with Resource Manager](/ipam/automate-with-resource-manager.md): IPAM and Resource Manager are complementary layers. IPAM defines the data model — your prefix and address objects live in the graph, organized into namespaces and hierarchies. Resource Manager is the allocation engine — it draws from those prefix objects as pool sources to assign addresses and subnets automatically, without manual tracking. ### building-your-schema An IPAM namespace is already provided with Infrahub out of the box, so there is no need to redefine it unless you require additional attributes. The default IpamNamespace implementation is minimal — it exposes only a name and a description. - [Building your IPAM schema](/ipam/building-your-schema.md): An IPAM namespace is already provided with Infrahub out of the box, so there is no need to redefine it unless you require additional attributes. The default IpamNamespace implementation is minimal — it exposes only a name and a description. ### ip-namespaces IP Namespaces are logical containers that segment and isolate IPAM resources. They allow you to manage separate pools of IP prefixes and addresses within the same Infrahub instance, preventing conflicts and enabling organizational separation of IP resources. - [IP Namespaces](/ipam/ip-namespaces.md): IP Namespaces are logical containers that segment and isolate IPAM resources. They allow you to manage separate pools of IP prefixes and addresses within the same Infrahub instance, preventing conflicts and enabling organizational separation of IP resources. ### overview IP address management (IPAM) in Infrahub gives you a structured, version-controlled capability to manage your IP resources. IP Prefixes and IP Addresses are first-class objects in Infrahub — they form hierarchies, track utilization automatically, and integrate with the rest of your infrastructure data. Both IPv4 and IPv6 are supported. - [IP Address Management](/ipam/overview.md): IP address management (IPAM) in Infrahub gives you a structured, version-controlled capability to manage your IP resources. IP Prefixes and IP Addresses are first-class objects in Infrahub — they form hierarchies, track utilization automatically, and integrate with the rest of your infrastructure data. Both IPv4 and IPv6 are supported. ## menu ### overview Infrahub lets you control the menu on the left side of the web interface. - [Menu customization](/menu/overview.md): Infrahub lets you control the menu on the left side of the web interface. ## object-templates ### allocate-resources-from-pools Object templates support automatic resource allocation. This guide extends the device and interface example from Use object templates to show how to wire a CoreIPAddressPool to the interface template so that IP addresses are allocated automatically on object creation. - [Allocate resources from pools](/object-templates/allocate-resources-from-pools.md): Object templates support automatic resource allocation. This guide extends the device and interface example from Use object templates to show how to wire a CoreIPAddressPool to the interface template so that IP addresses are allocated automatically on object creation. ### overview In many infrastructures, multiple instances of objects share a common structure. Consider network devices: we know in advance the port setup for a given model. When documenting this in our source of truth, repeatedly entering the same port details is both inefficient and error-prone. The Object Template feature allows you to create a reusable blueprint for any object. This blueprint can be used to generate multiple instances that adhere to the predefined structure, ensuring uniformity while reducing manual effort. - [Object Templates](/object-templates/overview.md): In many infrastructures, multiple instances of objects share a common structure. Consider network devices: we know in advance the port setup for a given model. When documenting this in our source of truth, repeatedly entering the same port details is both inefficient and error-prone. The Object Template feature allows you to create a reusable blueprint for any object. This blueprint can be used to generate multiple instances that adhere to the predefined structure, ensuring uniformity while reducing manual effort. ### use This guide provides a structured approach to defining an object template and creating object instances based on that template. - [Use object templates](/object-templates/use.md): This guide provides a structured approach to defining an object template and creating object instances based on that template. ### with-profiles When both generatetemplate and generateprofile are configured on a schema node, you can assign Profiles to templates to enable bulk configuration updates. Objects created from templates automatically inherit the Profiles assigned to those templates, allowing you to update values in bulk by modifying the Profile. - [Assign Profiles to a template](/object-templates/with-profiles.md): When both generatetemplate and generateprofile are configured on a schema node, you can assign Profiles to templates to enable bulk configuration updates. Objects created from templates automatically inherit the Profiles assigned to those templates, allowing you to update values in bulk by modifying the Profile. ## objects ### convert-object-kind Object conversion in Infrahub provides a powerful mechanism to transform existing objects from one schema type to another without losing data or breaking relationships. This capability addresses the common infrastructure management challenge of evolving data models while preserving existing configurations and connections. - [Object conversion](/objects/convert-object-kind.md): Object conversion in Infrahub provides a powerful mechanism to transform existing objects from one schema type to another without losing data or breaking relationships. This capability addresses the common infrastructure management challenge of evolving data models while preserving existing configurations and connections. ### load-from-yaml An Object file is a YAML file that allows you to manage data to be loaded in Infrahub based on your own custom schema. It provides a declarative way to define and manage resources in your Infrahub instance. - [Object files](/objects/load-from-yaml.md): An Object file is a YAML file that allows you to manage data to be loaded in Infrahub based on your own custom schema. It provides a declarative way to define and manage resources in your Infrahub instance. ### metadata Data lineage - [Data lineage and metadata](/objects/metadata.md): Data lineage ### overview An object is a record stored in Infrahub that conforms to a schema node definition. If schema nodes are the blueprints, objects are the actual instances of data — a specific router, an interface, a BGP session, a rack. - [Objects](/objects/overview.md): An object is a record stored in Infrahub that conforms to a schema node definition. If schema nodes are the blueprints, objects are the actual instances of data — a specific router, an interface, a BGP session, a rack. ## overview A graph-based infrastructure data management platform with built-in version control, CI workflows, peer review, and API access. - [What is Infrahub](/overview.md): A graph-based infrastructure data management platform with built-in version control, CI workflows, peer review, and API access. ### architecture Infrahub's architecture combines graph database technology with Git-like version control to create a powerful platform for infrastructure management. This topic explains the core components, design principles, and how they work together to deliver a unified infrastructure management solution. - [Understanding Infrahub's architecture](/overview/architecture.md): Infrahub's architecture combines graph database technology with Git-like version control to create a powerful platform for infrastructure management. This topic explains the core components, design principles, and how they work together to deliver a unified infrastructure management solution. ### community-vs-enterprise Infrahub is available in two distinct editions designed to meet different organizational needs: Community Edition and Enterprise Edition. This topic explains the differences between these editions to help you choose the right version for your infrastructure management needs. - [Understanding Infrahub community vs enterprise](/overview/community-vs-enterprise.md): Infrahub is available in two distinct editions designed to meet different organizational needs: Community Edition and Enterprise Edition. This topic explains the differences between these editions to help you choose the right version for your infrastructure management needs. ### concepts Core principles behind Infrahub — flexible schemas, version control, Transformations, Generators, and design-driven automation. - [Key Concepts](/overview/concepts.md): Core principles behind Infrahub — flexible schemas, version control, Transformations, Generators, and design-driven automation. ### explore See Infrahub in action through the sandbox, interactive labs, videos, and demos. - [Explore Infrahub](/overview/explore.md): See Infrahub in action through the sandbox, interactive labs, videos, and demos. ### next-steps Turn your local Infrahub setup into a proof of concept for your organization. - [Next Steps](/overview/next-steps.md): Turn your local Infrahub setup into a proof of concept for your organization. ### quickstart Set up a local Infrahub instance, load a data model, create infrastructure data, and explore branching. - [Quick Start](/overview/quickstart.md): Set up a local Infrahub instance, load a data model, create infrastructure data, and explore branching. ## profiles ### assign When creating or updating an object, reference a Profile by HFID. The object inherits the Profile's values for any attributes you don't set explicitly. - [Assign a Profile to an object](/profiles/assign.md): When creating or updating an object, reference a Profile by HFID. The object inherits the Profile's values for any attributes you don't set explicitly. ### create Define a reusable set of attribute values for a Profile-enabled node type. - [Create a Profile](/profiles/create.md): Define a reusable set of attribute values for a Profile-enabled node type. ### override-values When a Profile-assigned object should differ for a specific field, set the value explicitly when creating or updating the object. Explicit values take precedence over Profile values; other attributes still inherit from the Profile. - [Override specific Profile values](/profiles/override-values.md): When a Profile-assigned object should differ for a specific field, set the value explicitly when creating or updating the object. Explicit values take precedence over Profile values; other attributes still inherit from the Profile. ### overview This topic explains the concept of Profiles in Infrahub, their purpose in the system architecture, and how they enable configuration consistency across your infrastructure. You'll gain a deeper understanding of Profile inheritance, priority mechanisms, and the design decisions behind this feature. - [Profiles](/profiles/overview.md): This topic explains the concept of Profiles in Infrahub, their purpose in the system architecture, and how they enable configuration consistency across your infrastructure. You'll gain a deeper understanding of Profile inheritance, priority mechanisms, and the design decisions behind this feature. ### priority-and-inheritance When an object has one or more Profiles assigned, Infrahub uses a deterministic process to decide which value applies to each attribute or relationship. This page explains both the inheritance flow and the priority system that resolves conflicts when multiple Profiles apply. - [Priority and inheritance](/profiles/priority-and-inheritance.md): When an object has one or more Profiles assigned, Infrahub uses a deterministic process to decide which value applies to each attribute or relationship. This page explains both the inheritance flow and the priority system that resolves conflicts when multiple Profiles apply. ### update Change Profile values; all objects assigned to the Profile inherit the change automatically. Objects that have explicitly overridden the attribute keep their override — only objects relying on the Profile value see the change. - [Update a Profile](/profiles/update.md): Change Profile values; all objects assigned to the Profile inherit the change automatically. Objects that have explicitly overridden the attribute keep their override — only objects relying on the Profile value see the change. ### use-multiple A single object can be assigned multiple Profiles. When multiple Profiles define the same attribute, the profile_priority value (lower number = higher priority) determines which Profile's value wins. - [Use multiple Profiles](/profiles/use-multiple.md): A single object can be assigned multiple Profiles. When multiple Profiles define the same attribute, the profile_priority value (lower number = higher priority) determines which Profile's value wins. ## proposed-changes ### lifecycle Proposed changes follow a workflow with specific states that track progression from initial creation to final resolution. - [Lifecycle and state transitions](/proposed-changes/lifecycle.md): Proposed changes follow a workflow with specific states that track progression from initial creation to final resolution. ### overview A proposed change in Infrahub is a structured workflow mechanism that enables teams to review, discuss, and merge changes in a controlled and collaborative manner. It serves as the primary method for implementing infrastructure changes safely while maintaining proper oversight and governance. - [Proposed Changes](/proposed-changes/overview.md): A proposed change in Infrahub is a structured workflow mechanism that enables teams to review, discuss, and merge changes in a controlled and collaborative manner. It serves as the primary method for implementing infrastructure changes safely while maintaining proper oversight and governance. ### resolve-conflict When a proposed change has data conflicts between its source and target branches, Infrahub blocks the merge to protect the integrity of your infrastructure data. Conflicts are resolved during the review process, before the change can be approved and merged. - [Resolve a proposed-change conflict](/proposed-changes/resolve-conflict.md): When a proposed change has data conflicts between its source and target branches, Infrahub blocks the merge to protect the integrity of your infrastructure data. Conflicts are resolved during the review process, before the change can be approved and merged. ### review-and-stamp The review process gives team members structured ways to evaluate a proposed change, comment on specific elements, and ultimately stamp it as approved or rejected. - [Review and stamp](/proposed-changes/review-and-stamp.md): The review process gives team members structured ways to evaluate a proposed change, comment on specific elements, and ultimately stamp it as approved or rejected. ## reference - [Reference](/reference.md) ### configuration Configuration options for Infrahub - [Infrahub configuration](/reference/configuration.md): Configuration options for Infrahub ### dotinfrahub The repository configuration file allows you to define multiple resources that need to be imported into Infrahub. - [Repository configuration file](/reference/dotinfrahub.md): The repository configuration file allows you to define multiple resources that need to be imported into Infrahub. ### error-catalogue Catalogue version: 1 — 10 codes - [Error Catalogue](/reference/error-catalogue.md): Catalogue version: 1 — 10 codes ### graph-traversal Reference for the InfrahubPathTraversal and InfrahubReachableNodes GraphQL queries. - [Graph Traversal reference](/reference/graph-traversal.md): Reference for the InfrahubPathTraversal and InfrahubReachableNodes GraphQL queries. ### infrahub-cli - [CLI](/reference/infrahub-cli.md) - [infrahub db](/reference/infrahub-cli/infrahub-db.md): Manage the graph in the database. - [infrahub dev](/reference/infrahub-cli/infrahub-dev.md): Usage: - [infrahub server](/reference/infrahub-cli/infrahub-server.md): Control the API Server. - [infrahub upgrade](/reference/infrahub-cli/infrahub-upgrade.md): Upgrade Infrahub to the latest version. ### infrahub-events - [Account events](/reference/infrahub-events/account.md): Account Logged In Event - [Artifact events](/reference/infrahub-events/artifact.md): Artifact Created Event - [Branch events](/reference/infrahub-events/branch.md): Branch Created Event - [Commit events](/reference/infrahub-events/commit.md): Commit Updated Event - [Group events](/reference/infrahub-events/group.md): Group Auto Create Capped Event - [Node events](/reference/infrahub-events/node.md): Node Created Event - [Event reference](/reference/infrahub-events/overview.md): Complete reference for all events emitted by Infrahub. Each page lists every event in that category with its type identifier, description, and payload fields. - [Proposed events](/reference/infrahub-events/proposed.md): Proposed Change Approval Revoked Event - [Schema events](/reference/infrahub-events/schema.md): Schema Updated Event - [Validator events](/reference/infrahub-events/validator.md): Validator Failed Event ### infrahub-tests The tests configuration file allows you to define multiple tests for Infrahub resources such as Jinja2 Transformations, Python Transformations, checks and GraphQL queries. - [Tests configuration file](/reference/infrahub-tests.md): The tests configuration file allows you to define multiple tests for Infrahub resources such as Jinja2 Transformations, Python Transformations, checks and GraphQL queries. ### ldap This reference document describes the configuration options for LDAP authentication in Infrahub. - [LDAP](/reference/ldap.md): This reference document describes the configuration options for LDAP authentication in Infrahub. ### menu A menu definition file allows you to control the layout and structure of the menu on the left side of the Infrahub web interface. More information can be found in the Controlling the menu guide. - [Menu definition file](/reference/menu.md): A menu definition file allows you to control the layout and structure of the menu on the left side of the Infrahub web interface. More information can be found in the Controlling the menu guide. ### message-bus-events This document provides detailed documentation for all events used in the Infrahub message bus system. - [Message bus events](/reference/message-bus-events.md): This document provides detailed documentation for all events used in the Infrahub message bus system. ### permissions Reference documentation for Infrahub's global and object permissions - [Permissions](/reference/permissions.md): Reference documentation for Infrahub's global and object permissions ### schema - [Schema Specification](/reference/schema.md) - [Attribute](/reference/schema/attribute.md): In a schema file, an attribute can be defined inside a node, a generic or a node extension. - [Generic](/reference/schema/generic.md): All options to define a Generic in the schema - [Groups](/reference/schema/groups.md): Groups schema reference - [Node](/reference/schema/node.md): Node schema reference - [Node Extension](/reference/schema/node-extension.md): All options to define a node extension in the schema - [Relationship](/reference/schema/relationship.md): In a schema file, a relationship can be defined inside a node, a generic or a node extension. - [Schema Update](/reference/schema/validator-migration.md): In this context, an element represent either a Node, a Generic, an Attribute or a Relationship ### schema-validation Infrahub requires the user to define multiple YAML files. One or more files defining the schema in Infrahub and the external repository configuration file. - [Schema validation](/reference/schema-validation.md): Infrahub requires the user to define multiple YAML files. One or more files defining the schema in Infrahub and the external repository configuration file. ### sso This reference document describes the available SSO protocols, configuration options, and parameters in Infrahub. - [SSO (Single sign-on)](/reference/sso.md): This reference document describes the available SSO protocols, configuration options, and parameters in Infrahub. ## release-notes - [Release Notes](/release-notes.md) ### deprecation-guides - [Deprecation Guides](/release-notes/deprecation-guides.md) - [How to migrate from display_labels to display_label](/release-notes/deprecation-guides/display_labels.md): With Infrahub version 1.5, the displaylabels configuration was deprecated in favor of a Jinja2-based displaylabel configuration that provides more flexibility and consistency. - [How to migrate off the SSO account-name fallback](/release-notes/deprecation-guides/sso-account-name-fallback.md): Infrahub 1.8.7 and 1.9.7 introduce a feature flag, ssoaccountname_fallback, that ### infrahub - [Infrahub](/release-notes/infrahub.md) - [Documentation restructure](/release-notes/infrahub/docs-restructure.md): Why we reorganized - [Release 0.10.0 - Alpha #4](/release-notes/infrahub/release-0_10.md): Release Number - [Release 0.11.0 - Alpha #5](/release-notes/infrahub/release-0_11.md): Release Number - [Release 0.12.0 - Beta #1](/release-notes/infrahub/release-0_12.md): Release Number - [Release 0.13.0](/release-notes/infrahub/release-0_13.md): Release Number - [Release 0.14.0](/release-notes/infrahub/release-0_14.md): Release Number - [Release 0.15.0](/release-notes/infrahub/release-0_15_0.md): Release Number - [Release 0.15.1](/release-notes/infrahub/release-0_15_1.md): Release Number - [Release 0.15.2](/release-notes/infrahub/release-0_15_2.md): Release Number - [Release 0.15.3](/release-notes/infrahub/release-0_15_3.md): Release Number - [Release 0.16](/release-notes/infrahub/release-0_16_0.md): Release Number - [Release 0.16.1](/release-notes/infrahub/release-0_16_1.md): Release Number - [Release 0.16.2](/release-notes/infrahub/release-0_16_2.md): Release Number - [Release 0.16.3](/release-notes/infrahub/release-0_16_3.md): Release Number - [Release 0.16.4](/release-notes/infrahub/release-0_16_4.md): Release Number - [Release 0.6.0](/release-notes/infrahub/release-0_6.md): Release Number - [Release 0.7.0](/release-notes/infrahub/release-0_7.md): Release Number - [Release 0.8.0 - Alpha #2](/release-notes/infrahub/release-0_8.md): Release Number - [Release 0.9.0 - Alpha #3](/release-notes/infrahub/release-0_9.md): Release Number - [Release 1.0](/release-notes/infrahub/release-1_0_0.md): Release Number - [Release 1.0.1](/release-notes/infrahub/release-1_0_1.md): Release Number - [Release 1.0.10](/release-notes/infrahub/release-1_0_10.md): Release Number - [Release 1.0.2](/release-notes/infrahub/release-1_0_2.md): Release Number - [Release 1.0.3](/release-notes/infrahub/release-1_0_3.md): Release Number - [Release 1.0.4](/release-notes/infrahub/release-1_0_4.md): Release Number - [Release 1.0.5](/release-notes/infrahub/release-1_0_5.md): Release Number - [Release 1.0.6](/release-notes/infrahub/release-1_0_6.md): Release Number - [Release 1.0.7](/release-notes/infrahub/release-1_0_7.md): Release Number - [Release 1.0.8](/release-notes/infrahub/release-1_0_8.md): Release Number - [Release 1.0.9](/release-notes/infrahub/release-1_0_9.md): Release Number - [Release 1.1.0](/release-notes/infrahub/release-1_1_0.md): Release Number - [Release 1.1.1](/release-notes/infrahub/release-1_1_1.md): Release Number - [Release 1.1.2](/release-notes/infrahub/release-1_1_2.md): Release Number - [Release 1.1.3](/release-notes/infrahub/release-1_1_3.md): Release Number - [Release 1.1.4](/release-notes/infrahub/release-1_1_4.md): Release Number - [Release 1.1.5](/release-notes/infrahub/release-1_1_5.md): Release Number - [Release 1.1.6](/release-notes/infrahub/release-1_1_6.md): Release Number - [Release 1.1.7](/release-notes/infrahub/release-1_1_7.md): Release Number - [Release 1.1.8](/release-notes/infrahub/release-1_1_8.md): Release Number - [Release 1.1.9](/release-notes/infrahub/release-1_1_9.md): Release Number - [Release 1.10.0](/release-notes/infrahub/release-1_10_0.md): Release Number - [Release 1.10.1](/release-notes/infrahub/release-1_10_1.md): Release Number - [Release 1.10.2](/release-notes/infrahub/release-1_10_2.md): Release Number - [Release 1.10.3](/release-notes/infrahub/release-1_10_3.md): Release Number - [Release 1.2.0](/release-notes/infrahub/release-1_2_0.md): Release Number - [Release 1.2.1](/release-notes/infrahub/release-1_2_1.md): Release Number - [Release 1.2.10](/release-notes/infrahub/release-1_2_10.md): Release Number - [Release 1.2.11](/release-notes/infrahub/release-1_2_11.md): Release Number - [Release 1.2.12](/release-notes/infrahub/release-1_2_12.md): Release Number - [Release 1.2.2](/release-notes/infrahub/release-1_2_2.md): Release Number - [Release 1.2.3](/release-notes/infrahub/release-1_2_3.md): Release Number - [Release 1.2.4](/release-notes/infrahub/release-1_2_4.md): Release Number - [Release 1.2.5](/release-notes/infrahub/release-1_2_5.md): Release Number - [Release 1.2.6](/release-notes/infrahub/release-1_2_6.md): Release Number - [Release 1.2.7](/release-notes/infrahub/release-1_2_7.md): Release Number - [Release 1.2.8](/release-notes/infrahub/release-1_2_8.md): Release Number - [Release 1.2.9](/release-notes/infrahub/release-1_2_9.md): Release Number - [Release 1.3.0](/release-notes/infrahub/release-1_3_0.md): Release Number - [Release 1.3.1](/release-notes/infrahub/release-1_3_1.md): Release Number - [Release 1.3.2](/release-notes/infrahub/release-1_3_2.md): Release Number - [Release 1.3.3](/release-notes/infrahub/release-1_3_3.md): Release Number - [Release 1.3.5](/release-notes/infrahub/release-1_3_5.md): Release Number - [Release 1.3.6](/release-notes/infrahub/release-1_3_6.md): Release Number - [Release 1.3.7](/release-notes/infrahub/release-1_3_7.md): Release Number - [Release 1.4.0](/release-notes/infrahub/release-1_4_0.md): Release Number - [Release 1.4.1](/release-notes/infrahub/release-1_4_1.md): Release Number - [Release 1.4.10](/release-notes/infrahub/release-1_4_10.md): Release Number - [Release 1.4.11](/release-notes/infrahub/release-1_4_11.md): Release Number - [Release 1.4.12](/release-notes/infrahub/release-1_4_12.md): Release Number - [Release 1.4.13](/release-notes/infrahub/release-1_4_13.md): Release Number - [Release 1.4.2](/release-notes/infrahub/release-1_4_2.md): Release Number - [Release 1.4.3](/release-notes/infrahub/release-1_4_3.md): Release Number - [Release 1.4.4](/release-notes/infrahub/release-1_4_4.md): Release Number - [Release 1.4.5](/release-notes/infrahub/release-1_4_5.md): Release Number - [Release 1.4.6](/release-notes/infrahub/release-1_4_6.md): Release Number - [Release 1.4.7](/release-notes/infrahub/release-1_4_7.md): Release Number - [Release 1.4.8](/release-notes/infrahub/release-1_4_8.md): Release Number - [Release 1.4.9](/release-notes/infrahub/release-1_4_9.md): Release Number - [Main changes](/release-notes/infrahub/release-1_5_0.md): Release Number - [Release 1.5.1](/release-notes/infrahub/release-1_5_1.md): Release Number - [Release 1.5.2](/release-notes/infrahub/release-1_5_2.md): Release Number - [Release 1.5.3](/release-notes/infrahub/release-1_5_3.md): Release Number - [Main changes](/release-notes/infrahub/release-1_6_0.md): Release Number - [Release 1.6.1](/release-notes/infrahub/release-1_6_1.md): Release Number - [Release 1.6.2](/release-notes/infrahub/release-1_6_2.md): Release Number - [Release 1.6.3](/release-notes/infrahub/release-1_6_3.md): Release Number - [Release 1.7.0](/release-notes/infrahub/release-1_7_0.md): Release Number - [Release 1.7.1](/release-notes/infrahub/release-1_7_1.md): Release Number - [Release 1.7.2](/release-notes/infrahub/release-1_7_2.md): Release Number - [Release 1.7.3](/release-notes/infrahub/release-1_7_3.md): Release Number - [Release 1.7.4](/release-notes/infrahub/release-1_7_4.md): Release Number - [Release 1.7.5](/release-notes/infrahub/release-1_7_5.md): Release Number - [Release 1.7.6](/release-notes/infrahub/release-1_7_6.md): Release Number - [Release 1.7.7](/release-notes/infrahub/release-1_7_7.md): Release Number - [Release 1.8.0](/release-notes/infrahub/release-1_8_0.md): Release Number - [Release 1.8.1](/release-notes/infrahub/release-1_8_1.md): Release Number - [Release 1.8.2](/release-notes/infrahub/release-1_8_2.md): Release Number - [Release 1.8.3](/release-notes/infrahub/release-1_8_3.md): Release Number - [Release 1.8.4](/release-notes/infrahub/release-1_8_4.md): Release Number - [Release 1.8.5](/release-notes/infrahub/release-1_8_5.md): Release Number - [Release 1.8.6](/release-notes/infrahub/release-1_8_6.md): Release Number - [Release 1.9.0](/release-notes/infrahub/release-1_9_0.md): Release Number - [Release 1.9.1](/release-notes/infrahub/release-1_9_1.md): Release Number - [Release 1.9.10](/release-notes/infrahub/release-1_9_10.md): Release Number - [Release 1.9.2](/release-notes/infrahub/release-1_9_2.md): Release Number - [Release 1.9.3](/release-notes/infrahub/release-1_9_3.md): Release Number - [Release 1.9.4](/release-notes/infrahub/release-1_9_4.md): Release Number - [Release 1.9.5](/release-notes/infrahub/release-1_9_5.md): Release Number - [Release 1.9.6](/release-notes/infrahub/release-1_9_6.md): Release Number - [Release 1.9.7](/release-notes/infrahub/release-1_9_7.md): Release Number - [Release 1.9.8](/release-notes/infrahub/release-1_9_8.md): Release Number - [Release 1.9.9](/release-notes/infrahub/release-1_9_9.md): Release Number ## resource-manager ### allocate-ip-address IP address pools (CoreIPAddressPool) dynamically allocate individual IP addresses from source prefixes. - [Allocate IP addresses](/resource-manager/allocate-ip-address.md): IP address pools (CoreIPAddressPool) dynamically allocate individual IP addresses from source prefixes. ### allocate-ip-prefix IP prefix pools (CoreIPPrefixPool) allocate IP subnets from larger prefixes. - [Allocate IP prefixes](/resource-manager/allocate-ip-prefix.md): IP prefix pools (CoreIPPrefixPool) allocate IP subnets from larger prefixes. ### allocate-number Number pools (CoreNumberPool) automatically assign sequential numbers to numeric attributes. - [Allocate numbers](/resource-manager/allocate-number.md): Number pools (CoreNumberPool) automatically assign sequential numbers to numeric attributes. ### overview Resource Manager in Infrahub automates the allocation of network resources from predefined pools, eliminating manual assignment and preventing conflicts across your infrastructure. This system ensures consistent resource allocation while maintaining complete visibility and control over your available resources. - [Resource Manager](/resource-manager/overview.md): Resource Manager in Infrahub automates the allocation of network resources from predefined pools, eliminating manual assignment and preventing conflicts across your infrastructure. This system ensures consistent resource allocation while maintaining complete visibility and control over your available resources. ### weighted-allocation When multiple resources can be allocated from a pool, you can control the allocation order using weights. Resources with higher weights are allocated first. - [Weighted allocation](/resource-manager/weighted-allocation.md): When multiple resources can be allocated from a pool, you can control the allocation order using weights. Resources with higher weights are allocated first. ## schema-and-data - [Schema & Data](/schema-and-data.md) ## schema ### branch-awareness Every schema element — nodes, attributes, and relationships — can be configured to control how changes to its data behave across branches. - [Branch awareness](/schema/branch-awareness.md): Every schema element — nodes, attributes, and relationships — can be configured to control how changes to its data behave across branches. ### create-and-load Schemas are defined in YAML format and follow a strict structure. Splitting a schema into multiple smaller files is recommended to keep it maintainable as it grows. - [Create and load schema](/schema/create-and-load.md): Schemas are defined in YAML format and follow a strict structure. Splitting a schema into multiple smaller files is recommended to keep it maintainable as it grows. ### default-ordering The order_by field on a node or generic sets the default sort order for every list of that kind's objects. Once declared, the order applies everywhere the objects are listed - the web interface, the GraphQL API, and the Python SDK - without each query specifying how to sort. - [Default ordering](/schema/default-ordering.md): The order_by field on a node or generic sets the default sort order for every list of that kind's objects. Once declared, the order applies everywhere the objects are listed - the web interface, the GraphQL API, and the Python SDK - without each query specifying how to sort. ### display_label Display labels in Infrahub provide human-friendly names and identifiers for schema elements and object instances. They serve as the primary way users interact with and identify objects throughout the system, from the user interface to GraphQL queries. - [Display labels](/schema/display_label.md): Display labels in Infrahub provide human-friendly names and identifiers for schema elements and object instances. They serve as the primary way users interact with and identify objects throughout the system, from the user interface to GraphQL queries. ### extensions Schema extensions provide a mechanism to add attributes and relationships to existing nodes without redefining them. This enables modular schema composition where features can be developed, reviewed, and managed as self-contained files. - [Schema extensions](/schema/extensions.md): Schema extensions provide a mechanism to add attributes and relationships to existing nodes without redefining them. This enables modular schema composition where features can be developed, reviewed, and managed as self-contained files. ### field-visibility Infrahub schemas can declare how attributes and relationships should appear in the frontend, independently of their presence in the data model. These declarations are purely UI hints — they do not affect data access via the API, GraphQL, or forms. - [Field visibility](/schema/field-visibility.md): Infrahub schemas can declare how attributes and relationships should appear in the frontend, independently of their presence in the data model. These declarations are purely UI hints — they do not affect data access via the API, GraphQL, or forms. ### file-object Infrastructure management often involves more than just structured data. Contracts, documents, and pictures are all essential assets that relate directly to the devices, circuits, and services they describe. Infrahub's file object feature lets you attach files to any node in the graph, making these files first-class citizens of your infrastructure data model with full version control, branch isolation, and permission enforcement. - [File objects](/schema/file-object.md): Infrastructure management often involves more than just structured data. Contracts, documents, and pictures are all essential assets that relate directly to the devices, circuits, and services they describe. Infrahub's file object feature lets you attach files to any node in the graph, making these files first-class citizens of your infrastructure data model with full version control, branch isolation, and permission enforcement. ### generics-and-inheritance A Generic in Infrahub is a reusable base that shares attributes and relationships across multiple node types. They work similarly to abstract base classes or interfaces in object-oriented programming — you define common structure once, and node types that inherit_from a generic receive that structure automatically. - [Generics & inheritance](/schema/generics-and-inheritance.md): A Generic in Infrahub is a reusable base that shares attributes and relationships across multiple node types. They work similarly to abstract base classes or interfaces in object-oriented programming — you define common structure once, and node types that inherit_from a generic receive that structure automatically. ### hierarchy Infrahub supports organizing nodes of similar types into a tree structure. Hierarchy mode enables ancestor and descendant queries, and allows filtering related data across multiple levels of the tree without direct relationships between every node. - [Hierarchy](/schema/hierarchy.md): Infrahub supports organizing nodes of similar types into a tree structure. Hierarchy mode enables ancestor and descendant queries, and allows filtering related data across multiple levels of the tree without direct relationships between every node. ### marketplace The Infrahub Marketplace is a public catalog of pre-built schemas and schema collections that you can fetch and load into any Infrahub instance. - [Infrahub Marketplace](/schema/marketplace.md): The Infrahub Marketplace is a public catalog of pre-built schemas and schema collections that you can fetch and load into any Infrahub instance. ### migration Unlike most databases that support a single schema at a time, in Infrahub it is possible to have a different schema per branch. - [Schema migration](/schema/migration.md): Unlike most databases that support a single schema at a time, in Infrahub it is possible to have a different schema per branch. ### nodes-and-attributes A Node is the fundamental unit of the schema — it represents a concrete object in your infrastructure model such as a device, interface, or IP address. Nodes are composed of Attributes (direct values) and Relationships (links to other nodes). This page covers how to define nodes and attributes, what kinds are available, and how to configure identifiers, constraints, and display. - [Nodes & attributes](/schema/nodes-and-attributes.md): A Node is the fundamental unit of the schema — it represents a concrete object in your infrastructure model such as a device, interface, or IP address. Nodes are composed of Attributes (direct values) and Relationships (links to other nodes). This page covers how to define nodes and attributes, what kinds are available, and how to configure identifiers, constraints, and display. ### number-pool The NumberPool attribute kind enables the dynamic creation of a CoreNumberPool for the specific schema node and attribute rather than requiring users to define this after the schema is loaded. - [Number pools](/schema/number-pool.md): The NumberPool attribute kind enables the dynamic creation of a CoreNumberPool for the specific schema node and attribute rather than requiring users to define this after the schema is loaded. ### order-weight The orderweight property controls how attributes and relationships are ordered in the Infrahub frontend, including table views and detailed object views. **Lower orderweight values appear first. This means items with smaller numbers are displayed before those with larger numbers. Understanding how default values are assigned and how to work with them effectively is crucial for creating well-organized schemas. - [Order weight](/schema/order-weight.md): The orderweight property controls how attributes and relationships are ordered in the Infrahub frontend, including table views and detailed object views. **Lower orderweight values appear first. This means items with smaller numbers are displayed before those with larger numbers. Understanding how default values are assigned and how to work with them effectively is crucial for creating well-organized schemas. ### overview The schema is Infrahub's data model — a flexible, organization-specific definition of what objects exist in your infrastructure, design, and business logic, what properties they have, and how they relate to each other. It acts as an abstraction layer over the graph database, so no knowledge of database administration is needed for day-to-day use. - [About schema](/schema/overview.md): The schema is Infrahub's data model — a flexible, organization-specific definition of what objects exist in your infrastructure, design, and business logic, what properties they have, and how they relate to each other. It acts as an abstraction layer over the graph database, so no knowledge of database administration is needed for day-to-day use. ### relationships A Relationship in Infrahub represents a typed link between two nodes. Unlike a simple foreign key, relationships have a kind that determines how they appear in the UI and how they behave during deletion and hierarchy traversal. - [Relationships](/schema/relationships.md): A Relationship in Infrahub represents a typed link between two nodes. Unlike a simple foreign key, relationships have a kind that determines how they appear in the UI and how they behave during deletion and hierarchy traversal. ## snippets ### attribute-kind-params | Parameter | Default | - [attribute-kind-params](/snippets/attribute-kind-params.md): | Parameter | Default | ### pre-reqs-base | Prerequisite Tool(s) | Post Installation Steps | - [pre-reqs-base](/snippets/pre-reqs-base.md): | Prerequisite Tool(s) | Post Installation Steps | ### pre-reqs-frontend | Prerequisite Tool(s) | Post Installation Steps | - [pre-reqs-frontend](/snippets/pre-reqs-frontend.md): | Prerequisite Tool(s) | Post Installation Steps | ## testing-framework ### overview Summary - [Testing framework](/testing-framework/overview.md): Summary ## transformations ### jinja2 A Jinja2 Transformation renders Infrahub data through a Jinja template, producing plain text output (configurations, manifests, payloads). The steps below cover how to write one. - [Write a Jinja2 Transformation](/transformations/jinja2.md): A Jinja2 Transformation renders Infrahub data through a Jinja template, producing plain text output (configurations, manifests, payloads). The steps below cover how to write one. ### overview A Transformation is a generic plugin to transform a dataset into a different format to simplify it's ingestion by third-party systems. - [Transformations](/transformations/overview.md): A Transformation is a generic plugin to transform a dataset into a different format to simplify it's ingestion by third-party systems. ### python A Python Transformation processes Infrahub data through user-written Python code, producing JSON output (or any structured data you serialize). Use this when Jinja templating isn't enough — for example, conditional logic, external API calls, or complex aggregation. The steps below cover how to write one. - [Write a Python Transformation](/transformations/python.md): A Python Transformation processes Infrahub data through user-written Python code, producing JSON output (or any structured data you serialize). Use this when Jinja templating isn't enough — for example, conditional logic, external API calls, or complex aggregation. The steps below cover how to write one. ## webhooks ### create 1. Login to Infrahub's web interface as an administrator. - [Create a webhook](/webhooks/create.md): 1. Login to Infrahub's web interface as an administrator. ### custom-transformation Custom webhooks allow you to transform event data before sending it to an external endpoint. This is useful when the receiving system expects a specific payload format, such as Slack, Microsoft Teams, GitHub Actions, or other third-party APIs. - [Webhook with custom Transformation](/webhooks/custom-transformation.md): Custom webhooks allow you to transform event data before sending it to an external endpoint. This is useful when the receiving system expects a specific payload format, such as Slack, Microsoft Teams, GitHub Actions, or other third-party APIs. ### overview Webhooks are HTTP callbacks that deliver data to external systems in real-time when an event occurs within Infrahub. They allow you to: - [What are webhooks?](/webhooks/overview.md): Webhooks are HTTP callbacks that deliver data to external systems in real-time when an event occurs within Infrahub. They allow you to: --- # Full Documentation Content [Skip to main content](#__docusaurus_skipToContent_fallback) 📚 New docs structure: content is now grouped by capability, not split across Topics and Guides. [**See what changed →**](/release-notes/infrahub/docs-restructure.md) [![Infrahub](/img/infrahub-hori.svg)![Infrahub](/img/infrahub-hori-dark.svg)](/index.md) [Infrahub Docs](/index.md) [Tools & SDKs](#) * [Python SDK](/python-sdk/introduction.md) * [Infrahubctl](/infrahubctl/infrahubctl.md) * [Infrahub Assistant | Emma](/emma.md) * [VScode Extension for Infrahub](/vscode.md) * [MCP Server for Infrahub](/mcp.md) * [Infrahub AI Skills](/skills.md) * [Infrahub Backup](/backup.md) [Integrations](#) * [All Integrations](/integrations.md) * [Ansible](/ansible.md) * [Infrahub Exporter](/exporter.md) * [Infrahub Sync](/sync.md) * [Nornir](/nornir.md) [Demos & Examples](#) * [demo-dc](/demo-dc.md) * [demo-sp](/infrahub-demo-sp.md) * [demo-service-catalog](/demo-service-catalog.md) * [Schema Library](/schema-library.md) [Solutions](#) * [AI Datacenter](/infrahub-solution-ai-dc/solution-ai-dc/overview.md) Search [](https://github.com/opsmill/infrahub)[](https://discord.gg/opsmill) # Search the documentation Type your search here Powered by[](https://www.algolia.com/) Copyright © 2026 - Infrahub by OpsMill. --- # opsmill.infrahub Ansible collection Collection version 1.8.3 ## Collection overview[​](#collection-overview "Direct link to Collection overview") The OpsMill Infrahub Ansible Collection provides is intend to help interact with Infrahub through Ansible. This Ansible collection consists of a set of modules and plugins designed to work seamlessly with your existing infrastructure, enabling you to define and enforce the desired state of your infrastructure with ease. ## Guides[​](#guides "Direct link to Guides") To begin using the OpsMill Infrahub Ansible Collection, please follow our step-by-step guides: * **[Installation Guide](/ansible/guides/installation.md)**: Learn how to install the collection, including Python module and Ansible setup, as well as alternative installation options. * **[Dynamic Inventory Guide](/ansible/guides/dynamic-inventory.md)**: Discover how to leverage the collection's dynamic inventory features to streamline your infrastructure management. * **[Query & Lookup Guide](/ansible/guides/query-and-lookup.md)**: Learn how to retrieve structured GraphQL data from Infrahub using Query and Lookup plugins. * **[Create, Update and Delete Nodes Guide](/ansible/guides/node.md)**: Learn how to create, update and delete nodes in Infrahub using GraphQL action plugin or the node module. * **[Manipulate Branch Guide](/ansible/guides/branch.md)**: Learn how to manipulate Branch in Infrahub. ## References[​](#references "Direct link to References") ### Plugins[​](#plugins "Direct link to Plugins") These are the plugins in the `opsmill.infrahub` collection: #### Modules[​](#modules "Direct link to Modules") * [artifact\_generate](/ansible/references/plugins/artifact_generate_module.md) – Trigger artifact regeneration in Infrahub * [object\_file\_fetch](/ansible/references/plugins/object_file_fetch_module.md) – Fetch file content from a CoreFileObject node in Infrahub * [branch](/ansible/references/plugins/branch_module.md) – Creates, Updates or Deletes a branch in Infrahub * [artifact\_fetch](/ansible/references/plugins/artifact_fetch_module.md) – Fetch the content of an artifact from Infrahub * [node](/ansible/references/plugins/node_module.md) – Creates, Updates or Deletes a node in Infrahub * [query\_graphql](/ansible/references/plugins/query_graphql_module.md) – Queries and returns elements from Infrahub GraphQL API * [schema](/ansible/references/plugins/schema_module.md) – Load, check, or export schemas in Infrahub #### Inventory[​](#inventory "Direct link to Inventory") * [inventory](/ansible/references/plugins/inventory_inventory.md) – Infrahub inventory source (using GraphQL) #### Lookup[​](#lookup "Direct link to Lookup") * [lookup](/ansible/references/plugins/lookup_lookup.md) – Queries and returns elements from Infrahub (using GraphQL) ### Roles[​](#roles "Direct link to Roles") These are the roles in the `opsmill.infrahub` collection: * [install](/ansible/references/roles/install.md) – Install Infrahub --- # Manage branches in Infrahub Infrahub lets you manage branches directly via the `opsmill.infrahub.branch` module. With this module you can create, confirm, and delete branches—using the `state` parameter to control the desired operation. The `opsmill.infrahub.branch` module uses the Infrahub SDK to perform branch operations through GraphQL. You can specify the branch name, description, and whether the branch should be synchronized with Git. * Use `state: present` to create or confirm that a branch exists. * Use `state: absent` to delete a branch. The `opsmill.infrahub.branch` module does not support Ansible check option. ## Usage[​](#usage "Direct link to Usage") ### Creating a branch or confirming its existence[​](#creating-a-branch-or-confirming-its-existence "Direct link to Creating a branch or confirming its existence") The following playbook creates a branch named `test` with a description. If the branch already exists, the module will report that it already exists without making any changes. create\_branch.yml ``` --- - name: Manage Branch in Infrahub hosts: localhost gather_facts: false tasks: - name: Create a Branch 'test' opsmill.infrahub.branch: name: "test" sync_with_git: false description: "This is a test branch" state: present - name: Confirm 'test' exists opsmill.infrahub.branch: name: "test" state: present ``` **Example Output from create\_branch.yml** ``` TASK [Create a Branch 'test'] ************************************************************************* changed: [localhost] => {"branch": "id='1823716d-eb2a-c26f-35ef-c5155b8f34e4' name='test' description='This is a test branch' sync_with_git=False is_default=False has_schema_changes=False origin_branch='main' branched_from='2025-02-12T11:09:58.005622Z'", "changed": true, "msg": "InfrahubBranch test created"} TASK [Confirm 'test' exists] ************************************************************************* ok: [localhost] => {"branch": "id='1823716d-eb2a-c26f-35ef-c5155b8f34e4' name='test' description='This is a test branch' sync_with_git=False is_default=False has_schema_changes=False origin_branch='main' branched_from='2025-02-12T11:09:58.005622Z'", "changed": false, "msg": "InfrahubBranch test already exists."} ``` ### Deleting a branch[​](#deleting-a-branch "Direct link to Deleting a branch") To delete a branch, set the `state` parameter to absent. The module will remove the branch from Infrahub. delete\_branch.yml ``` --- - name: Delete Branch 'test' hosts: localhost gather_facts: false tasks: - name: Delete a Branch 'test' opsmill.infrahub.branch: name: "test" state: absent ``` **Example Output from delete\_branch.yml** ``` TASK [Delete a Branch 'test'] ************************************************************************* changed: [localhost] => {"branch": "id='1823716d-eb2a-c26f-35ef-c5155b8f34e4' name='test' description='This is a test branch' sync_with_git=False is_default=False has_schema_changes=False origin_branch='main' branched_from='2025-02-12T11:09:58.005622Z'", "changed": true, "msg": "InfrahubBranch test deleted"} ``` --- # Use Infrahub for Ansible dynamic inventory ## Overview[​](#overview "Direct link to Overview") The [**Inventory Plugin**](/ansible/references/plugins/inventory_inventory.md) component of the OpsMill Infrahub Ansible Collection is used to dynamically generate the inventory from Infrahub to be used in Ansible playbooks. ## Configuration[​](#configuration "Direct link to Configuration") In your `ansible.cfg` file, specify the inventory source as the `infrahub_inv.yml` file: ansible.cfg ``` [defaults] inventory = ./infrahub_inv.yml ``` ## Infrahub inventory plugin configuration[​](#infrahub-inventory-plugin-configuration "Direct link to Infrahub inventory plugin configuration") Configure the Infrahub inventory plugin in the `infrahub_inv.yml` file to define the returned hosts and groupings. Here's a minimal example: * Retrieving attributes for the `InfraDevice` Node Kind in a specific branch * Forcing the inclusion of specific attributes for `InfraDevice` * Creating composed variables for `hostname` infrahub\_inv.yml ``` plugin: opsmill.infrahub.inventory api_endpoint: "http://localhost:8000" nodes: InfraDevice: include: - name - primary_address.address - platform.ansible_network_os - site.parent.name - site.name - role - tags compose: hostname: name ``` ## Advanced configuration example[​](#advanced-configuration-example "Direct link to Advanced configuration example") This example demonstrates a more comprehensive configuration, including: * Retrieving attributes for the `InfraDevice` Node Kind in a specific branch * Forcing the inclusion of specific attributes for `InfraDevice` * Creating composed variables for `hostname` and `platform` * Creating groups based on the `site` name and `country` name infrahub\_inv.yml (Advanced) ``` strict: true branch: "branch1" nodes: InfraDevice: include: - name - primary_address.address - platform.ansible_network_os - site.parent.name - site.name - role - tags compose: hostname: name platform: platform.ansible_network_os ansible_host: primary_address.address | ansible.utils.ipaddr('address') keyed_groups: - prefix: site key: site.name | lower - prefix: country key: site.parent.name | lower ``` ## Viewing the inventory graph[​](#viewing-the-inventory-graph "Direct link to Viewing the inventory graph") To visualize the inventory retrieved from Infrahub, run the `ansible-inventory` command with the `-i` flag, specifying the `infrahub_inv.yml` file as the source, followed by `--graph`: ``` ansible-inventory -i infrahub_inv.yml --graph ``` **Sample Output** ``` @all: |--@ungrouped: |--@site_atl1: | |--atl1-edge1 | |--atl1-edge2 | |--atl1-core1 | |--atl1-core2 | |--atl1-leaf1 | |--atl1-leaf2 |--@country_united_states_of_america: | |--atl1-edge1 | |--atl1-edge2 | |--atl1-core1 | |--atl1-core2 | |--atl1-leaf1 | |--atl1-leaf2 | |--ord1-edge1 | |--ord1-edge2 | |--ord1-core1 | |--ord1-core2 | |--ord1-leaf1 | |--ord1-leaf2 | |--jfk1-edge1 | |--jfk1-edge2 | |--jfk1-core1 | |--jfk1-core2 | |--jfk1-leaf1 |--@site_ord1: | |--ord1-edge1 | |--ord1-edge2 | |--ord1-core1 | |--ord1-core2 | |--ord1-leaf1 | |--ord1-leaf2 |--@site_jfk1: | |--jfk1-edge1 | |--jfk1-edge2 | |--jfk1-core1 | |--jfk1-core2 | |--jfk1-leaf1 ``` ## Viewing the inventory list[​](#viewing-the-inventory-list "Direct link to Viewing the inventory list") To view the inventory list, run: ``` ansible-inventory -i infrahub_inv.yml --list ``` **Sample Output** ``` { "_meta": { "hostvars": { "atl1-core1": { "id": "181fd07a-e43c-f377-35bf-c513b592513f", "site": { "id": "181fd075-b6d7-bd90-35b7-c51c888ef203", "name": "atl1" } }, "atl1-core2": { "id": "181fd07b-2615-5c76-35b3-c519087e0eb5", "site": { "id": "181fd075-b6d7-bd90-35b7-c51c888ef203", "name": "atl1" } }, ... } }, "all": { "children": [ "ungrouped", "site_atl1", "site_ord1", "site_jfk1", "site_den1", "site_dfw1" ] }, "site_atl1": { "hosts": [ "atl1-edge1", "atl1-edge2", "atl1-core1", "atl1-core2", "atl1-leaf1", "atl1-leaf2" ] }, ... } ``` ## Running playbooks with dynamic inventory[​](#running-playbooks-with-dynamic-inventory "Direct link to Running playbooks with dynamic inventory") To run a playbook that uses the dynamic inventory, specify the inventory file when executing the playbook: ``` ansible-playbook -i infrahub_inv.yml your_playbook.yml ``` Replace `infrahub_inv.yml` with the path to your dynamic inventory file, and `your_playbook.yml` with the name of your playbook file. ### Targeting groups in your playbook[​](#targeting-groups-in-your-playbook "Direct link to Targeting groups in your playbook") To target groups from the inventory in your playbook, reference the groups as normal. For example, to target the `site_atl1` group: playbook.yml ``` --- - name: Playbook using Infrahub Dynamic Inventory hosts: site_atl1 tasks: - name: Gather facts setup: - name: Print host facts debug: var: ansible_facts ``` --- # Install `opsmill.infrahub` Ansible collection info This guide assumes you have basic knowledge of Ansible and its ecosystem. For more information on using Ansible, please refer to the [official Ansible documentation](https://docs.ansible.com/ansible/latest/index.html). ## Requirements[​](#requirements "Direct link to Requirements") * You must be running one of the two most recent releases of Infrahub * A Infrahub API token when using modules (at least read-only for the inventory). * Python 3.11+ * Python modules: * infrahub-sdk * Ansible 2.18+ ## Installation[​](#installation "Direct link to Installation") ### Python modules and Ansible[​](#python-modules-and-ansible "Direct link to Python modules and Ansible") ``` pip install infrahub-sdk pip install ansible ``` ### Infrahub Ansible collection[​](#infrahub-ansible-collection "Direct link to Infrahub Ansible collection") Before using this collection, you need to install it with the Ansible Galaxy command-line tool: ``` ansible-galaxy collection install opsmill.infrahub ``` You can also include it in a `requirements.yml` file and install it with `ansible-galaxy collection install -r requirements.yml`, using the format: ``` collections: - name: opsmill.infrahub ``` To upgrade the collection to the latest available version, run the following command: ``` ansible-galaxy collection install opsmill.infrahub --upgrade ``` You can also install a specific version of the collection, for example, if you need to downgrade. Use the following syntax to install a specific version: ``` ansible-galaxy collection install opsmill.infrahub:==VERSION ``` See using [Ansible collections](https://docs.ansible.com/ansible/latest/collections_guide/collections_installing.html) for more Collapsible. ### Other installation options[​](#other-installation-options "Direct link to Other installation options") #### Build from source[​](#build-from-source "Direct link to Build from source") * Build from source * Build from source (Pull Request) 1. **Step 1**: `git clone git@github.com:opsmill/infrahub-ansible.git` 2. **Step 2**: `cd ansible_modules` 3. **Step 3**: `ansible-galaxy collection build .` 4. **Step 4**: `ansible-galaxy collection install opsmill-infrahub*.tar.gz` *For more Collapsible on building from source, please refer to \[our dedicated documentation]\(link if available).* *Useful for testing changes in Pull Requests.* 1. **Step 1**: `git clone git@github.com:opsmill/infrahub-ansible.git` 2. **Step 2**: `cd ansible_modules` 3. **Step 3**: `git fetch origin pull//head:` 4. **Step 4**: `git checkout ` 5. **Step 5**: `ansible-galaxy collection build .` 6. **Step 6**: `ansible-galaxy collection install opsmill-infrahub*.tar.gz` *For detailed information on checking out pull requests locally, please refer to [GitHub's Documentation](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/checking-out-pull-requests-locally).* --- # Create nodes in Infrahub ## Overview[​](#overview "Direct link to Overview") Infrahub provides two methods for creating nodes using GraphQL: * [**Query Plugin**](/ansible/references/plugins/query_graphql_module.md): Use the `opsmill.infrahub.query_graphql` module with a mutation to create nodes. * [**Node Module**](/ansible/references/plugins/node_module.md): Use the dedicated `opsmill.infrahub.node` module to create, update and delete nodes directly. `opsmill.infrahub.node` allowed a better control in your playbook as the module support both Ansible diff and check options. ## Usage[​](#usage "Direct link to Usage") ### Creating a node[​](#creating-a-node "Direct link to Creating a node") * Node Plugin * Creating nodes with the query plugin The Create Node Plugin simplifies node creation and supports the `allow_upsert` parameter. When `allow_upsert` is set to true (default mode), the module will update an existing node if it already exists, preventing duplicate entries or errors. This example demonstrates how to create various nodes (an InfraDevice, a VLAN, and an InterfaceL2) using the dedicated Create Node Plugin: node.yml ``` --- - name: Create InfraDevice examples gather_facts: false hosts: localhost tasks: - name: Create a InfraDevice 'atl1-sw1' opsmill.infrahub.node: kind: "InfraDevice" data: name: "atl1-sw1" type: "MX204" site: "atl1" platform: "Juniper JunOS" - name: Create a VLAN 'atl1-vlan1' opsmill.infrahub.node: kind: "InfraVLAN" data: name: "atl1-vlan1" vlan_id: 10 status: "active" role: "server" - name: Create a InterfaceL2 'xe-0/0/1' on 'atl1-sw1' and add VLAN 'atl1-vlan1' opsmill.infrahub.node: kind: "InfraInterfaceL2" data: name: "xe-0/0/1" device: "atl1-sw1" speed: 10 l2_mode: "Access" tagged_vlan: - ["atl1-vlan10", "10"] ``` **Example Output from node.yml** ``` PLAY [Create InfraDevice examples] ******************************************************************** TASK [Create a InfraDevice 'atl1-sw1'] **************************************************************** changed: [localhost] => {"InfraDevice": {"name": "atl1-sw1", "platform": "Juniper JunOS", "site": "atl1", "type": "MX204"}, "changed": true, "msg": "InfraDevice 18237235-3a70-85f6-2db7-c5139cfb8e9a created"} TASK [Create a VLAN 'atl1-vlan1'] ******************************************************************** changed: [localhost] => {"InfraVLAN": {"name": "atl1-vlan1", "role": "server", "status": "active", "vlan_id": 10}, "changed": true, "msg": "InfraVLAN 18237235-95f7-64ce-2dbc-c51f7c63c1d5 created"} TASK [Create a InterfaceL2 'xe-0/0/1' on 'atl1-sw1' and add VLAN 'atl1-vlan1'] *********************** changed: [localhost] => {"InfraInterfaceL2": {"device": "atl1-sw1", "l2_mode": "Access", "name": "xe-0/0/1", "speed": 10, "tagged_vlan": [["atl1-vlan10", "10"]]}, "changed": true, "msg": "InfraInterfaceL2 18237235-dd9a-4034-2dbb-c51a0c3505ce created"} PLAY RECAP ******************************************************************************************** localhost : ok=3 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ``` In this example, we create BuiltinTags using a GraphQL mutation via the Query Plugin: node.yml ``` --- - name: Create BuiltinTag using Query module gather_facts: false hosts: localhost vars: infrahub_tags: - name: my-tag10 description: "This is tag 10 description" - name: my-tag11 description: "This is tag 11 description" tasks: - name: Create tags opsmill.infrahub.query_graphql: query: | mutation CreateTag($name: String!, $description: String!) { BuiltinTagCreate( data: { name: {value: $name}, description: {value: $description} } ) { ok object { id } } } graph_variables: name: "{{ item.name }}" description: "{{ item.description }}" loop: "{{ infrahub_tags }}" register: created_tags ``` ### Deleting a node[​](#deleting-a-node "Direct link to Deleting a node") * Node Plugin To delete a node, set the `state` parameter to absent. The module will remove the node from Infrahub. delete\_tag.yml ``` --- - name: Delete tag 'tag1' hosts: localhost gather_facts: false tasks: - name: Delete tag1 opsmill.infrahub.node: kind: "BuiltinTag" data: name: "tag1" state: absent ``` **Example Output from delete\_tag.yml** ``` TASK [Delete tag1] ********************************************************************* changed: [localhost] => {"BuiltinTag": {"__typename": "BuiltinTag", "description": {"value": null}, "display_label": "tag1", "hfid": ["tag1"], "id": "182371ab-d055-096d-2dbd-c51e34a81383", "name": {"value": "tag1"}}, "changed": true, "msg": "BuiltinTag 182371ab-d055-096d-2dbd-c51e34a81383 deleted"} ``` --- # Retrieve data from Infrahub ## Overview[​](#overview "Direct link to Overview") The **OpsMill Infrahub Ansible Collection** provides two ways to interact with Infrahub GraphQL API: * [**Query Plugin**](/ansible/references/plugins/query_graphql_module.md): The `opsmill.infrahub.query_graphql` module allows you to send a structured GraphQL query and dynamically process the returned results. This method is used as an action plugin within your playbook. * [**Lookup Plugin**](/ansible/references/plugins/lookup_lookup.md): The `opsmill.infrahub.lookup` plugin enables you to retrieve GraphQL data using a query and integrate the results into your playbook (using the lookup plugin syntax). ## Usage[​](#usage "Direct link to Usage") * Querying Infrahub with the Query Plugin * Querying Infrahub with the Lookup Plugin query\_infrahub.yml ``` - name: Infrahub action plugin gather_facts: false hosts: localhost tasks: - name: SET FACTS TO SEND TO GRAPHQL ENDPOINT ansible.builtin.set_fact: variables: device_name: "atl1-edge1" enabled: true query_dict: InfraInterface: '@filters': {enabled__value: '$enabled', device__name__value: '$device_name'} edges: node: id: null name: value: null ... on InfraInterfaceL3: ip_addresses: edges: node: address: value: null ... on InfraInterfaceL2: tagged_vlan: edges: node: vlan_id: value: null name: value: null untagged_vlan: node: vlan_id: value: null name: value: null - name: Execute Query Plugin opsmill.infrahub.query_graphql: query: "{{ query_dict }}" graph_variables: "{{ variables }}" ``` * The `query_dict` defines a **GraphQL query structure**. * The query filters devices by `name` and interfaces by `enabled`. * The **`opsmill.infrahub.query_graphql`** plugin executes the query and returns the results. To run the query playbook, use: ``` ansible-playbook query_infrahub.yml ``` This executes the query and fetches data dynamically from **Infrahub**. **Example Output from Query plugin** ``` ok: [localhost] => { "msg": { "edges": [ { "node": { "id": "18219d8a-5417-acae-2dbc-c5156eefb194", "ip_addresses": { "edges": [ { "node": { "address": { "value": "10.1.0.0/31" } } } ] }, "name": { "value": "Ethernet1" } } }, { "node": { "id": "18219d8b-afcc-3d1b-2db4-c510fcc52b8a", "ip_addresses": { "edges": [] }, "name": { "value": "Ethernet10" } } }, { "node": { "id": "18219d8b-b7bc-dc82-2dba-c51741f8258e", "name": { "value": "Ethernet11" }, "tagged_vlan": { "edges": [] }, "untagged_vlan": { "node": { "name": { "value": "atl1_server" }, "vlan_id": { "value": 200 } } } } }, { "node": { "id": "18219d8b-d71d-e7d1-2dba-c515e35ae516", "name": { "value": "Ethernet12" }, "tagged_vlan": { "edges": [] }, "untagged_vlan": { "node": { "name": { "value": "atl1_server" }, "vlan_id": { "value": 200 } } } } }, { "node": { "id": "18219d8a-7e5a-df0a-2db2-c51e9967f805", "ip_addresses": { "edges": [ { "node": { "address": { "value": "10.1.0.2/31" } } } ] }, "name": { "value": "Ethernet2" } } }, { "node": { "id": "18219d8a-8673-961c-2db0-c5179da68d60", "ip_addresses": { "edges": [ { "node": { "address": { "value": "10.1.0.20/31" } } } ] }, "name": { "value": "Ethernet3" } } }, { "node": { "id": "18219d8a-8a01-a4b8-2db8-c51c7024f9dd", "ip_addresses": { "edges": [ { "node": { "address": { "value": "10.1.0.24/31" } } } ] }, "name": { "value": "Ethernet4" } } }, { "node": { "id": "18219d8a-8d42-143c-2dbf-c51a0cc428a9", "ip_addresses": { "edges": [ { "node": { "address": { "value": "203.111.0.1/29" } } } ] }, "name": { "value": "Ethernet5" } } }, { "node": { "id": "18219d8b-63d4-9267-2db4-c518a51761cf", "ip_addresses": { "edges": [ { "node": { "address": { "value": "203.111.0.9/29" } } } ] }, "name": { "value": "Ethernet6" } } }, { "node": { "id": "18219d8b-8e96-24cf-2db4-c51ba3de6b92", "ip_addresses": { "edges": [] }, "name": { "value": "Ethernet7" } } }, { "node": { "id": "18219d8b-921b-2dd9-2dbb-c519044a4a98", "ip_addresses": { "edges": [] }, "name": { "value": "Ethernet8" } } }, { "node": { "id": "18219d8b-95ce-829d-2dbd-c519355daf70", "ip_addresses": { "edges": [ { "node": { "address": { "value": "203.111.0.17/29" } } } ] }, "name": { "value": "Ethernet9" } } }, { "node": { "id": "18219d89-dbf4-92da-2dbd-c51e7009abd0", "ip_addresses": { "edges": [ { "node": { "address": { "value": "10.0.0.1/32" } } } ] }, "name": { "value": "Loopback0" } } }, { "node": { "id": "18219d8a-3900-2637-2db7-c511a30b601a", "ip_addresses": { "edges": [ { "node": { "address": { "value": "172.16.0.1/16" } } } ] }, "name": { "value": "Management0" } } }, { "node": { "id": "18219d8b-dfe6-4944-2db9-c511dc191dc6", "name": { "value": "port-channel1" } } } ] } } ``` This example retrieves a list of all locations (`LocationSite`) from Infrahub and prints them. lookup\_infrahub.yml ``` - name: Infrahub lookup gather_facts: false hosts: localhost tasks: - name: SET FACT OF STRING ansible.builtin.set_fact: query_string: | query { LocationSite { edges { node { name { value } } } } } - name: Obtain list of sites from Infrahub ansible.builtin.set_fact: query_response: "{{ query('opsmill.infrahub.lookup', query=query_string) }}" - name: Print result ansible.builtin.debug: msg: "{{ query_response }}" ``` * The `query_string` defines a **GraphQL query** to retrieve **all locations**. * The **`opsmill.infrahub.lookup`** plugin executes the query. * The result is stored in `query_response` and printed. To run the lookup playbook, use: ``` ansible-playbook lookup_infrahub.yml ``` This executes the lookup and retrieves the requested data. **Example Output from lookup playbook** ``` PLAY [Infrahub lookup] ************************************************************************************************************************************* TASK [SET FACT OF STRING] ********************************************************************************************************************************** ok: [localhost] TASK [Obtain list of sites from Infrahub] ****************************************************************************************************************** ok: [localhost] TASK [Print result] **************************************************************************************************************************************** ok: [localhost] => { "msg": [ { "node": { "name": { "value": "atl1" } } }, { "node": { "name": { "value": "den1" } } }, { "node": { "name": { "value": "dfw1" } } }, { "node": { "name": { "value": "jfk1" } } }, { "node": { "name": { "value": "ord1" } } } ] } ``` ## Conclusion[​](#conclusion "Direct link to Conclusion") Both the Query Plugin and Lookup Plugin allow you to perform GraphQL queries against Infrahub, but they are tailored to different usage patterns in your Ansible playbooks. Use the Query Plugin when you want to execute complex, structured queries with variables as an action, and the Lookup Plugin when you prefer a simpler query string approach for integrating query results directly into variables. --- # Modules modules Fetch the content of an artifact from Infrahub through Infrahub SDK ## Parameters[​](#parameters "Direct link to Parameters") | Parameter | Type | Required | Default | Description | | ---------------- | ------ | -------- | ------- | ------------------------------------------------------------------------- | | `api_endpoint` | `str` | No | | Endpoint of the Infrahub API, optional env=INFRAHUB\_ADDRESS | | `token` | `str` | No | | The API token created through Infrahub, optional env=INFRAHUB\_API\_TOKEN | | `timeout` | `int` | No | 10 | Timeout for Infrahub requests in seconds | | `artifact_name` | `str` | No | | Name of the artifact | | `artifact_id` | `str` | No | | UUID of the artifact | | `target_id` | `str` | No | | Id of the target for this artifact | | `branch` | `str` | No | main | Branch in which the request is made | | `validate_certs` | `bool` | No | True | Whether or not to validate SSL of the Infrahub instance | ## Examples[​](#examples "Direct link to Examples") ``` --- - name: Infrahub action plugin artifact_fetch gather_facts: false hosts: platform_eos vars: ansible_become: true tasks: - name: Query Startup Config for Edge Devices opsmill.infrahub.artifact_fetch: artifact_name: "Startup Config for Edge devices" target_id: "{{ id }}" register: startup_artifact - name: Save configs to localhost ansible.builtin.copy: content: "{{ startup_artifact.text }}" dest: "/tmp/{{ inventory_hostname }}-startup.conf" mode: '644' delegate_to: localhost ``` ## Return values[​](#return-values "Direct link to Return values") | Key | Type | Description | | ------ | ------ | --------------------------------------- | | `json` | `dict` | Content of the artifact in JSON format. | | `text` | `str` | Content of the artifact in TEXT format. | --- # Modules modules Triggers the regeneration of an artifact for a specified target node in Infrahub. The module looks up the artifact associated with the target node and triggers regeneration. ## Parameters[​](#parameters "Direct link to Parameters") | Parameter | Type | Required | Default | Description | | ---------------- | ------ | -------- | ------- | ------------------------------------------------------------------------------ | | `api_endpoint` | `str` | No | | Endpoint of the Infrahub API, optional env=INFRAHUB\_ADDRESS | | `token` | `str` | No | | The API token created through Infrahub, optional env=INFRAHUB\_API\_TOKEN | | `timeout` | `int` | No | 10 | Timeout for Infrahub requests in seconds | | `artifact_name` | `str` | No | | Name of the artifact (mutually exclusive with artifact\_id) | | `artifact_id` | `str` | No | | UUID of the artifact (mutually exclusive with artifact\_name) | | `target_id` | `str` | Yes | | UUID of the target node (e.g., device ID) that the artifact is associated with | | `branch` | `str` | No | main | Branch in which the request is made | | `validate_certs` | `bool` | No | True | Whether to validate SSL of the Infrahub instance | | `state` | `str` | No | present | Use C(present) or C(absent) for adding or removing. | ## Examples[​](#examples "Direct link to Examples") ``` --- # Example 1: Regenerate artifact by name for a device - name: Regenerate artifact by name gather_facts: false hosts: localhost connection: local tasks: - name: Regenerate Startup Config for a device opsmill.infrahub.artifact_generate: artifact_name: "Startup Config" target_id: "{{ device_id }}" register: result - name: Display regeneration result ansible.builtin.debug: var: result --- # Example 2: Regenerate artifact by UUID - name: Regenerate artifact by ID gather_facts: false hosts: localhost tasks: - name: Regenerate specific artifact by ID opsmill.infrahub.artifact_generate: artifact_id: "12345678-1234-1234-1234-123456789abc" target_id: "{{ device_id }}" --- # Example 3: Using with Infrahub inventory plugin # Run with: ansible-playbook playbook.yml -i inventory.infrahub.yml -l "*edge*" - name: Regenerate artifacts using inventory host IDs gather_facts: false hosts: all connection: local tasks: - name: Regenerate Startup Config for each device opsmill.infrahub.artifact_generate: artifact_name: "Startup Config" target_id: "{{ id }}" register: result ``` ## Return values[​](#return-values "Direct link to Return values") | Key | Type | Description | | --------------- | ------ | ------------------------------------------------------------ | | `artifact_id` | `str` | UUID of the artifact that was regenerated | | `artifact_name` | `str` | Name of the artifact that was regenerated | | `definition_id` | `str` | UUID of the artifact definition | | `target_id` | `str` | UUID of the target node that the artifact is associated with | | `changed` | `bool` | Whether the artifact regeneration was triggered | | `msg` | `str` | Message indicating the result of the operation | --- # Modules modules Creates, Updates or Deletes a branch (InrahubBranch) in Infrahub through Infrahub SDK ## Parameters[​](#parameters "Direct link to Parameters") | Parameter | Type | Required | Default | Description | | ---------------- | ------ | -------- | ------- | ------------------------------------------------------------------------- | | `api_endpoint` | `str` | No | | Endpoint of the Infrahub API, optional env=INFRAHUB\_ADDRESS | | `token` | `str` | No | | The API token created through Infrahub, optional env=INFRAHUB\_API\_TOKEN | | `timeout` | `int` | No | 10 | Timeout for Infrahub requests in seconds | | `name` | `str` | Yes | | Name of the branch to create | | `sync_with_git` | `bool` | No | False | Whether to sync the branch with git | | `description` | `str` | No | | Description of the branch | | `validate_certs` | `bool` | No | True | Whether or not to validate SSL of the Infrahub instance | | `state` | `str` | No | present | Use C(present) or C(absent) for adding or removing. | ## Examples[​](#examples "Direct link to Examples") ``` --- - name: Infrahub playbook for opsmill.infrahub.branch gather_facts: false hosts: localhost tasks: - name: Create a Branch 'test' opsmill.infrahub.branch: name: "test" sync_with_git: false description: "This is a test branch" state: present - name: Delete a Branch 'test' opsmill.infrahub.branch: name: "test" state: absent ``` ## Return values[​](#return-values "Direct link to Return values") | Key | Type | Description | | -------- | ------ | ----------------------------------------------------------------------- | | `object` | `dict` | Serialized Branch object as created or already existent within Infrahub | | `msg` | `str` | Message indicating failure or info about what has been achieved | --- # Inventory inventory Get inventory hosts from Infrahub ## Parameters[​](#parameters "Direct link to Parameters") | Parameter | Type | Required | Default | Description | | ------------------------ | ------ | -------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `plugin` | \`\` | Yes | | token that ensures this is a source file for the 'opsmill.infrahub' plugin. | | `api_endpoint` | \`\` | Yes | | Endpoint of the Infrahub API | | `token` | \`\` | Yes | | Infrahub API token to be able to read against Infrahub. | | `timeout` | `int` | No | 10 | Timeout for Infrahub requests in seconds | | `prefetch_relationships` | `bool` | No | True | Prefetch relationships for Infrahub nodes | | `nodes` | `dict` | Yes | | Configuration for specific node types within Infrahub. Defines the attributes to include or exclude for each node. | | `branch` | `str` | No | main | Branch in which the request is made | | `compose` | `dict` | No | | List of custom Ansible host vars to create from the objects fetched from Infrahub | | `keyed_groups` | `list` | No | \[] | Create groups based on attributes or relationships. groups is created as `attribute__value` | | `groups` | `dict` | No | | Create groups based on jinja filter. | | `hostnames` | `list` | No | \[] | A list of attribute paths used to determine the inventory hostname. Each entry is a dotted path resolved against node attributes (e.g., "name", "primary\_address.address"). The special value "display\_label" resolves to the node's display label. First non-empty string value wins. Falls back to display\_label if none resolve. Referenced attributes must be present in the node's include list. | | `validate_certs` | \`\` | No | True | Whether or not to validate SSL of the Infrahub instance | ## Examples[​](#examples "Direct link to Examples") ``` # inventory.yml file in YAML format # Example command line: ansible-inventory -v --list -i .yml # Add -vvv to the command to also see the GraphQL query that gets sent in the debug output. # Add -vvvv to the command to also see the JSON response that comes back in the debug output. # Minimum required parameters plugin: opsmill.infrahub.inventory api_endpoint: http://localhost:8000 # Can be omitted if the INFRAHUB_ADDRESS environment variable is set token: 1234567890123456478901234567 # Can be omitted if the INFRAHUB_API_TOKEN environment variable is set # Complete Example # This will : # - Retrieve in the branch "branch1" attributes for the Node Kind "InfraDevice" # - The attributes wanted for "InfraDevice" are forced with the keyword "include" # - Create 2 compose variable "hostname" ad "platform" (platform will override the attribute platform retrieved) # - Create group based on the "site" name strict: true branch: "branch1" nodes: InfraDevice: include: - name - platform.ansible_network_os - primary_address.address - site.name - interfaces compose: hostname: name platform: platform.ansible_network_os keyed_groups: - prefix: site key: site.name # Using hostnames to set clean inventory hostnames plugin: opsmill.infrahub.inventory api_endpoint: http://localhost:8000 hostnames: - name - display_label nodes: InfraDevice: include: - name - primary_address.address ``` ## Return values[​](#return-values "Direct link to Return values") | Key | Type | Description | | ------- | ------ | ------------------------------------------------ | | `_list` | `list` | list of composed dictionaries with key and value | --- # Lookup lookup Get inventory hosts from Infrahub ## Parameters[​](#parameters "Direct link to Parameters") | Parameter | Type | Required | Default | Description | | ----------------- | ------ | -------- | ------- | -------------------------------------------------------- | | `api_endpoint` | \`\` | Yes | | Endpoint of the Infrahub API | | `token` | \`\` | Yes | | Infrahub API token to be able to read against Infrahub. | | `timeout` | `int` | No | 10 | Timeout for Infrahub requests in seconds | | `query` | `str` | Yes | | GraphQL query to send to Infrahub to obtain desired data | | `graph_variables` | `dict` | No | | dictionary of keys/values to pass into the GraphQL query | | `branch` | `str` | No | main | Branch in which the request is made | | `validate_certs` | \`\` | No | True | Whether or not to validate SSL of the Infrahub instance | ## Examples[​](#examples "Direct link to Examples") ``` - name: Infrahub lookup gather_facts: false hosts: localhost tasks: - name: SET FACT OF STRING ansible.builtin.set_fact: query_string: | query { BuiltinLocation { edges { node { name { value } } } } } - name: Obtain list of sites from Infrahub ansible.builtin.set_fact: query_response: "{{ query('opsmill.infrahub.lookup', query=query_string) }}" - name: Print result ansible.builtin.debug: msg: "{{ query_response }}" ``` ## Return values[​](#return-values "Direct link to Return values") | Key | Type | Description | | ------ | ------ | ---------------------------------------------- | | `data` | `dict` | Data result from the Infrahub GraphQL endpoint | --- # Modules modules Creates, Updates or Deletes a node of a given Kind in Infrahub through Infrahub SDK ## Parameters[​](#parameters "Direct link to Parameters") | Parameter | Type | Required | Default | Description | | ---------------- | ------ | -------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `api_endpoint` | `str` | No | | Endpoint of the Infrahub API, optional env=INFRAHUB\_ADDRESS | | `token` | `str` | No | | The API token created through Infrahub, optional env=INFRAHUB\_API\_TOKEN | | `timeout` | `int` | No | 10 | Timeout for Infrahub requests in seconds | | `kind` | `str` | Yes | | Kind of node to create | | `data` | `raw` | Yes | | Dictionary of node attributes | | `branch` | `str` | No | main | Branch in which the request is made | | `validate_certs` | `bool` | No | True | Whether or not to validate SSL of the Infrahub instance | | `state` | `str` | No | present | Use C(present) or C(absent) for adding or removing. | | `file_path` | `str` | No | None | Local filesystem path to the file to upload when creating or updating a CoreFileObject node. Required when the kind inherits from CoreFileObject (unless C(fetch\_file) is used instead). Mutually exclusive with C(fetch\_file). When provided, the module computes a SHA-1 checksum and skips the upload if it matches the server-side checksum (idempotent). Fails if the kind does not inherit from CoreFileObject. Ignored in check mode (no upload performed). | | `fetch_file` | `bool` | No | False | When C(true), download the file content from the CoreFileObject node and include it in the result as C(binary) (base64-encoded) and C(text) (UTF-8 decoded for text MIME types, null otherwise). Required when the kind inherits from CoreFileObject and C(file\_path) is not provided. Mutually exclusive with C(file\_path). Ignored in check mode (no download performed). Fails if the kind does not inherit from CoreFileObject. | ## Examples[​](#examples "Direct link to Examples") ``` --- - name: Infrahub playbook for opsmill.infrahub.node gather_facts: false hosts: localhost tasks: - name: Create tag1 opsmill.infrahub.node: kind: "BuiltinTag" data: name: "tag1" state: present - name: Delete tag1 opsmill.infrahub.node: kind: "BuiltinTag" data: name: "tag1" state: absent ``` ## Return values[​](#return-values "Direct link to Return values") | Key | Type | Description | | -------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `object` | `dict` | Serialized object as created or already existent within Infrahub | | `msg` | `str` | Message indicating failure or info about what has been achieved | | `binary` | `str` | Base64-encoded file content downloaded from the CoreFileObject node. Present only when I(fetch\_file=true) and not in check mode. | | `text` | `str` | UTF-8 decoded file content for text MIME types (text/plain, application/json, etc.). null for binary MIME types. Present only when I(fetch\_file=true) and not in check mode. | --- # Modules modules Downloads the binary file content stored on a CoreFileObject schema node. Identifies the node by UUID (node\_id) or HFID (hfid). Optionally saves the file to a local destination path. Returns base64-encoded binary content and metadata regardless of I(dest). ## Parameters[​](#parameters "Direct link to Parameters") | Parameter | Type | Required | Default | Description | | ---------------- | ------ | -------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `api_endpoint` | `str` | No | | Endpoint of the Infrahub API, optional env=INFRAHUB\_ADDRESS | | `token` | `str` | No | | The API token created through Infrahub, optional env=INFRAHUB\_API\_TOKEN | | `timeout` | `int` | No | 10 | Timeout for Infrahub requests in seconds | | `validate_certs` | `bool` | No | True | Whether or not to validate SSL of the Infrahub instance | | `branch` | `str` | No | main | Branch in which the request is made | | `kind` | `str` | Yes | | Schema kind that inherits from CoreFileObject (e.g. C(NetworkCircuitContract)) | | `node_id` | `str` | No | | UUID of the CoreFileObject node to fetch. One of I(node\_id) or I(hfid) is required. | | `hfid` | `list` | No | | Human-friendly ID component values for the CoreFileObject node. One of I(node\_id) or I(hfid) is required. | | `dest` | `str` | No | None | Local path to save the file content. When a directory path is given (trailing slash or existing directory), the file is saved as `{dest}/{node.file_name}`. When a file path is given, the file is saved exactly at that path. When omitted, file content is returned as variables only. | ## Examples[​](#examples "Direct link to Examples") ``` --- - name: Fetch contract PDF by node UUID opsmill.infrahub.object_file_fetch: kind: NetworkCircuitContract node_id: "abc123-uuid" register: fetch_result - name: Write fetched file to disk ansible.builtin.copy: content: "{{ fetch_result.binary | b64decode }}" dest: /tmp/contract.pdf - name: Fetch and save to directory by HFID opsmill.infrahub.object_file_fetch: kind: NetworkCircuitContract hfid: - "contract.pdf" dest: /tmp/contracts/ register: fetch_result # File saved to /tmp/contracts/contract.pdf # fetch_result.dest == "/tmp/contracts/contract.pdf" - name: Fetch and save to explicit file path opsmill.infrahub.object_file_fetch: kind: NetworkCircuitContract node_id: "abc123-uuid" dest: /tmp/my-contract.pdf register: fetch_result # File saved to exactly /tmp/my-contract.pdf ``` ## Return values[​](#return-values "Direct link to Return values") | Key | Type | Description | | ----------- | ----- | ---------------------------------------------------------------------------------------------------------------- | | `binary` | `str` | Base64-encoded file content downloaded from the CoreFileObject node. | | `text` | `str` | UTF-8 decoded file content for text MIME types (text/plain, application/json, etc.). null for binary MIME types. | | `file_name` | `str` | Original filename as stored in Infrahub. | | `file_type` | `str` | MIME type of the file as detected by Infrahub. | | `file_size` | `int` | Size of the file in bytes. | | `checksum` | `str` | SHA-1 hex digest of the file content as stored in Infrahub. | | `node_id` | `str` | UUID of the fetched CoreFileObject node. | | `dest` | `str` | Resolved local path where the file was saved. null if I(dest) was not provided. | | `msg` | `str` | Status message. | --- # Modules modules Queries Infrahub via its GraphQL API through Infrahub SDK ## Parameters[​](#parameters "Direct link to Parameters") | Parameter | Type | Required | Default | Description | | ----------------- | ------ | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `api_endpoint` | `str` | No | | Endpoint of the Infrahub API, optional env=INFRAHUB\_ADDRESS | | `token` | `str` | No | | The API token created through Infrahub, optional env=INFRAHUB\_API\_TOKEN | | `timeout` | `int` | No | 10 | Timeout for Infrahub requests in seconds | | `query` | `str` | Yes | | GraphQL query parameters or filters to send to Infrahub to obtain desired data | | `graph_variables` | `dict` | No | | Dictionary of keys/values to pass into the GraphQL query | | `branch` | `str` | No | main | Branch in which the request is made | | `validate_certs` | `bool` | No | True | Whether or not to validate SSL of the Infrahub instance | | `update_hostvars` | `bool` | No | False | Whether or not to populate data in the in the root (example hostvars\[inventory\_hostname]) or within the 'data' key (example `hostvars[inventory_hostname]['data']`). Beware, that the root keys provided by the query will overwrite any root keys already present, leverage the GraphQL alias feature to avoid issues. | ## Examples[​](#examples "Direct link to Examples") ``` --- - name: Infrahub action plugin query_graphql gather_facts: false hosts: localhost tasks: - name: SET FACTS TO SEND TO GRAPHQL ENDPOINT ansible.builtin.set_fact: variables: device_name: "atl1-edge1" enabled: true query_dict: InfraDevice: '@filters': {name__value: '$device_name'} edges: node: name: value: null interfaces: '@filters': {enabled__value: '$enabled'} edges: node: name: value: null - name: Action Plugin opsmill.infrahub.query_graphql: query: "{{ query_dict }}" graph_variables: "{{ variables }}" ``` ## Return values[​](#return-values "Direct link to Return values") | Key | Type | Description | | ------ | ------ | ---------------------------------------------- | | `data` | `dict` | Data result from the Infrahub GraphQL endpoint | --- # Modules modules Load, check, or export schemas in Infrahub through the Infrahub SDK. Use C(action=load) to load schemas into Infrahub. Use C(action=check) to validate schemas without applying them. Use C(action=export) to export existing schemas from Infrahub. ## Parameters[​](#parameters "Direct link to Parameters") | Parameter | Type | Required | Default | Description | | ---------------------- | ------ | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `api_endpoint` | `str` | No | | Endpoint of the Infrahub API, optional env=INFRAHUB\_ADDRESS | | `token` | `str` | No | | The API token created through Infrahub, optional env=INFRAHUB\_API\_TOKEN | | `timeout` | `int` | No | 10 | Timeout for Infrahub requests in seconds | | `validate_certs` | `bool` | No | True | Whether or not to validate SSL of the Infrahub instance | | `action` | `str` | Yes | | The schema action to perform. C(load) loads schemas into Infrahub. C(check) validates schemas without applying them. C(export) exports existing schemas from Infrahub. | | `branch` | `str` | No | main | Branch in which the request is made | | `schemas` | `list` | No | | List of inline schema definitions (nodes and generics). For C(load) and C(check), at least one of C(schemas) or C(schema\_files) must be provided. | | `schema_files` | `list` | No | | List of YAML file paths containing schema definitions. Files are read on the Ansible controller. For C(load) and C(check), at least one of C(schemas) or C(schema\_files) must be provided. | | `namespaces` | `list` | No | | List of namespace names to filter the export. Only used with C(action=export). | | `wait_until_converged` | `bool` | No | False | Wait for schema to be synchronized across all workers. Only used with C(action=load). | ## Examples[​](#examples "Direct link to Examples") ``` --- - name: Check schema from inline definition opsmill.infrahub.schema: action: check schemas: - name: Site namespace: Location attributes: - name: name kind: Text unique: true - name: Load schema from file opsmill.infrahub.schema: action: load schema_files: - "schemas/my_schema.yml" - name: Load schema with convergence wait opsmill.infrahub.schema: action: load schema_files: - "schemas/my_schema.yml" wait_until_converged: true - name: Export all schemas opsmill.infrahub.schema: action: export register: result - name: Export schemas for specific namespaces opsmill.infrahub.schema: action: export namespaces: - Infra - Location register: result ``` ## Return values[​](#return-values "Direct link to Return values") | Key | Type | Description | | ---------------- | ------ | --------------------------------------------------------------------- | | `changed` | `bool` | Whether the schema was updated (load) or always false (check/export). | | `schema_updated` | `bool` | Whether the schema hash changed after loading. | | `hash` | `str` | New schema hash after loading. | | `previous_hash` | `str` | Previous schema hash before loading. | | `warnings` | `list` | Schema warnings returned during load. | | `valid` | `bool` | Whether the schema passed validation. | | `errors` | `dict` | Validation errors when schema check fails. | | `schemas` | `dict` | Exported schemas organized by namespace. | | `msg` | `str` | Message indicating the result of the operation. | --- The **Infrahub Installation Role** in the **OpsMill Infrahub Ansible Collection** allows you to install and configure Infrahub using `Docker` and `systemd`. # Overview This role automates the installation and configuration of Infrahub by: * Setting up the required directory structure * Configuring Docker Compose * Installing and configuring `systemd` services * Managing Infrahub configuration # Requirements This role requires: * Docker Engine with Docker Compose v2 to be installed * Root or `sudo` access for `systemd` service installation (on the target server) # Role variables | Variable | Type | Default | Description | | ---------------------------------------- | ------ | ----------------------------- | ---------------------------------------------------------------------------------------------------------- | | `install_infrahub_version` | `str` | | Version of Infrahub to install. Can be any Docker image tag name. | | `install_infrahub_url` | `str` | `https://infrahub.opsmill.io` | URL from where to fetch the Infrahub Docker Compose file. | | `install_infrahub_install_directory` | `str` | `/opt/infrahub` | Install directory for the Infrahub files (Docker Compose and configuration file). | | `install_infrahub_config` | `dict` | | Environment variables to pass as configuration for Infrahub. | | `install_infrahub_docker_project` | `str` | `infrahub` | Docker project name to use when starting Infrahub. | | `install_infrahub_docker_pull_images` | `bool` | `true` | Whether to pull the required Docker images. | | `install_infrahub_setup_systemd` | `bool` | `true` | Whether to install the `systemd` service for Infrahub. | | `install_infrahub_systemd_directory` | `str` | `/etc/systemd/system/` | Where to install the `systemd` service unit file. | | `install_infrahub_systemd_service_state` | `str` | `restarted` | Target state of the `systemd` service. Can be used to avoid starting Infrahub during the role's execution. | # Installation example * Quick start * Production setup For a basic installation with default settings: install\_infrahub\_minimal.yml ``` - name: Install Infrahub hosts: infrahub_servers become: true roles: - role: opsmill.infrahub.install vars: install_infrahub_version: "latest" ``` ## Production setup[​](#production-setup "Direct link to Production setup") For a production environment with custom configuration: install\_infrahub\_advanced.yml ``` - name: Install Infrahub with custom configuration hosts: infrahub_servers become: true roles: - role: opsmill.infrahub.install vars: install_infrahub_version: "2.0.0" install_infrahub_install_directory: "/usr/local/infrahub" install_infrahub_docker_project: "infrahub_prod" install_infrahub_docker_pull_images: true install_infrahub_setup_systemd: true install_infrahub_systemd_service_state: "started" infrahub_config: INFRAHUB_ADMIN_EMAIL: "admin@example.com" INFRAHUB_ADMIN_PASSWORD: "secure_password" INFRAHUB_DATABASE_URL: "postgresql://user:pass@db:5432/infrahub" ``` # Usage To install Infrahub, use: ``` ansible-playbook install_infrahub.yml -i inventory.yml ``` --- # Infrahub Backup ## Overview[​](#overview "Direct link to Overview") ### Why reliable backups matter[​](#why-reliable-backups-matter "Direct link to Why reliable backups matter") Infrahub stores critical state for your infrastructure in multiple systems: the Neo4j graph database, the PostgreSQL database that powers the task manager, and external artifact storage. If any piece is lost or corrupted, you can end up with drifted configurations, orphaned tasks, or a completely unusable deployment. Regulatory requirements and business continuity plans often demand auditable recovery procedures, so "having some dumps around" is rarely enough—you need consistent, verifiable recovery points. ### How the tool keeps environments recoverable[​](#how-the-tool-keeps-environments-recoverable "Direct link to How the tool keeps environments recoverable") Infrahub Backup orchestrates backup and restore workflows across deployment targets. It coordinates quiescing and snapshotting services, pulls data out with the right credentials, and packages everything so restores are deterministic. During restore, the tool brings services back in dependency order, reapplies data, and captures logs so you can prove the operation succeeded. Whether you install with Docker Compose, Kubernetes, or a bespoke stack, the CLI abstracts away provider-specific commands and ensures each step runs in a supported sequence. For Kubernetes deployments, the `infrahub-backup` Helm chart provides a declarative way to manage backups through your existing GitOps pipeline. Enable scheduled backups via CronJob, push artifacts to S3-compatible storage, and perform restores—all without requiring direct kubectl access. ### High availability support[​](#high-availability-support "Direct link to High availability support") Infrahub Backup supports high availability (HA) deployments on both Kubernetes and Docker Compose: * **Kubernetes**: HA PostgreSQL is supported via [CloudNativePG](https://cloudnative-pg.io/) only. The tool automatically detects and targets the primary pod during backup and restore — no additional configuration is needed. * **Docker Compose**: HA setups require stopping the `task-manager` and `task-manager-background-svc` containers before running a restore to prevent database accesses during the process. See [Restore from a backup](/backup/guides/restore-backup.md#docker-high-availability-deployments) for details. ### Community edition support[​](#community-edition-support "Direct link to Community edition support") The CLI supports Infrahub Community Edition in addition to Enterprise deployments. When you run a backup against Community Edition, the tool stops the Infrahub service while the snapshot is captured, so plan for temporary downtime. Restores must target the same edition that generated the backup—Enterprise backups cannot be applied to Community Edition instances. ### Best practices[​](#best-practices "Direct link to Best practices") #### Schedule regular backups[​](#schedule-regular-backups "Direct link to Schedule regular backups") * For Kubernetes deployments, enable the `infrahub-backup` Helm chart with CronJob mode for fully automated, declarative backup scheduling integrated with your GitOps workflow. * For Docker Compose deployments, automate backups via cron, systemd timers, or CI workflows; daily is a good baseline, with more frequent snapshots for high-change environments. * Configure S3-compatible storage to automatically push backups off-cluster and apply bucket lifecycle policies for retention management. * Rotate backup artifacts with a retention policy that matches your recovery point objectives, and replicate critical backups to an off-site location. * Monitor backup jobs and alert on failures—silent backup gaps often surface only when it is too late. #### Run regular restore tests[​](#run-regular-restore-tests "Direct link to Run regular restore tests") * Practice restores in a disposable environment at least monthly to confirm that credentials, network access, and storage quotas are still correct. * Track the time required to restore and compare it to your recovery time objectives; tune the schedule or resource sizing if the gap widens. * Document each drill, including any manual steps uncovered, and fold the lessons back into your runbooks so production incidents are predictable. #### Validate backups after major Infrahub upgrades[​](#validate-backups-after-major-infrahub-upgrades "Direct link to Validate backups after major Infrahub upgrades") * Every time you perform a major Infrahub version upgrade, capture a fresh backup immediately afterward and run a full restore rehearsal. * Watch for schema migrations or new services introduced by the upgrade that might require additional credentials, storage buckets, or hooks in your automation. * Keep the validation artifacts—logs, checksums, and timelines—so you can prove post-upgrade recoverability during audits. ## Getting started[​](#getting-started "Direct link to Getting started") ### Tutorials[​](#tutorials "Direct link to Tutorials") Learn the fundamentals through hands-on exercises: * [Getting started with the tool](/backup/tutorials/getting-started.md) - Your first backup and restore operation ### How-to guides[​](#how-to-guides "Direct link to How-to guides") Accomplish specific tasks: #### Infrahub Backup[​](#infrahub-backup "Direct link to Infrahub Backup") * [Install Infrahub Backup](/backup/guides/install.md) - Install the backup tool on your system * [Back up your Infrahub instance](/backup/guides/backup-instance.md) - Create comprehensive backups * [Restore from a backup](/backup/guides/restore-backup.md) - Restore your instance from a backup file * [Back up Infrahub on Kubernetes](/backup/guides/kubernetes-backup.md) - Back up using the Helm chart * [Restore Infrahub on Kubernetes](/backup/guides/kubernetes-restore.md) - Restore using the Helm chart #### Infrahub Collect[​](#infrahub-collect "Direct link to Infrahub Collect") * [Install Infrahub Collect](/backup/guides/install-collect.md) - Install the collect tool on your system * [Collect a troubleshooting bundle](/backup/guides/collect-troubleshooting-bundle.md) - Gather logs and diagnostics for OpsMill support ### Reference[​](#reference "Direct link to Reference") Technical specifications and command details: * [CLI command reference](/backup/reference/commands.md) - Complete list of commands and options * [Configuration reference](/backup/reference/configuration.md) - Environment variables and settings --- # Back up your Infrahub instance This guide walks you through creating a comprehensive backup of your Infrahub instance. If you need to protect your data, prepare for upgrades, or establish disaster recovery procedures, follow these steps to create reliable backups. ## Community edition support[​](#community-edition-support "Direct link to Community edition support") Infrahub Community Edition deployments are fully supported by `infrahub-backup`, but there are a few operational differences to plan for compared to Enterprise environments. warning Backing up Community Edition stops the Infrahub application container while the snapshot is taken. Schedule the command during a maintenance window to avoid user-facing downtime. Restoring a backup that was taken from an Enterprise cluster to a Community Edition instance is not supported. Create and maintain backups from the same edition you intend to restore to. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before creating a backup: * Ensure infrahub-backup is installed and configured * Verify you have sufficient disk space (at least 2x your database size) * Have write permissions to the backup directory * Confirm your Infrahub instance is accessible ## Step 1: Check for running tasks[​](#step-1-check-for-running-tasks "Direct link to Step 1: Check for running tasks") Before initiating a backup, check if there are active tasks that might be interrupted by checking the Infrahub web interface or waiting for natural completion. If tasks are running, you have two options: * Wait for Completion * Force Backup Wait for tasks to complete naturally by monitoring the Infrahub UI, then proceed with backup: ``` infrahub-backup create ``` Force backup despite running tasks: ``` # Force backup (running tasks will be ignored) infrahub-backup create --force ``` ## Step 2: Create the backup[​](#step-2-create-the-backup "Direct link to Step 2: Create the backup") Execute the backup command with your desired options: ### Basic backup[​](#basic-backup "Direct link to Basic backup") For a standard backup with all components: ``` infrahub-backup create ``` ### Selective metadata backup[​](#selective-metadata-backup "Direct link to Selective metadata backup") Control which Neo4j metadata to include: ``` # Backup without metadata (smallest size) infrahub-backup create --neo4jmetadata=none # Backup with only user accounts infrahub-backup create --neo4jmetadata=users # Backup with only roles infrahub-backup create --neo4jmetadata=roles # Backup with everything (default) infrahub-backup create --neo4jmetadata=all ``` ### Custom backup location[​](#custom-backup-location "Direct link to Custom backup location") Specify an alternative backup directory: ``` infrahub-backup create --backup-dir=/mnt/backups/infrahub ``` ### Target specific project[​](#target-specific-project "Direct link to Target specific project") If running multiple Infrahub instances: ``` infrahub-backup create --project=infrahub-production ``` ## Step 3: Monitor backup progress[​](#step-3-monitor-backup-progress "Direct link to Step 3: Monitor backup progress") The backup process provides detailed progress information: ``` INFO[0000] Starting backup process... INFO[0000] Checking for running tasks... INFO[0001] No running tasks found INFO[0001] Creating backup ID: 20250929_143022 INFO[0002] Stopping Infrahub application containers... INFO[0005] Application containers stopped INFO[0005] Backing up Neo4j database... INFO[0015] Neo4j backup completed (1.2GB) INFO[0015] Backing up PostgreSQL database... INFO[0018] PostgreSQL backup completed (256MB) INFO[0020] Creating compressed archive... INFO[0025] Archive created: infrahub_backup_20250929_143022.tar.gz INFO[0025] Starting application containers... INFO[0030] All containers started successfully INFO[0030] Backup completed successfully ``` ## Step 4: Verify backup integrity[​](#step-4-verify-backup-integrity "Direct link to Step 4: Verify backup integrity") After backup completion, verify the backup file: ### Check backup file[​](#check-backup-file "Direct link to Check backup file") ``` # List backup files with sizes ls -lh infrahub_backups/ # Verify archive integrity tar -tzf infrahub_backups/infrahub_backup_20250929_143022.tar.gz > /dev/null && echo "Archive is valid" # View backup metadata tar -xzOf infrahub_backups/infrahub_backup_20250929_143022.tar.gz backup_information.json | jq '.' ``` ### Validate backup contents[​](#validate-backup-contents "Direct link to Validate backup contents") The backup should contain: * `backup_information.json` - Backup information and checksums * `database/` - Neo4j database files * `prefect.dump` - PostgreSQL dump Example metadata structure: ``` { "metadata_version": 2025092500, "backup_id": "20250929_143022", "created_at": "2025-09-29T14:30:22Z", "tool_version": "1.0.0", "infrahub_version": "0.15.0", "components": ["database", "task-manager-db"], "checksums": { "database": "sha256:abc123...", "prefect.dump": "sha256:def456..." }, "redacted": false } ``` ## Step 5: Backup artifact storage[​](#step-5-backup-artifact-storage "Direct link to Step 5: Backup artifact storage") Capture any artifact storage (object stores, shared volumes, artifact registries) that Infrahub references during task execution. Align the snapshot timing with the database backup so the two stay consistent. * Identify the storage location configured for your task manager (bucket, container, or filesystem path). * Copy or synchronize the artifacts into a dated folder or snapshot using the native tooling for your storage provider. * Record the snapshot path or version ID alongside your database backup metadata so operators can retrieve the artifacts during a restore. If your storage tier already offers automatic versioning or replication, confirm the retention window meets your recovery objectives and document how to retrieve the relevant versions during a disaster scenario. ## Step 6: Test restore capability[​](#step-6-test-restore-capability "Direct link to Step 6: Test restore capability") Periodically test restoration to ensure backups are valid: ``` # Review what would be restored infrahub-backup restore infrahub_backups/infrahub_backup_20250929_143022.tar.gz ``` ## Advanced usage[​](#advanced-usage "Direct link to Advanced usage") ### Automated backups with cron[​](#automated-backups-with-cron "Direct link to Automated backups with cron") You can either use a [systemd timer](/backup/guides/install.md#system-service-installation) or regular cron. Create a cron job for regular backups: ``` # Edit crontab crontab -e # Add daily backup at 2 AM 0 2 * * * /usr/local/bin/infrahub-backup create >> /var/log/infrahub-backup.log 2>&1 # With retention management (keep 7 days) 0 2 * * * /usr/local/bin/infrahub-backup create && find /path/to/backups -name "*.tar.gz" -mtime +7 -delete ``` ### Backup with notification[​](#backup-with-notification "Direct link to Backup with notification") Create a wrapper script for notifications: ``` #!/bin/bash BACKUP_RESULT=$(infrahub-backup create 2>&1) if [ $? -eq 0 ]; then echo "Backup successful" | mail -s "Infrahub Backup Success" ops@example.com else echo "$BACKUP_RESULT" | mail -s "Infrahub Backup Failed" ops@example.com exit 1 fi ``` ### S3 backup storage[​](#s3-backup-storage "Direct link to S3 backup storage") Upload backups directly to S3 or S3-compatible storage (like MinIO): * AWS S3 * MinIO * Environment Variables ``` # Upload to S3 (deletes local file by default) infrahub-backup create --s3-upload --s3-bucket my-backups --s3-prefix infrahub/prod # Upload to S3 but keep local copy infrahub-backup create --s3-upload --s3-bucket my-backups --s3-keep-local ``` ``` # Upload to MinIO or S3-compatible storage infrahub-backup create --s3-upload \ --s3-bucket my-backups \ --s3-endpoint http://minio.local:9000 \ --s3-region us-east-1 ``` ``` # Configure S3 via environment variables export INFRAHUB_S3_BUCKET=my-backups export INFRAHUB_S3_PREFIX=infrahub/prod export AWS_ACCESS_KEY_ID=your-access-key export AWS_SECRET_ACCESS_KEY=your-secret-key infrahub-backup create --s3-upload ``` info AWS credentials are loaded from the standard AWS credential chain: environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`), shared credentials file (`~/.aws/credentials`), or IAM roles when running on AWS infrastructure. ### Redacted backups[​](#redacted-backups "Direct link to Redacted backups") If you need to share a backup for debugging or testing purposes without exposing sensitive data, use the `--redact` flag. This replaces all attribute values in the Neo4j database with random UUIDs before the backup is created. danger The `--redact` flag is a **destructive operation** that irreversibly modifies the live database. All attribute values will be replaced with random UUIDs. Only use this on a disposable copy of your instance, never on a production database you intend to keep using. Because this operation is destructive, `--force` is required as confirmation: ``` infrahub-backup create --redact --force ``` The resulting backup archive is marked as redacted in its metadata (`"redacted": true`), so consumers can identify that the data has been anonymized. A typical workflow for creating a redacted backup safely: 1. Create a normal backup of your production instance 2. Restore that backup to a temporary or staging instance 3. Run the redacted backup on the temporary instance 4. Share the redacted backup archive 5. Tear down the temporary instance ### Manual remote backup storage[​](#manual-remote-backup-storage "Direct link to Manual remote backup storage") If you prefer manual transfers, you can copy backups after creation: ``` # Backup locally first infrahub-backup create # Transfer to remote storage LATEST_BACKUP=$(ls -t infrahub_backups/*.tar.gz | head -1) # S3 example (manual) aws s3 cp "$LATEST_BACKUP" s3://my-bucket/infrahub-backups/ # SCP example scp "$LATEST_BACKUP" backup-server:/backups/infrahub/ # Rsync example rsync -avz "$LATEST_BACKUP" backup-server:/backups/infrahub/ ``` ## Validation[​](#validation "Direct link to Validation") Confirm your backup strategy works: * ✓ Backup completes without errors * ✓ Archive passes integrity check * ✓ Metadata contains expected components * ✓ File size is reasonable for your data volume * ✓ Test restore succeeds in non-production environment ## Related resources[​](#related-resources "Direct link to Related resources") * [Restore from a backup](/backup/guides/restore-backup.md) * [CLI command reference](/backup/reference/commands.md) --- # Collect a troubleshooting bundle Use `infrahub-collect` to gather everything OpsMill support needs to investigate an issue — service logs, diagnostic status, configuration, and metrics — into a single local archive. Run it when support asks for logs, or to attach diagnostic data to a support request. The same command produces the same bundle on Docker Compose and Kubernetes deployments. info Collection is strictly read-only. Unlike `infrahub-backup create`, the collect command never stops, restarts, or scales any container or pod, so it is safe to run against a production instance at any time — including while the instance is degraded. Collect or back up? A troubleshooting bundle contains diagnostics for support to read; a [backup](/backup/guides/backup-instance.md) is a restorable snapshot of your data. When support asks for both, run `infrahub-collect create --include-backup` to produce them in one run. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before collecting a bundle: * Docker Compose * Kubernetes - The `infrahub-collect` binary is installed ([installation guide](/backup/guides/install-collect.md)) - Docker and Docker Compose are available to your user - The Infrahub Compose project is running (fully or partially) * The `infrahub-collect` binary is installed ([installation guide](/backup/guides/install-collect.md)) * `kubectl` is configured with access to the cluster running Infrahub * Your role can read pod logs and execute commands in pods (`pods/log` and `pods/exec`); no write or scale permissions are needed The binary is self-contained and collection works fully offline: it uses your existing Docker or kubectl access and performs no network access beyond your deployment itself. ## Step 1: Verify environment detection[​](#step-1-verify-environment-detection "Direct link to Step 1: Verify environment detection") Confirm the tool can find your Infrahub deployment: ``` infrahub-collect environment detect ``` The tool auto-detects Docker Compose projects and Kubernetes namespaces containing Infrahub. To list every deployment it detects: ``` infrahub-collect environment list ``` ## Step 2: Create the bundle[​](#step-2-create-the-bundle "Direct link to Step 2: Create the bundle") * Docker Compose * Kubernetes For a single Infrahub project, run: ``` infrahub-collect create ``` If several Infrahub projects run on the same host, target one explicitly: ``` infrahub-collect create --project=infrahub-production ``` If a single namespace contains Infrahub, run: ``` infrahub-collect create ``` To target a specific namespace: ``` infrahub-collect create --k8s-namespace=infrahub-prod ``` On Kubernetes, the bundle includes logs from **every replica** of each service, and for pods that crashed and restarted it also captures the previous container's logs — often exactly where the error is. By default the bundle is written to `./infrahub_bundles`. Use `--output-dir` to choose another location: ``` infrahub-collect create --output-dir=/tmp/support ``` ## Step 3: Monitor collection progress[​](#step-3-monitor-collection-progress "Direct link to Step 3: Monitor collection progress") The command streams progress as each collector runs: ``` INFO[0000] Detecting deployment environment... INFO[0000] Kubernetes environment detected (namespace: infrahub-prod) INFO[0000] Creating collection ID: 20260702_141530 INFO[0001] Collecting logs: infrahub-server (2 replicas) INFO[0004] Collecting logs: task-worker (3 replicas, 1 restarted - including previous logs) INFO[0009] Collecting database logs... INFO[0012] Collecting message-queue status... INFO[0013] Collecting cache status... INFO[0014] Collecting task-manager state... INFO[0016] Collecting server info... INFO[0017] Collecting container metrics... INFO[0018] Creating compressed archive... INFO[0021] Bundle created: infrahub_bundles/support_bundle_20260702_141530.tar.gz ``` If a service is down or unreachable, collection continues with the remaining services and records the failure: ``` WARN[0013] Collector failed: cache status (container not running) - continuing ``` A partial bundle is still a useful bundle — a degraded instance is precisely when you need one. ## Step 4: Verify the bundle[​](#step-4-verify-the-bundle "Direct link to Step 4: Verify the bundle") Check the archive and review what was collected: ``` # Verify archive integrity tar -tzf infrahub_bundles/support_bundle_20260702_141530.tar.gz > /dev/null && echo "Archive is valid" # View the collection manifest tar -xzOf infrahub_bundles/support_bundle_20260702_141530.tar.gz bundle/bundle_information.json | jq '.' ``` The manifest records what was attempted and what succeeded: ``` { "manifest_version": 2026070200, "collect_id": "20260702_141530", "created_at": "2026-07-02T14:15:30Z", "tool_version": "1.2.0", "infrahub_version": "1.5.2", "environment": "kubernetes", "helm": { "release_name": "infrahub", "chart": "infrahub", "chart_version": "1.2.3" }, "log_lines": 100000, "collectors": [ {"name": "logs/infrahub-server", "status": "success"}, {"name": "logs/task-worker", "status": "success"}, {"name": "cache-status", "status": "failed", "reason": "container not running"}, {"name": "benchmark", "status": "skipped", "reason": "not requested"} ] } ``` The bundle layout is identical on Docker Compose and Kubernetes. Inside the archive, all files live under a top-level `bundle/` directory: * `bundle_information.json` - Collection manifest * `logs//` - Container logs, one file per replica (plus `*.previous.log` for restarted pods) * `database/` - Neo4j server logs * `message-queue/` - RabbitMQ queues, exchanges, connections, and status * `cache/` - Redis info, clients, configuration, and slow log * `task-worker/` - Task worker state, one directory per replica * `task-manager/` - Work pools, work queues, recent flow runs, events, and automations * `server/` - Version, installed packages, API information, configuration, schema, environment variables * `metrics/` - Container resource metrics Review before sharing Masking is based on key names: environment variables and configuration keys containing `password`, `secret`, `token`, or `key` are replaced with `********` before they are written to the bundle. Secrets stored under other key names are not detected. Service logs are collected as-is, and database query logs (`--include-queries`) can contain customer data. Review the bundle contents before sharing. ## Step 5: Share the bundle with support[​](#step-5-share-the-bundle-with-support "Direct link to Step 5: Share the bundle with support") Send the archive to OpsMill support through your usual support channel. The bundle is a plain local file, so environments with restricted egress can transfer it through whatever mechanism their security policy allows. ## Advanced usage[​](#advanced-usage "Direct link to Advanced usage") ### Include a backup for issue reproduction[​](#include-a-backup-for-issue-reproduction "Direct link to Include a backup for issue reproduction") Support often asks for a backup alongside logs so they can reproduce your issue locally. Create both in one run: ``` infrahub-collect create --include-backup ``` This runs the standard `infrahub-backup` logic and records the backup in the bundle manifest. warning Unlike log collection, the backup step follows the normal backup behavior for your deployment, which can include stopping application containers while the snapshot is taken. Plan for a short interruption, or skip `--include-backup` and create the backup separately during a maintenance window. ### Include database query logs[​](#include-database-query-logs "Direct link to Include database query logs") Query logs can help diagnose slow or failing database operations, but they may contain customer data, so they are excluded by default: ``` infrahub-collect create --include-queries ``` ### Adjust the log volume limit[​](#adjust-the-log-volume-limit "Direct link to Adjust the log volume limit") By default, collection captures the most recent 100,000 log lines per container. Raise or lower the limit as needed: ``` # Collect more history for a long-running issue infrahub-collect create --log-lines=500000 # Or via environment variable export INFRAHUB_LOG_LINES=500000 infrahub-collect create ``` The effective limit is recorded in the manifest, so support knows whether logs were truncated. ### Run a performance benchmark[​](#run-a-performance-benchmark "Direct link to Run a performance benchmark") For performance investigations, support may ask you to include a benchmark run: ``` infrahub-collect create --benchmark ``` The benchmark requires downloading the OpsMill benchmark container image and measures whether the host meets Infrahub's resource requirements — disk IOPS, memory, and single-core CPU — rather than generating load against your instance. If the image cannot be pulled — for example in an air-gapped environment — the benchmark is skipped with a warning and the rest of the collection completes normally. ## Troubleshoot collection[​](#troubleshoot-collection "Direct link to Troubleshoot collection") ### No deployment detected[​](#no-deployment-detected "Direct link to No deployment detected") If `infrahub-collect create` reports that no environment was found, list what the tool can see: ``` infrahub-collect environment list ``` On Docker Compose, confirm the project is running with `docker compose ls`; the project name must contain `infrahub`, or you must name it with `--project`. On Kubernetes, the namespace is discovered through the `app.kubernetes.io/name=infrahub` label; for deployments with custom labels, name the namespace with `--k8s-namespace`. ### Permission denied on Kubernetes[​](#permission-denied-on-kubernetes "Direct link to Permission denied on Kubernetes") Log collection needs the `pods/log` permission and the status collectors need `pods/exec`. If your role lacks one of them, the affected collectors are recorded as failed in the manifest. Ask your cluster administrator for read access to both — collection needs no write or scale permissions. ### Docker or kubectl not found[​](#docker-or-kubectl-not-found "Direct link to Docker or kubectl not found") The tool depends on the Docker CLI or kubectl being installed and configured on the machine where it runs. When neither is usable, the command fails immediately and the error names the missing CLI. ### Individual collectors failed[​](#individual-collectors-failed "Direct link to Individual collectors failed") Collector failures on a degraded deployment are expected — a stopped cache container cannot answer status queries. The command still exits successfully, and the manifest records each failure with a reason. Send the partial bundle as-is; support reads the manifest first. ## Validation[​](#validation "Direct link to Validation") Confirm your collection works: * [ ] `infrahub-collect environment detect` finds your deployment * Bundle archive is created and passes the integrity check * Manifest lists an explicit outcome for every collector * Logs are present for every service replica * Masked environment output contains no plaintext secrets ## Related resources[​](#related-resources "Direct link to Related resources") * [Install Infrahub Collect](/backup/guides/install-collect.md) * [Back up your Infrahub instance](/backup/guides/backup-instance.md) * [CLI command reference](/backup/reference/commands.md) * [Configuration reference](/backup/reference/configuration.md) --- # Install Infrahub Backup This guide shows you how to install Infrahub Backup on your system. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before installing Infrahub Backup, ensure you have: **For Docker deployments:** * Administrative or sudo access on your system * Docker with Docker Compose installed **For Kubernetes deployments (in-band via Helm):** * Permissions to install or upgrade the `infrahub-backup` Helm chart in your cluster **For Kubernetes deployments (out-of-band CLI):** * Administrative or sudo access on your system * kubectl configured with permissions to access the Infrahub namespace **If building from source:** * Git and network access to clone the repository * Go 1.21 or later installed ## Installation methods[​](#installation-methods "Direct link to Installation methods") * Docker * Kubernetes Choose one of the following methods to install the CLI for Docker deployments: * Direct Download * Manual Selection * Build from Source ### Download the pre-built binary[​](#download-the-pre-built-binary "Direct link to Download the pre-built binary") Download the pre-built binary directly: ``` # Download the appropriate binary for your system curl https://infrahub.opsmill.io/ops/$(uname -s)/$(uname -m)/infrahub-backup -o infrahub-backup # Make it executable chmod +x infrahub-backup # Move to a directory in your PATH (optional) sudo mv infrahub-backup /usr/local/bin/ ``` ### Select and download a specific version[​](#select-and-download-a-specific-version "Direct link to Select and download a specific version") If you need a specific version or architecture, manually select the appropriate binary: 1. Identify your system architecture: ``` uname -s # Operating system (Linux/Darwin) uname -m # Architecture (x86_64/aarch64) ``` 2. Download the matching binary: * **Linux AMD64**: `curl -L https://infrahub.opsmill.io/ops/Linux/x86_64/infrahub-backup -o infrahub-backup` * **Linux ARM64**: `curl -L https://infrahub.opsmill.io/ops/Linux/aarch64/infrahub-backup -o infrahub-backup` * **macOS AMD64**: `curl -L https://infrahub.opsmill.io/ops/Darwin/x86_64/infrahub-backup -o infrahub-backup` * **macOS ARM64**: `curl -L https://infrahub.opsmill.io/ops/Darwin/arm64/infrahub-backup -o infrahub-backup` 3. Make executable and optionally install system-wide: ``` chmod +x infrahub-backup sudo mv infrahub-backup /usr/local/bin/ # Optional ``` ### Build from source code[​](#build-from-source-code "Direct link to Build from source code") To install Infrahub Backup, build from source: 1. Install Go 1.21 or later: ``` # Check if Go is installed go version ``` 2. Clone the repository: ``` git clone https://github.com/opsmill/infrahub-ops-cli.git cd infrahub-ops-cli ``` 3. Build the binaries: ``` make build ``` 4. Install to your PATH: ``` sudo cp bin/infrahub-backup /usr/local/bin/ ``` * Helm (In-band) * CLI (Out-of-band) ### Enable via Infrahub Helm chart (Recommended)[​](#enable-via-infrahub-helm-chart-recommended "Direct link to Enable via Infrahub Helm chart (Recommended)") If you deploy Infrahub on Kubernetes using the Infrahub Helm chart, enable the `infrahub-backup` subchart. This in-band approach deploys backup functionality directly within your cluster—no separate CLI installation required. ``` # values.yaml for Infrahub Helm chart infrahub-backup: enabled: true backup: enabled: true mode: "cronjob" schedule: "0 2 * * *" storage: type: "s3" s3: bucket: "my-infrahub-backups" endpoint: "https://s3.amazonaws.com" region: "us-east-1" secretName: "backup-s3-credentials" ``` Apply the configuration: ``` helm upgrade infrahub oci://registry.opsmill.io/opsmill/chart/infrahub \ --namespace infrahub \ --values values.yaml ``` This approach integrates backups with your existing Infrahub deployment and is ideal for GitOps workflows with ArgoCD or Flux. For detailed configuration options, see: * [Back up Infrahub on Kubernetes](/backup/guides/kubernetes-backup.md) ### Install the CLI for out-of-band operations[​](#install-the-cli-for-out-of-band-operations "Direct link to Install the CLI for out-of-band operations") If you need to run backup operations from outside the cluster (for example, from a CI/CD pipeline or local workstation), install the CLI binary: ``` # Download the appropriate binary for your system curl https://infrahub.opsmill.io/ops/$(uname -s)/$(uname -m)/infrahub-backup -o infrahub-backup # Make it executable chmod +x infrahub-backup # Move to a directory in your PATH (optional) sudo mv infrahub-backup /usr/local/bin/ ``` Ensure kubectl is configured with access to the Infrahub namespace before running commands. ## Verify installation[​](#verify-installation "Direct link to Verify installation") note If you installed via Helm (in-band), skip this section—verification is handled by Kubernetes. The steps below apply to CLI installations only. After installing the CLI, verify it is working correctly: ``` # Check versions infrahub-backup version # Verify environment detection infrahub-backup environment detect # Display help infrahub-backup --help ``` Expected output: ``` Version: [git ref] ``` ## Configure environment[​](#configure-environment "Direct link to Configure environment") note If you installed via Helm (in-band), configuration is managed through Helm values. The environment variables below apply to CLI installations only. Infrahub Backup CLI uses environment variables for configuration. Set these based on your deployment: ### Docker Compose deployments[​](#docker-compose-deployments "Direct link to Docker Compose deployments") ``` # Optional: Target a specific project export INFRAHUB_COMPOSE_PROJECT=infrahub-production # Optional: Custom backup location export BACKUP_DIR=/data/backups/infrahub ``` ### Kubernetes deployments[​](#kubernetes-deployments "Direct link to Kubernetes deployments") ``` # Set the namespace export INFRAHUB_K8S_NAMESPACE=infrahub # Set kubeconfig if not default export KUBECONFIG=/path/to/kubeconfig ``` ### Database credentials[​](#database-credentials "Direct link to Database credentials") If your deployment uses non-default credentials and the tools cannot fetch them automatically: ``` # Neo4j settings export INFRAHUB_DB_USERNAME=neo4j export INFRAHUB_DB_PASSWORD=your-password export INFRAHUB_DB_DATABASE=neo4j # PostgreSQL settings (for task manager) export PREFECT_API_DATABASE_CONNECTION_URL=postgresql://user:pass@localhost/prefect ``` ## Validation[​](#validation "Direct link to Validation") note If you installed via Helm (in-band), validate your deployment by checking the CronJob status with `kubectl get cronjobs -n infrahub`. The steps below apply to CLI installations only. Confirm the CLI can connect to your Infrahub instance: ``` # List available projects (Docker) infrahub-backup environment list # Test environment detection infrahub-backup environment detect ``` If these commands succeed, installation is complete. ## Advanced usage (Docker deployments)[​](#advanced-usage-docker-deployments "Direct link to Advanced usage (Docker deployments)") ### System service installation[​](#system-service-installation "Direct link to System service installation") For Docker deployments, you can run scheduled backups using a systemd service: 1. Create service file: ``` sudo tee /etc/systemd/system/infrahub-backup.service > /dev/null < /dev/null < /dev/null && echo "Archive is valid" # View metadata tar -xzOf infrahub_backup_20250120_020000.tar.gz backup_information.json | jq '.' ``` ## Understanding permissions[​](#understanding-permissions "Direct link to Understanding permissions") The Helm chart creates a ServiceAccount with the required RBAC permissions to perform backups. ### Default behavior[​](#default-behavior "Direct link to Default behavior") By default, the chart creates: * A ServiceAccount named `infrahub-backup` * A Role with required permissions * A RoleBinding linking the ServiceAccount to the Role ### Required permissions[​](#required-permissions "Direct link to Required permissions") The backup process needs these Kubernetes permissions: | Resource | Verbs | Purpose | | -------------- | ---------- | ------------------------------- | | `pods` | list, get | Discover Infrahub pods | | `pods/exec` | create | Execute backup commands in pods | | `deployments` | get, patch | Scale down/up during backup | | `statefulsets` | get, patch | Scale down/up during backup | | `pods/log` | get | Monitor backup progress | ### Using an existing ServiceAccount[​](#using-an-existing-serviceaccount "Direct link to Using an existing ServiceAccount") If your security policy requires using a pre-existing ServiceAccount: ``` infrahub-backup: serviceAccount: create: false name: "my-existing-serviceaccount" rbac: create: false # Assumes permissions already exist ``` ### Cloud provider integration[​](#cloud-provider-integration "Direct link to Cloud provider integration") Add annotations for cloud provider IAM integration: ``` infrahub-backup: serviceAccount: annotations: # AWS IRSA eks.amazonaws.com/role-arn: "arn:aws:iam::123456789012:role/infrahub-backup" # GCP Workload Identity iam.gke.io/gcp-service-account: "infrahub-backup@project.iam.gserviceaccount.com" ``` ## Validation[​](#validation "Direct link to Validation") Confirm your backup configuration works: * Backup Job/CronJob is created in the correct namespace * ServiceAccount has required RBAC permissions * S3 credentials secret exists and is correctly referenced * Backup completes without errors in logs * Backup file appears in S3 bucket (or can be retrieved from pod for local storage) * Backup archive passes integrity check * Backup metadata contains expected components ## Related resources[​](#related-resources "Direct link to Related resources") * [Restore Infrahub on Kubernetes](/backup/guides/kubernetes-restore.md) * [Back up your Infrahub instance (CLI)](/backup/guides/backup-instance.md) --- # Restore Infrahub on Kubernetes This guide walks you through restoring your Infrahub instance on Kubernetes from a backup stored in S3-compatible storage. The restore process uses the same `infrahub-backup` Helm chart and runs as a Kubernetes Job. warning Restoring a backup will overwrite your current Infrahub data. Create a safety backup before proceeding if you need to preserve any recent changes. ## High availability (CloudNativePG)[​](#high-availability-cloudnativepg "Direct link to High availability (CloudNativePG)") If your deployment uses a CloudNativePG HA PostgreSQL cluster, the restore process automatically detects and targets the primary pod. No special configuration is needed. See the [Kubernetes backup guide](/backup/guides/kubernetes-backup.md#high-availability-cloudnativepg) for details on supported HA operators. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before restoring: * A backup file stored in S3-compatible storage * The backup was created from the same Infrahub edition (Community or Enterprise) * Access to modify Helm values for your Infrahub deployment * S3 credentials with read access to the backup bucket ## Step 1: Identify the backup to restore[​](#step-1-identify-the-backup-to-restore "Direct link to Step 1: Identify the backup to restore") List available backups in your S3 bucket: ``` # AWS S3 aws s3 ls s3://my-infrahub-backups/ # MinIO mc ls myminio/my-infrahub-backups/ ``` Note the exact filename of the backup you want to restore, for example: `infrahub_backup_20250120_020000.tar.gz` ### Verify backup metadata[​](#verify-backup-metadata "Direct link to Verify backup metadata") Before restoring, verify the backup is compatible: ``` # Download and inspect metadata aws s3 cp s3://my-infrahub-backups/infrahub_backup_20250120_020000.tar.gz ./ tar -xzOf infrahub_backup_20250120_020000.tar.gz backup_information.json | jq '.' ``` Check that: * `neo4j_edition` matches your current deployment * `infrahub_version` is compatible with your target version * `components` includes the data you need to restore ## Step 2: Configure S3 source[​](#step-2-configure-s3-source "Direct link to Step 2: Configure S3 source") Create or verify the S3 credentials secret exists: ``` kubectl create secret generic backup-s3-credentials \ --namespace infrahub \ --from-literal=AWS_ACCESS_KEY_ID=your-access-key \ --from-literal=AWS_SECRET_ACCESS_KEY=your-secret-key ``` ## Step 3: Configure the restore Job[​](#step-3-configure-the-restore-job "Direct link to Step 3: Configure the restore Job") * Via Infrahub Helm Chart (Recommended) * Standalone Chart Add the restore configuration to your Infrahub Helm values: ``` # values.yaml for Infrahub Helm chart infrahub-backup: enabled: true # Disable backup during restore backup: enabled: false # Enable restore restore: enabled: true s3: bucket: "my-infrahub-backups" key: "infrahub_backup_20250120_020000.tar.gz" endpoint: "https://s3.amazonaws.com" region: "us-east-1" secretName: "backup-s3-credentials" ``` Create a values file for the restore operation: ``` # restore-values.yaml backup: enabled: false restore: enabled: true s3: bucket: "my-infrahub-backups" key: "infrahub_backup_20250120_020000.tar.gz" endpoint: "https://s3.amazonaws.com" region: "us-east-1" secretName: "backup-s3-credentials" ``` ## Step 4: Deploy the restore Job[​](#step-4-deploy-the-restore-job "Direct link to Step 4: Deploy the restore Job") warning The restore process will stop Infrahub services temporarily. Plan for downtime during the restore operation. * Via Infrahub Helm Chart * Standalone Chart Update your Infrahub Helm release: ``` helm upgrade infrahub oci://registry.opsmill.io/opsmill/chart/infrahub \ --namespace infrahub \ --values values.yaml ``` Install or upgrade the chart with restore enabled: ``` helm upgrade --install infrahub-backup oci://registry.opsmill.io/opsmill/chart/infrahub-backup \ --namespace infrahub \ --values restore-values.yaml ``` ## Step 5: Monitor restore progress[​](#step-5-monitor-restore-progress "Direct link to Step 5: Monitor restore progress") ### Watch the restore Job[​](#watch-the-restore-job "Direct link to Watch the restore Job") ``` # Check Job status kubectl get job -n infrahub -l app.kubernetes.io/name=infrahub-backup # Watch pod status kubectl get pods -n infrahub -l app.kubernetes.io/name=infrahub-backup -w ``` ### View restore logs[​](#view-restore-logs "Direct link to View restore logs") ``` # Stream logs from the restore pod kubectl logs -n infrahub -l app.kubernetes.io/name=infrahub-backup -f ``` Expected output for a successful restore: ``` INFO[0000] Starting restore process... INFO[0001] Downloading backup from S3: s3://my-infrahub-backups/infrahub_backup_20250120_020000.tar.gz INFO[0010] Download completed (1.5GB) INFO[0010] Extracting backup archive... INFO[0012] Validating backup metadata... INFO[0012] Backup ID: 20250120_020000 INFO[0012] Infrahub version: 0.15.0 INFO[0012] Components: database, task-manager-db INFO[0013] Validating checksums... INFO[0015] All checksums valid INFO[0015] Stopping Infrahub services... INFO[0020] Wiping transient data (cache, message-queue)... INFO[0022] Restoring PostgreSQL database... INFO[0030] PostgreSQL restore completed INFO[0030] Restarting support services... INFO[0035] Restoring Neo4j database... INFO[0060] Neo4j restore completed INFO[0060] Starting Infrahub services... INFO[0070] All services started INFO[0070] Restore completed successfully ``` ## Step 6: Verify restored instance[​](#step-6-verify-restored-instance "Direct link to Step 6: Verify restored instance") ### Check service health[​](#check-service-health "Direct link to Check service health") ``` # Verify all pods are running kubectl get pods -n infrahub # Check Infrahub server logs kubectl logs -n infrahub -l app.kubernetes.io/component=infrahub-server --tail=50 ``` ### Validate data integrity[​](#validate-data-integrity "Direct link to Validate data integrity") 1. **Access the Infrahub UI** - Log in and verify your data is present 2. **Check the GraphQL API** - Query a known object to confirm data restoration 3. **Review task manager** - Verify historical task runs are visible ### Test a sample query[​](#test-a-sample-query "Direct link to Test a sample query") ``` # Port-forward to the Infrahub server kubectl port-forward -n infrahub svc/infrahub-server 8000:8000 # Query the API curl -X POST http://localhost:8000/graphql \ -H "Content-Type: application/json" \ -d '{"query": "{ InfrahubStatus { summary { schema_hash } } }"}' ``` ## Step 7: Disable restore and re-enable backups[​](#step-7-disable-restore-and-re-enable-backups "Direct link to Step 7: Disable restore and re-enable backups") After a successful restore, update your values to disable the restore Job and re-enable scheduled backups: ``` infrahub-backup: enabled: true backup: enabled: true mode: "cronjob" schedule: "0 2 * * *" storage: type: "s3" s3: bucket: "my-infrahub-backups" endpoint: "https://s3.amazonaws.com" region: "us-east-1" secretName: "backup-s3-credentials" restore: enabled: false # Disable restore ``` Apply the updated configuration: ``` helm upgrade infrahub oci://registry.opsmill.io/opsmill/chart/infrahub \ --namespace infrahub \ --values values.yaml ``` ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Restore Job fails to start[​](#restore-job-fails-to-start "Direct link to Restore Job fails to start") Check if the S3 credentials secret exists: ``` kubectl get secret backup-s3-credentials -n infrahub ``` Verify the secret contains the expected keys: ``` kubectl get secret backup-s3-credentials -n infrahub -o jsonpath='{.data}' | jq 'keys' ``` ### Download fails[​](#download-fails "Direct link to Download fails") Check network connectivity and S3 endpoint configuration: ``` # View detailed error in logs kubectl logs -n infrahub -l app.kubernetes.io/name=infrahub-backup # Common issues: # - Incorrect bucket name # - Wrong S3 endpoint URL # - Invalid credentials # - Network policy blocking egress ``` ### Checksum validation fails[​](#checksum-validation-fails "Direct link to Checksum validation fails") The backup file may be corrupted. Try downloading a fresh copy: ``` # Verify the backup locally aws s3 cp s3://my-infrahub-backups/infrahub_backup_20250120_020000.tar.gz ./ tar -tzf infrahub_backup_20250120_020000.tar.gz > /dev/null ``` ### Services fail to restart[​](#services-fail-to-restart "Direct link to Services fail to restart") Check for resource constraints or scheduling issues: ``` # Check pod events kubectl describe pods -n infrahub -l app.kubernetes.io/name=infrahub # Check node resources kubectl top nodes ``` ## Validation[​](#validation "Direct link to Validation") Confirm your restore completed successfully: * Restore Job completed without errors * All Infrahub pods are running * Infrahub UI is accessible * Data is present and correct * Task manager shows historical runs * Scheduled backups are re-enabled * Restore Job is disabled to prevent accidental re-runs ## Related resources[​](#related-resources "Direct link to Related resources") * [Back up Infrahub on Kubernetes](/backup/guides/kubernetes-backup.md) * [Restore from a backup (CLI)](/backup/guides/restore-backup.md) --- # Restore from a backup This guide shows you how to restore your Infrahub instance from a backup file. If you need to recover from data loss, roll back changes, or migrate to new infrastructure, follow these steps to safely restore your system. ## Community edition compatibility[​](#community-edition-compatibility "Direct link to Community edition compatibility") Restores are supported for Infrahub Community Edition when the backup was created from the same edition. Enterprise backups include components that are not available in Community Edition and cannot be restored. danger Restoring an Enterprise backup to a Community Edition deployment is not supported. Always use backups that were captured from the same edition you plan to restore. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before restoring from backup: * Have a valid backup file created by infrahub-backup * Ensure sufficient disk space for extraction (3x backup size) * Stop all write operations to the current instance * Verify you have necessary permissions * **Create a current backup** before overwriting existing data danger Restoration replaces all existing data. Always verify you have a current backup before proceeding with a restore operation. ## Docker high availability deployments[​](#docker-high-availability-deployments "Direct link to Docker high availability deployments") If your Docker Compose deployment runs multiple task manager replicas (HA setup), you must stop the `task-manager` and `task-manager-background-svc` containers before running a restore. This prevents those services from accessing the database while data is being replaced. ``` # Stop task manager services before restore docker compose stop task-manager task-manager-background-svc ``` After the restore completes, restart the stopped services: ``` # Restart task manager services after restore docker compose start task-manager task-manager-background-svc ``` ## Step 1: Prepare for restoration[​](#step-1-prepare-for-restoration "Direct link to Step 1: Prepare for restoration") ### Create safety backup[​](#create-safety-backup "Direct link to Create safety backup") Before overwriting current data, create a safety backup: ``` # Backup current state infrahub-backup create --backup-dir=/tmp/safety-backup # Note the filename for potential recovery SAFETY_BACKUP=$(ls -t /tmp/safety-backup/*.tar.gz | head -1) echo "Safety backup: $SAFETY_BACKUP" ``` ## Step 2: Execute the restore[​](#step-2-execute-the-restore "Direct link to Step 2: Execute the restore") ### Basic restore[​](#basic-restore "Direct link to Basic restore") Restore from a backup file: ``` infrahub-backup restore infrahub_backups/infrahub_backup_20250929_143022.tar.gz ``` The restore process will: 1. Validate backup compatibility 2. Stop all Infrahub containers 3. Clear existing data 4. Restore Neo4j database 5. Restore PostgreSQL task manager 6. Restart all services ### Restore from S3[​](#restore-from-s3 "Direct link to Restore from S3") Restore directly from S3 or S3-compatible storage using an `s3://` URI: * AWS S3 * MinIO * Environment Variables ``` # Restore from S3 infrahub-backup restore s3://my-backups/infrahub/prod/infrahub_backup_20250929_143022.tar.gz ``` ``` # Restore from MinIO or S3-compatible storage infrahub-backup restore \ --s3-endpoint http://minio.local:9000 \ s3://my-backups/infrahub_backup_20250929_143022.tar.gz ``` ``` # Configure S3 via environment variables export AWS_ACCESS_KEY_ID=your-access-key export AWS_SECRET_ACCESS_KEY=your-secret-key infrahub-backup restore s3://my-backups/infrahub/prod/infrahub_backup_20250929_143022.tar.gz ``` info The backup is downloaded to the local backup directory before restoration. The downloaded file is automatically cleaned up after the restore completes. ### Restore to specific project[​](#restore-to-specific-project "Direct link to Restore to specific project") If you have multiple Infrahub projects: ``` # Restore to a specific Docker Compose project infrahub-backup restore infrahub_backups/infrahub_backup_20250929_143022.tar.gz --project=infrahub-staging ``` ### Reset the deployment ID[​](#reset-the-deployment-id "Direct link to Reset the deployment ID") Every Infrahub instance carries a unique deployment ID stored on the Root node in the Neo4j database. When you restore a production backup into a non-production environment — for example, cloning prod into staging or spinning up a disaster-recovery replica — the restored instance inherits the source deployment ID, and both environments report the same identity. If you need the restored instance to appear as a distinct deployment, add the `--reset-deployment-id` flag: ``` infrahub-backup restore infrahub_backups/infrahub_backup_20250929_143022.tar.gz --reset-deployment-id ``` The flag generates a new UUID and writes it to the Root node after the database is restored and before the Infrahub application containers start, so the application never observes the source deployment ID. info Omit this flag when you are restoring into the same environment the backup came from (for example, recovering from data loss on the original instance). Resetting the deployment ID is only required when the restored instance is logically separate from the source. ## Step 3: Monitor restoration progress[​](#step-3-monitor-restoration-progress "Direct link to Step 3: Monitor restoration progress") Watch the detailed restoration output: ``` INFO[0000] Starting restore process... INFO[0000] Reading backup metadata... INFO[0001] Backup created: 2025-09-29T14:30:22Z INFO[0001] Backup version: 1.0.0 INFO[0001] Validating backup compatibility... INFO[0002] Validation successful INFO[0002] Stopping all Infrahub containers... INFO[0008] All containers stopped INFO[0008] Extracting backup archive... INFO[0012] Archive extracted successfully INFO[0012] Restoring Neo4j database... INFO[0025] Neo4j database restored INFO[0025] Restoring PostgreSQL database... INFO[0030] PostgreSQL database restored INFO[0032] Starting Infrahub containers... INFO[0040] All containers started INFO[0040] Restore completed successfully ``` ## Step 4: verify restoration[​](#step-4-verify-restoration "Direct link to Step 4: verify restoration") ### Check service health[​](#check-service-health "Direct link to Check service health") Verify all services are running: ``` # Check container status docker compose ps # All containers should show "Up" status # Example output: # NAME IMAGE STATUS # infrahub-server opsmill/infrahub:stable Up 2 minutes # database neo4j:5.13 Up 2 minutes # task-manager-db postgres:15 Up 2 minutes ``` ### Test application access[​](#test-application-access "Direct link to Test application access") Confirm Infrahub is accessible: ``` # Test HTTP endpoint curl -I http://localhost:8000 # Check GraphQL endpoint curl -X POST http://localhost:8000/graphql \ -H "Content-Type: application/json" \ -d '{"query": "{ InfrahubInfo { version } }"}' ``` ### Validate data integrity[​](#validate-data-integrity "Direct link to Validate data integrity") Verify your data was restored correctly: 1. Log into the Infrahub web interface 2. Check that your schemas are present 3. Verify critical data objects exist 4. Test a few key queries or operations 5. Review recent task history ## Step 5: post-restoration tasks[​](#step-5-post-restoration-tasks "Direct link to Step 5: post-restoration tasks") ### Verify integrations[​](#verify-integrations "Direct link to Verify integrations") Check external integrations are working: * Git repositories sync correctly * External authentication (LDAP/SSO) functions * Webhook endpoints are accessible * API integrations reconnect properly ## Validation[​](#validation "Direct link to Validation") Confirm restoration was successful: * ✓ All services are running * ✓ Web interface is accessible * ✓ Data appears complete and correct * ✓ Authentication works * ✓ Background tasks are processing * ✓ Integrations are functional ## Related resources[​](#related-resources "Direct link to Related resources") * [Back up your Infrahub instance](/backup/guides/backup-instance.md) * [CLI command reference](/backup/reference/commands.md) --- # How to update the CLI This guide shows you how to update the `infrahub-backup` and `infrahub-collect` binaries to a newer release using the built-in `update` command, so you no longer need to download and replace the binary by hand. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * A binary you installed by direct download (not via Homebrew or a container image). * Network access to GitHub. * Permission to write the binary's location (use `sudo` if it lives in a system path such as `/usr/local/bin`). The same `update` command is available on both `infrahub-backup` and `infrahub-collect`; each updates the binary you invoke. The examples below use `infrahub-backup`. ## Check for an update[​](#check-for-an-update "Direct link to Check for an update") To see whether a newer version is available without changing anything on disk: ``` infrahub-backup update --check ``` You will see one of: ``` update available: v1.7.3 → v1.8.0 (https://github.com/opsmill/infrahub-backup/releases/tag/v1.8.0) # or already up to date (v1.8.0) ``` ## Update to the latest version[​](#update-to-the-latest-version "Direct link to Update to the latest version") ``` infrahub-backup update ``` The command shows the current and target versions and asks for confirmation before replacing the binary. On success it prints: ``` updated v1.7.3 → v1.8.0 ``` The download is verified against the release's published SHA-256 checksum and the binary is replaced atomically: if anything fails, your existing working binary is left untouched. ## Update without prompting (CI and scripts)[​](#update-without-prompting-ci-and-scripts "Direct link to Update without prompting (CI and scripts)") For unattended use, skip the confirmation prompt with `--yes`: ``` infrahub-backup update --yes ``` note In a non-interactive session (no terminal attached), the command refuses to run without `--yes` so it never blocks waiting for input. ## Install a specific version[​](#install-a-specific-version "Direct link to Install a specific version") To pin to or roll back to a specific release: ``` infrahub-backup update --version v1.7.2 --yes ``` ## Avoid GitHub rate limits[​](#avoid-github-rate-limits "Direct link to Avoid GitHub rate limits") The command uses the public GitHub API, which is rate-limited for unauthenticated requests (60 per hour per IP). On shared CI runners or over SSH you can raise the limit by exporting a token: ``` export GITHUB_TOKEN=ghp_... # or GH_TOKEN infrahub-backup update --yes ``` A token is never required for normal interactive use. ## When self-update is declined[​](#when-self-update-is-declined "Direct link to When self-update is declined") The command refuses to replace the binary in cases where doing so would be incorrect, and points you to the right path instead: | Message | What to do instead | | -------------------------- | --------------------------------------------------------- | | managed by Homebrew | `brew upgrade infrahub-backup` | | running inside a container | Pull a newer image tag | | development build | Install a released binary | | permission denied | Re-run with `sudo`, or as a user who can write the binary | note On Windows, a freshly downloaded binary may be flagged by Windows Defender. If this happens, allow the file in Defender and re-run the command. ## Validation[​](#validation "Direct link to Validation") Confirm the new version is in place: ``` infrahub-backup version ``` ## Related resources[​](#related-resources "Direct link to Related resources") * [How to install Infrahub Backup](/backup/guides/install.md) * [CLI command reference](/backup/reference/commands.md) --- # CLI command reference Complete reference for the Infrahub Ops CLI tools (`infrahub-backup`, `infrahub-collect`), their commands, flags, and options. ## infrahub-backup[​](#infrahub-backup "Direct link to infrahub-backup") ### Command structure[​](#command-structure "Direct link to Command structure") ``` infrahub-backup [global-flags] [flags] [arguments] ``` ### Global flags[​](#global-flags "Direct link to Global flags") These flags apply to all infrahub-backup commands: | Flag | Description | Default | Environment Variable | | --------------------------- | -------------------------------------- | -------------------- | ---------------------- | | `--project ` | Target specific Docker Compose project | Auto-detect | `INFRAHUB_PROJECT` | | `--backup-dir ` | Directory for backup files | `./infrahub_backups` | `INFRAHUB_BACKUP_DIR` | | `--log-format ` | Output format for logs | `text` | `INFRAHUB_LOG_FORMAT` | | `--s3-bucket ` | S3 bucket name for backup storage | - | `INFRAHUB_S3_BUCKET` | | `--s3-prefix ` | S3 key prefix (path within bucket) | - | `INFRAHUB_S3_PREFIX` | | `--s3-endpoint ` | Custom S3 endpoint URL (for MinIO) | - | `INFRAHUB_S3_ENDPOINT` | | `--s3-region ` | AWS region for S3 bucket | `us-east-1` | `INFRAHUB_S3_REGION` | | `--help, -h` | Show help for any command | - | - | ### Backup commands[​](#backup-commands "Direct link to Backup commands") #### create[​](#create "Direct link to create") Creates a comprehensive backup of the Infrahub instance. **Syntax:** ``` infrahub-backup create [flags] ``` **Flags:** | Flag | Description | Default | Environment Variable | | ------------------------ | --------------------------------------------------------------------------- | ------- | ------------------------------ | | `--force` | Force backup even if tasks are running | `false` | `INFRAHUB_FORCE` | | `--redact` | Redact all attribute values before backup (destructive, requires `--force`) | `false` | `INFRAHUB_REDACT` | | `--neo4jmetadata ` | Neo4j metadata to include | `all` | `INFRAHUB_NEO4JMETADATA` | | `--exclude-taskmanager` | Exclude the task manager (Prefect) database from the backup archive | `false` | `INFRAHUB_EXCLUDE_TASKMANAGER` | | `--s3-upload` | Upload backup to S3 after creation | `false` | `INFRAHUB_S3_UPLOAD` | | `--s3-keep-local` | Keep local backup file after S3 upload | `false` | `INFRAHUB_S3_KEEP_LOCAL` | | `--sleep` | Sleep duration after backup for manual file transfer | `0` | `INFRAHUB_SLEEP` | **Neo4j metadata options:** * `all` - Include all user and role metadata * `users` - Include only user accounts * `roles` - Include only role definitions * `none` - Exclude all metadata **Examples:** ``` # Basic backup infrahub-backup create # Force backup with running tasks infrahub-backup create --force # Backup without user metadata infrahub-backup create --neo4jmetadata=none # Backup and upload to S3 infrahub-backup create --s3-upload --s3-bucket my-backups --s3-prefix infrahub/prod # Backup and upload to S3, keeping local copy infrahub-backup create --s3-upload --s3-bucket my-backups --s3-keep-local # Create a redacted backup (replaces all attribute values with random UUIDs) infrahub-backup create --redact --force ``` #### restore[​](#restore "Direct link to restore") Restores Infrahub from a backup file or S3 URI. **Syntax:** ``` infrahub-backup restore ``` **Arguments:** * `` - Path to backup archive or S3 URI (required) * Local file: `infrahub_backup_20250929_143022.tar.gz` * S3 URI: `s3://bucket/prefix/infrahub_backup_20250929_143022.tar.gz` **Flags:** | Flag | Description | Default | | ----------------------- | ------------------------------------------------------------------------------------------------- | ------- | | `--exclude-taskmanager` | Skip restoring the task manager database even if the dump is present | `false` | | `--migrate-format` | Run Neo4j database format migration after restore | `false` | | `--reset-deployment-id` | Generate a new Root node UUID after restore to detach this instance from the source deployment ID | `false` | **Examples:** ``` # Restore from local file infrahub-backup restore infrahub_backup_20250929_143022.tar.gz # Restore from S3 infrahub-backup restore s3://my-backups/infrahub/prod/infrahub_backup_20250929_143022.tar.gz # Restore from MinIO infrahub-backup restore --s3-endpoint http://minio.local:9000 s3://my-backups/infrahub_backup_20250929_143022.tar.gz # Restore when the task manager database was excluded from the backup infrahub-backup restore infrahub_backup_20251022_120000.tar.gz --exclude-taskmanager ``` ### Environment commands[​](#environment-commands "Direct link to Environment commands") #### environment detect[​](#environment-detect "Direct link to environment detect") Detects and displays the current deployment environment. **Syntax:** ``` infrahub-backup environment detect ``` **Example output:** ``` INFO[0000] Detecting deployment environment... INFO[0000] Docker environment detected INFO[0000] Found Docker Compose project: infrahub-demo ``` #### environment list[​](#environment-list "Direct link to environment list") Lists all available Infrahub Docker Compose projects. **Syntax:** ``` infrahub-backup environment list ``` **Example output:** ``` infrahub-production Running 7/7 infrahub-staging Running 7/7 infrahub-dev Stopped 0/7 ``` ### Utility commands[​](#utility-commands "Direct link to Utility commands") #### version[​](#version "Direct link to version") Displays version information. **Syntax:** ``` infrahub-backup version ``` **Example output:** ``` Version: 1.0.0 ``` #### update[​](#update "Direct link to update") Updates the running binary to a newer release. Downloads the matching artifact from GitHub Releases, verifies its SHA-256 checksum, and replaces the binary atomically. Available on both `infrahub-backup` and `infrahub-collect`. **Syntax:** ``` infrahub-backup update [flags] ``` **Flags:** | Flag | Description | Default | | ----------------- | ---------------------------------------------------------------- | ------- | | `--check` | Report whether an update is available without installing it | `false` | | `--yes, -y` | Skip the confirmation prompt (required for non-interactive use) | `false` | | `--version ` | Install a specific release (e.g. `v1.7.2`) instead of the latest | latest | **Environment variables:** | Variable | Description | | --------------------------- | -------------------------------------------------------------------------------------------------- | | `GITHUB_TOKEN` / `GH_TOKEN` | Optional GitHub token to raise the API rate limit on CI/shared IPs. Never required for normal use. | Self-update is refused for binaries installed via Homebrew or running inside a container image, for development builds, and when the binary location is not writable. See [How to update the CLI](/backup/guides/self-update.md). ## infrahub-collect[​](#infrahub-collect "Direct link to infrahub-collect") ### Command structure[​](#command-structure-1 "Direct link to Command structure") ``` infrahub-collect [global-flags] [flags] ``` ### Global flags[​](#global-flags-1 "Direct link to Global flags") These flags apply to all infrahub-collect commands: | Flag | Description | Default | Environment Variable | | --------------------------- | -------------------------------------- | -------------------- | ------------------------ | | `--project ` | Target specific Docker Compose project | Auto-detect | `INFRAHUB_PROJECT` | | `--k8s-namespace ` | Target specific Kubernetes namespace | Auto-detect | `INFRAHUB_K8S_NAMESPACE` | | `--output-dir ` | Directory for bundle files | `./infrahub_bundles` | `INFRAHUB_OUTPUT_DIR` | | `--log-format ` | Output format for logs | `text` | `INFRAHUB_LOG_FORMAT` | | `--help, -h` | Show help for any command | - | - | ### Collect commands[​](#collect-commands "Direct link to Collect commands") #### create[​](#create-1 "Direct link to create") Collects a troubleshooting bundle from the Infrahub instance. Collection is read-only: no container or pod is stopped, restarted, or scaled. Individual collector failures are recorded in the bundle manifest and do not abort the run. **Syntax:** ``` infrahub-collect create [flags] ``` **Flags:** | Flag | Description | Default | Environment Variable | | ------------------- | ------------------------------------------------------------------------------------------------------------------ | -------- | -------------------------- | | `--log-lines ` | Maximum log lines collected per container | `100000` | `INFRAHUB_LOG_LINES` | | `--include-backup` | Also create a backup using the standard backup behavior | `false` | `INFRAHUB_INCLUDE_BACKUP` | | `--include-queries` | Include database query logs (may contain customer data) | `false` | `INFRAHUB_INCLUDE_QUERIES` | | `--benchmark` | Run the OpsMill benchmark and include its results (requires image download; skipped with a warning if unavailable) | `false` | `INFRAHUB_BENCHMARK` | **Examples:** ``` # Basic collection infrahub-collect create # Target a specific Kubernetes namespace infrahub-collect create --k8s-namespace=infrahub-prod # Collect with more log history infrahub-collect create --log-lines=500000 # Collect logs and a backup in one run infrahub-collect create --include-backup # Performance investigation with benchmark infrahub-collect create --benchmark ``` **Output:** A single archive named `support_bundle_.tar.gz` in the output directory. Inside the archive, all files live under a top-level `bundle/` directory, with a `bundle_information.json` manifest recording an explicit outcome for every collector. **Collectors:** | Collector | What it gathers | | -------------------- | -------------------------------------------------------------------------------------------------------- | | Service logs | Container logs for every Infrahub service, all replicas, plus previous-container logs for restarted pods | | Database logs | Neo4j server logs (`neo4j.log`, `debug.log`); full log directory with `--include-queries` | | Message-queue status | RabbitMQ queues, exchanges, bindings, connections, channels, and status | | Cache status | Redis info, client list, configuration, slow log, and database size | | Task-worker state | Prefect worker status, one directory per replica | | Task-manager state | Work pools, work queues, recent flow runs, events, and automations | | Server info | Versions, installed packages, API information/configuration/schema, masked environment variables | | Container metrics | Resource usage via `docker compose stats` or `kubectl top` | | Backup (opt-in) | Backup produced by the standard backup behavior (`--include-backup`) | | Benchmark (opt-in) | OpsMill benchmark results (`--benchmark`) | ### Environment and utility commands[​](#environment-and-utility-commands "Direct link to Environment and utility commands") `infrahub-collect` provides the same `environment detect`, `environment list`, `version`, and `update` commands as `infrahub-backup`, with identical behavior. See [`update`](#update) for the self-update flags and refusal cases. ## Configuration precedence[​](#configuration-precedence "Direct link to Configuration precedence") Configuration values are resolved in this order: 1. Command-line flags (highest priority) 2. Environment variables 3. Default values (lowest priority) ## Related documentation[​](#related-documentation "Direct link to Related documentation") * [Getting started tutorial](/backup/tutorials/getting-started.md) * [Configuration reference](/backup/reference/configuration.md) * [Back up your Infrahub instance](/backup/guides/backup-instance.md) --- # Configuration reference Complete reference for configuring the Infrahub Ops CLI tools through environment variables and runtime flags. ## Configuration methods[​](#configuration-methods "Direct link to Configuration methods") Tools can be configured through these methods, applied in precedence order: 1. **Command-line flags** (highest priority) 2. **Environment variables** 3. **Default values** (lowest priority) ## Environment variables[​](#environment-variables "Direct link to Environment variables") ### Core configuration[​](#core-configuration "Direct link to Core configuration") | Variable | Description | Default | Example | | --------------------- | --------------------------------------------- | -------------------- | --------------- | | `INFRAHUB_BACKUP_DIR` | Directory for storing backup files | `./infrahub_backups` | `/data/backups` | | `INFRAHUB_OUTPUT_DIR` | Directory for storing troubleshooting bundles | `./infrahub_bundles` | `/data/bundles` | | `INFRAHUB_LOG_FORMAT` | Output format for logs | `text` | `json` | ### Docker compose configuration[​](#docker-compose-configuration "Direct link to Docker compose configuration") | Variable | Description | Default | Example | | ------------------ | --------------------------- | ----------- | --------------- | | `INFRAHUB_PROJECT` | Docker Compose project name | Auto-detect | `infrahub-prod` | ### Database configuration[​](#database-configuration "Direct link to Database configuration") #### Neo4j[​](#neo4j "Direct link to Neo4j") | Variable | Description | Default | Example | | ---------------------- | ------------------- | ------- | --------------- | | `INFRAHUB_DB_DATABASE` | Neo4j database name | `neo4j` | `infrahub` | | `INFRAHUB_DB_USERNAME` | Neo4j username | `neo4j` | `admin` | | `INFRAHUB_DB_PASSWORD` | Neo4j password | `admin` | `SecurePass123` | #### Task manager PostgreSQL[​](#task-manager-postgresql "Direct link to Task manager PostgreSQL") | Variable | Description | Default | Example | | ------------------------------------- | ---------------------------- | ----------- | ------------------------------------------ | | `PREFECT_API_DATABASE_CONNECTION_URL` | PostgreSQL connection string | Auto-detect | `postgresql://user:pass@localhost/prefect` | ### Collect configuration[​](#collect-configuration "Direct link to Collect configuration") | Variable | Description | Default | Example | | -------------------------- | ---------------------------------------------------- | ----------------------------------- | ------------------------------------ | | `INFRAHUB_BENCHMARK_IMAGE` | Container image used by the opt-in `--benchmark` run | `registry.opsmill.io/opsmill/bench` | `registry.example.com/opsmill/bench` | ## Command-line flag reference[​](#command-line-flag-reference "Direct link to Command-line flag reference") ### Global flags[​](#global-flags "Direct link to Global flags") | Flag | Environment Override | Description | | -------------- | --------------------- | -------------------------------------- | | `--backup-dir` | `INFRAHUB_BACKUP_DIR` | Set backup directory | | `--project` | `INFRAHUB_PROJECT` | Target specific Docker Compose project | | `--log-format` | `INFRAHUB_LOG_FORMAT` | Set log output format | ### Backup command flags[​](#backup-command-flags "Direct link to Backup command flags") | Flag | Description | | ----------------- | --------------------------------------------------- | | `--force` | Force backup creation even if tasks are running | | `--neo4jmetadata` | Neo4j metadata to include (all, none, users, roles) | ### Collect command flags[​](#collect-command-flags "Direct link to Collect command flags") | Flag | Environment Override | Description | | ------------------- | -------------------------- | ------------------------------------------------------------ | | `--log-lines` | `INFRAHUB_LOG_LINES` | Maximum log lines collected per container (default `100000`) | | `--include-backup` | `INFRAHUB_INCLUDE_BACKUP` | Also create a backup during collection | | `--include-queries` | `INFRAHUB_INCLUDE_QUERIES` | Include database query logs in the bundle | | `--benchmark` | `INFRAHUB_BENCHMARK` | Run the OpsMill benchmark and include its results | ## Auto-detection behavior[​](#auto-detection-behavior "Direct link to Auto-detection behavior") ### Docker Compose project detection[​](#docker-compose-project-detection "Direct link to Docker Compose project detection") Order of detection: 1. `--project` flag 2. `INFRAHUB_PROJECT` environment variable 3. Search for running Infrahub containers Detection command: ``` docker compose ls --filter "name=*infrahub*" ``` ### Database credential detection[​](#database-credential-detection "Direct link to Database credential detection") For Docker Compose deployments: ``` # Neo4j credentials from environment docker compose exec database printenv NEO4J_AUTH # PostgreSQL credentials from environment docker compose exec task-manager-db printenv POSTGRES_PASSWORD ``` ## Troubleshooting configuration[​](#troubleshooting-configuration "Direct link to Troubleshooting configuration") ### Debug configuration loading[​](#debug-configuration-loading "Direct link to Debug configuration loading") View effective configuration: ``` infrahub-backup --help ``` ### Test configuration[​](#test-configuration "Direct link to Test configuration") Validate configuration without running operations: ``` infrahub-backup environment detect ``` ### Common issues[​](#common-issues "Direct link to Common issues") #### Cannot detect environment[​](#cannot-detect-environment "Direct link to Cannot detect environment") ``` # Check Docker docker compose ls docker ps # Check for Infrahub containers docker ps --filter "name=infrahub" ``` #### Authentication failures[​](#authentication-failures "Direct link to Authentication failures") ``` # Verify credentials are set env | grep INFRAHUB env | grep PREFECT # Test database connectivity docker compose exec database cypher-shell -u neo4j ``` ## Examples[​](#examples "Direct link to Examples") ### Minimal configuration[​](#minimal-configuration "Direct link to Minimal configuration") For basic Docker Compose setup: ``` export INFRAHUB_BACKUP_DIR=/backups infrahub-backup create ``` ### Production configuration[​](#production-configuration "Direct link to Production configuration") ``` #!/bin/bash # production-backup.sh export INFRAHUB_LOG_FORMAT=json export INFRAHUB_PROJECT=infrahub-production export INFRAHUB_BACKUP_DIR=/data/backups/infrahub export INFRAHUB_DB_PASSWORD="${NEO4J_PASSWORD}" infrahub-backup create --neo4jmetadata=all ``` ## Related documentation[​](#related-documentation "Direct link to Related documentation") * [CLI command reference](/backup/reference/commands.md) * [Install Infrahub Backup](/backup/guides/install.md) * [Install Infrahub Collect](/backup/guides/install-collect.md) --- # Get started with Infrahub Backup This tutorial walks you through your first experience with Infrahub Backup, teaching you the fundamentals by creating a backup of your Infrahub instance and then restoring it. By the end of this tutorial, you'll understand the basic workflow and be confident in using `infrahub-backup` for essential maintenance tasks. ## What you'll learn[​](#what-youll-learn "Direct link to What you'll learn") In this tutorial, you'll: * Install infrahub-backup on your system * Detect your Infrahub deployment environment * Create your first backup * Verify the backup was successful * Capture a snapshot of artifact storage alongside database backups * Restore from the backup you created ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before starting this tutorial, ensure you have: * A running Infrahub instance (using Docker Compose) * Docker installed and running on your system * Terminal access with sudo privileges * At least 1GB of free disk space for backups ## Step 1: install infrahub-backup[​](#step-1-install-infrahub-backup "Direct link to Step 1: install infrahub-backup") First, install the infrahub-backup tool. The recommended method is to download binaries: ``` curl https://infrahub.opsmill.io/ops/$(uname -s)/$(uname -m)/infrahub-backup -o infrahub-backup chmod +x infrahub-backup # Verify installation ./infrahub-backup version ``` You should see output similar to: ``` Version: 1.0.0 ``` ## Step 2: detect your environment[​](#step-2-detect-your-environment "Direct link to Step 2: detect your environment") Before performing any operations, verify that infrahub-backup can detect your Infrahub deployment: ``` ./infrahub-backup environment detect ``` Expected output: ``` INFO[0000] Detecting deployment environment... INFO[0000] Docker environment detected INFO[0000] Found Docker Compose project: infrahub ``` If you have multiple Infrahub projects, list them all: ``` ./infrahub-backup environment list ``` This shows all available Docker Compose projects running Infrahub. ## Step 3: create your first backup[​](#step-3-create-your-first-backup "Direct link to Step 3: create your first backup") Now, let's create a backup of your Infrahub instance. This operation will: 1. Check for running tasks 2. Backup the Neo4j database 3. Backup the PostgreSQL task manager database 4. Create a compressed archive Run the backup command: ``` ./infrahub-backup create ``` You'll see detailed progress output: ``` INFO[0000] Starting backup process... INFO[0000] Checking for running tasks... INFO[0001] No running tasks found INFO[0004] Backing up Neo4j database... INFO[0010] Neo4j backup completed INFO[0010] Backing up PostgreSQL database... INFO[0012] PostgreSQL backup completed INFO[0012] Creating backup archive... INFO[0015] Backup created successfully: infrahub_backups/infrahub_backup_20250929_143022.tar.gz INFO[0015] Starting application containers... INFO[0020] Backup process completed ``` note The backup file is stored in the `infrahub_backups` directory by default. Note the filename for the restore step. ## Step 4: verify your backup[​](#step-4-verify-your-backup "Direct link to Step 4: verify your backup") Check that your backup was created successfully: ``` # List backup files ls -lh infrahub_backups/ # View backup contents (without extracting) tar -tzf infrahub_backups/infrahub_backup_*.tar.gz | head -10 ``` You should see: * A `.tar.gz` file with today's timestamp * Inside the archive: `backup_information.json`, database files, and other components ## Step 5: capture artifact storage[​](#step-5-capture-artifact-storage "Direct link to Step 5: capture artifact storage") If your Infrahub workflows produce artifacts stored outside the databases, take a moment to snapshot that storage so the backup remains consistent. Identify the bucket, container, or volume where artifacts live and copy the contents into a dated folder or version. Use your provider’s native tooling (for example, `aws s3 sync`, `az storage blob download`, or `rsync` for shared volumes) and note where you stored the snapshot so you can pair it with the database backup when restoring. If using standard Docker Compose deployment: ``` # Copy artifacts directory to backup location docker compose cp -a infrahub-server:/opt/infrahub/storage /backup/artifacts/ ``` ## Step 6: restore from backup[​](#step-6-restore-from-backup "Direct link to Step 6: restore from backup") To complete the learning cycle, let's restore from the backup you created. This simulates recovering from a disaster or rolling back changes. warning Restoring will replace all current data in your Infrahub instance. In a production environment, always verify you have a current backup before restoring an older one. ``` # Replace with your actual backup filename ./infrahub-backup restore infrahub_backups/infrahub_backup_20250929_143022.tar.gz ``` The restore process will: 1. Validate the backup file 2. Stop all containers 3. Restore the Neo4j database 4. Restore the PostgreSQL database 5. Restart all services Expected output: ``` INFO[0000] Starting restore process... INFO[0000] Validating backup file... INFO[0001] Backup validation successful INFO[0001] Stopping all containers... INFO[0005] Restoring Neo4j database... INFO[0015] Neo4j restore completed INFO[0015] Restoring PostgreSQL database... INFO[0018] PostgreSQL restore completed INFO[0018] Starting all containers... INFO[0025] Restore completed successfully ``` ## Step 7: verify the restore[​](#step-7-verify-the-restore "Direct link to Step 7: verify the restore") Confirm your Infrahub instance is running correctly: ``` # Check container status docker compose ps # Test Infrahub connectivity (adjust URL as needed) curl -I http://localhost:8080 ``` All containers should be in the "Up" state, and the HTTP request should return a successful response. ## What you've learned[​](#what-youve-learned "Direct link to What you've learned") Congratulations! You've completed your first backup and restore cycle with Infrahub Backup. You now know how to: * Install and verify infrahub-backup * Detect your deployment environment * Create comprehensive backups of your Infrahub instance * Capture artifact storage snapshots to keep backups complete * Restore from a backup file * Verify successful operations ## Next steps[​](#next-steps "Direct link to Next steps") Now that you understand the basics, explore: * [Back up your Infrahub instance](/backup/guides/backup-instance.md) * [Restore from a backup](/backup/guides/restore-backup.md) ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") If you encounter issues: **Permission denied when running commands:** * Ensure your user has Docker permissions: `sudo usermod -aG docker $USER` * Log out and back in for changes to take effect **Cannot detect environment:** * Verify Docker is running: `docker ps` * Check Docker Compose is installed: `docker compose version` **Backup fails with "running tasks detected":** * Wait for tasks to complete, or use `--force` flag to proceed anyway * Check task status in Infrahub UI --- # Infrahub demo-dc example Welcome to the Infrahub demo-dc example. This repository showcases how Infrahub serves as an infrastructure data management platform for managing modern network infrastructure with design-driven automation. It demonstrates Infrahub's core capabilities including: * Schema-driven data modeling * Composable topology generation * Version control for infrastructure data * Automated configuration generation * Validation workflows Whether you're a network engineer exploring automation, a developer building on Infrahub, or an architect evaluating infrastructure management platforms, this bundle provides hands-on experience with real-world patterns and workflows. ## Community contribution[​](#community-contribution "Direct link to Community contribution") note This demo repository is partially authored by the OpsMill community member [tomek](https://www.linkedin.com/in/tomekzajac/) from this example: [GitHub t0m3kz/infrahub-demo](https://github.com/t0m3kz/infrahub-demo) ## Documentation guide[​](#documentation-guide "Direct link to Documentation guide") This documentation is organized following the [Diataxis framework](https://diataxis.fr/) to help you find exactly what you need: ### Getting started[​](#getting-started "Direct link to Getting started") | Page | Purpose | | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | **[Installation guide](/demo-dc/install.md)** | Step-by-step instructions to install and set up the demo environment on your system. Start here if this is your first time running the demo. | ### Tutorials[​](#tutorials "Direct link to Tutorials") | Page | Purpose | | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **[User walkthrough](/demo-dc/user-walkthrough.md)** | A complete hands-on tutorial that guides you through the end-user experience: creating topologies, managing branches, running generators, creating proposed changes, and validating configurations. Perfect for learning the workflow from start to finish. | | **[Deploy a virtual lab with Containerlab](/demo-dc/containerlab-deployment.md)** | Learn how to deploy generated configurations to a virtual network lab using Containerlab. Extract device configurations and topology files from Infrahub, spin up virtual Arista cEOS switches, and test your data center fabric before production deployment. | | **[Working with security management](/demo-dc/security-management.md)** | Explore Infrahub's security management capabilities by examining firewall policies, security zones, and address objects. Learn how structured security data transforms into vendor-specific firewall configurations (Juniper JunOS) and how to modify policies safely using branches. | | **[Cloud resource management](/demo-dc/cloud-management.md)** | Manage multi-cloud infrastructure (AWS, GCP, Azure) with a vendor-agnostic schema. Load demo cloud data including accounts, regions, virtual networks, instances, and security groups. Learn how Infrahub serves as a unified inventory for cloud resources. | | **[Using the service catalog](/demo-dc/service-catalog.md)** | Learn how to use the Service Catalog web interface for simplified infrastructure provisioning. Enable the Streamlit application, navigate between branches, view existing infrastructure, and create new data centers through a guided form-based workflow that automates branch creation and generator execution. | ### Guides[​](#guides "Direct link to Guides") | Page | Purpose | | ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **[Developer guide](/demo-dc/developer-guide.md)** | Technical deep-dive into how the demo works under the hood. Covers schema architecture, bootstrap scripts, generators, transforms, checks, and testing. Use this when extending functionality, troubleshooting issues, or understanding implementation details. | | **[Using Infrahub Enterprise](/demo-dc/enterprise.md)** | Instructions for configuring the demo environment to use Infrahub Enterprise edition. Learn how to switch between Community and Enterprise editions, configure environment variables, and understand the differences between editions. | ### Topics[​](#topics "Direct link to Topics") | Page | Purpose | | ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **[Understanding the concepts](/demo-dc/concepts.md)** | Explains the architectural patterns, design decisions, and core Infrahub concepts demonstrated in this project. Read this to understand the "why" behind design-driven automation, composable topologies, generators, and integration patterns. | ## Quick start[​](#quick-start "Direct link to Quick start") If you're ready to dive in: 1. Follow the **[installation guide](/demo-dc/install.md)** to set up your environment 2. Walk through the **[user tutorial](/demo-dc/user-walkthrough.md)** to create your first data center topology 3. Deploy to a **[virtual lab with Containerlab](/demo-dc/containerlab-deployment.md)** to test configurations 4. Try the **[Service Catalog](/demo-dc/service-catalog.md)** for simplified infrastructure provisioning 5. Explore the **[concepts](/demo-dc/concepts.md)** to deepen your understanding 6. Reference the **[developer guide](/demo-dc/developer-guide.md)** when you're ready to extend or customize ## What you'll learn[​](#what-youll-learn "Direct link to What you'll learn") Through this demo, you'll gain practical experience with: * **Schema-driven infrastructure modeling** - Defining devices, networks, and services with flexible, extensible schemas * **Design-driven automation** - Creating abstract topology designs that generators transform into concrete infrastructure * **Composable topologies** - Building complex data center and POP architectures from reusable components * **Version control for data** - Using branches and proposed changes to safely modify infrastructure * **Automated resource allocation** - Generating IP prefixes, VLANs, and addressing from resource pools * **Configuration generation** - Transforming structured data into device configurations via templates * **Data validation** - Enforcing consistency with custom checks * **GitOps patterns** - Managing infrastructure as code with full versioning and review workflows ## Architecture at a glance[​](#architecture-at-a-glance "Direct link to Architecture at a glance") The demo implements realistic network topologies including: * **Data center fabrics** with spine-leaf architecture and VxLAN/EVPN overlay * **Point of presence (POP) networks** with edge routers and peering connections * **Network segments** with load balancers and service endpoints * **Security zones and policies** with firewall rules and access control * **Cloud infrastructure** with vendor-agnostic modeling for AWS, GCP, and Azure * **Resource pools** for IP address, VLAN, and ASN allocation * **Multi-vendor support** (Arista, Juniper, Cisco, and SONiC templates) * **Automated topology generation** from abstract design definitions ## Key features demonstrated[​](#key-features-demonstrated "Direct link to Key features demonstrated") ### Schema extensibility[​](#schema-extensibility "Direct link to Schema extensibility") The demo includes comprehensive schemas for DCIM, IPAM, topology, routing, security, and load balancing. These schemas define not just object types, but relationships, constraints, and lifecycle behaviors. ### Generators and automation[​](#generators-and-automation "Direct link to Generators and automation") When you create a topology design (for example, DC-3), generators automatically create all supporting objects including devices, interfaces, IP addresses, routing protocols, and BGP peer groups following best practices and business rules. ### Branch-based workflows[​](#branch-based-workflows "Direct link to Branch-based workflows") All changes happen in branches. Proposed changes provide diff views, run validation checks, and regenerate artifacts before merging to main. ### Artifact generation[​](#artifact-generation "Direct link to Artifact generation") Templates transform Infrahub data into deployable artifacts including device configurations, topology diagrams, and cabling matrices. ## Community and support[​](#community-and-support "Direct link to Community and support") * **Source code**: [GitHub repository](https://github.com/opsmill/infrahub-demo-dc) * **Infrahub documentation**: [docs.infrahub.app](https://docs.infrahub.app) * **Discord community**: [Discord](https://discord.gg/opsmill) * **OpsMill website**: [opsmill.com](https://opsmill.com) ## Next steps[​](#next-steps "Direct link to Next steps") Ready to get started? Head to the **[installation guide](/demo-dc/install.md)** to set up your environment. --- # Cloud resource management This tutorial shows an example of cloud resource management schema, which provides a vendor-agnostic way to model cloud infrastructure across AWS, GCP, and Azure. You'll load sample cloud data and explore how Infrahub can serve as a unified inventory for multi-cloud environments. ## Overview[​](#overview "Direct link to Overview") The cloud schema enables you to track: * **Cloud providers** - AWS, GCP, Azure, or other cloud platforms * **Cloud accounts** - AWS accounts, GCP projects, Azure subscriptions * **Regions and availability zones** - Geographic locations and fault domains * **Virtual networks** - VPCs (AWS), VPC networks (GCP), VNets (Azure) * **Subnets** - Network segments within virtual networks * **Security groups** - Network access control rules (Security Groups, Firewall Rules, NSGs) * **Compute instances** - Virtual machines across all providers * **Network infrastructure** - Internet gateways, NAT gateways, route tables, elastic IPs * **Network interfaces** - ENIs, NICs attached to instances This vendor-agnostic model allows you to manage multi-cloud infrastructure from a single source of truth, with consistent naming and relationships regardless of the underlying cloud provider. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before starting this tutorial, ensure you have: * Completed the [installation guide](/demo-dc/install.md) and have Infrahub running * Loaded the bootstrap data and schemas * Access to the Infrahub web interface at `http://localhost:8000` ## Loading cloud demo data[​](#loading-cloud-demo-data "Direct link to Loading cloud demo data") The demo includes sample data for all three major cloud providers with realistic infrastructure examples. The easiest way to load the cloud demo is using the provided invoke task: ``` uv run invoke demo-cloud ``` This command: 1. Creates a new branch named `demo-cloud` 2. Loads all schemas (including the cloud schema) 3. Loads cloud object files with sample data for AWS, GCP, and Azure 4. Displays a URL to view the cloud resources ## Exploring cloud resources[​](#exploring-cloud-resources "Direct link to Exploring cloud resources") After loading the demo data, navigate to the cloud resources in the Infrahub web interface. ### Viewing all cloud resources[​](#viewing-all-cloud-resources "Direct link to Viewing all cloud resources") 1. Ensure you're on the correct branch (for example, `demo-cloud`) 2. Navigate to **Cloud Resource** in the left sidebar menu 3. You'll see a list of all cloud resource types Or access the cloud resources directly: ``` http://localhost:8000/objects/CloudResource?branch=demo-cloud ``` ### Sample data structure[​](#sample-data-structure "Direct link to Sample data structure") The demo includes a comprehensive multi-cloud environment: #### Cloud providers (3)[​](#cloud-providers-3 "Direct link to Cloud providers (3)") * Amazon Web Services (AWS) * Google Cloud Platform (GCP) * Microsoft Azure #### Cloud accounts (12)[​](#cloud-accounts-12 "Direct link to Cloud accounts (12)") Each provider has production, staging, and development accounts: * `opsmill-aws-production`, `opsmill-aws-staging`, `opsmill-aws-dev` * `opsmill-gcp-production`, `opsmill-gcp-staging`, `opsmill-gcp-dev` * `opsmill-azure-production`, `opsmill-azure-staging`, `opsmill-azure-dev` #### Regions and availability zones[​](#regions-and-availability-zones "Direct link to Regions and availability zones") * **AWS**: US East (N. Virginia), US West (Oregon), EU West (Ireland) * **GCP**: US Central (Iowa), US East (South Carolina), Europe West (Belgium) * **Azure**: East US, West US 2, West Europe Each region includes 3 availability zones. #### Virtual networks (12)[​](#virtual-networks-12 "Direct link to Virtual networks (12)") VPCs and VNets across all accounts with various configurations: * Production VPCs with public and private subnets * Staging and development networks * DNS support and hostname configuration #### Compute instances (19)[​](#compute-instances-19 "Direct link to Compute instances (19)") Various instance types across all providers: * Web servers, application servers, database servers * Linux and Windows instances * Different instance sizes (t3.large, m5.xlarge, n1-standard-2, Standard\_D2s\_v3, etc.) #### Network infrastructure[​](#network-infrastructure "Direct link to Network infrastructure") * Internet gateways for public connectivity * NAT gateways for private subnet outbound access * Route tables for traffic routing * Elastic/static IP addresses * Network interfaces with security group associations ## Schema architecture[​](#schema-architecture "Direct link to Schema architecture") The cloud schema uses a hierarchical structure with clear relationships: ``` CloudProvider └── CloudAccount └── CloudVirtualNetwork ├── CloudSubnet ├── CloudSecurityGroup ├── CloudInternetGateway └── CloudRouteTable CloudRegion └── CloudAvailabilityZone └── CloudInstance └── CloudNetworkInterface ``` ### Key relationships[​](#key-relationships "Direct link to Key relationships") * **CloudAccount** belongs to a **CloudProvider** (parent relationship) * **CloudRegion** is associated with a **CloudProvider** * **CloudAvailabilityZone** belongs to a **CloudRegion** (parent relationship) * **CloudVirtualNetwork** is associated with a **CloudAccount** and **CloudRegion** * **CloudSubnet** belongs to a **CloudVirtualNetwork** (parent relationship) * **CloudInstance** is associated with a **CloudAccount**, **CloudAvailabilityZone**, and **CloudSubnet** * **CloudSecurityGroup** can be attached to **CloudInstance** and **CloudNetworkInterface** ### Common attributes[​](#common-attributes "Direct link to Common attributes") All cloud resources inherit from the `CloudResource` generic, providing: * `name` - Resource name * `description` - Optional description * `cloud_id` - Provider-specific resource identifier (ARN, resource ID, etc.) * `status` - Operational status (active, stopped, provisioning, terminating, error) * `tags` - Optional tags for categorization ## Use cases[​](#use-cases "Direct link to Use cases") ### Multi-cloud inventory[​](#multi-cloud-inventory "Direct link to Multi-cloud inventory") Use Infrahub as a single source of truth for all cloud resources: * Track resources across AWS, GCP, and Azure in one place * Maintain consistent naming conventions * Link cloud resources to on-premises infrastructure ### Security auditing[​](#security-auditing "Direct link to Security auditing") Query security groups and their associations: * Identify instances with specific security group configurations * Audit network access rules across all clouds * Track public IP assignments ### Capacity planning[​](#capacity-planning "Direct link to Capacity planning") Analyze compute resources across your cloud footprint: * Count instances by type, region, or provider * Track resource utilization patterns * Plan for growth and optimization ### Network documentation[​](#network-documentation "Direct link to Network documentation") Document your cloud network architecture: * Map virtual networks, subnets, and routing * Track NAT and internet gateway configurations * Document network interface assignments ## Next steps[​](#next-steps "Direct link to Next steps") For more information on Infrahub concepts, see: * **[Understanding the concepts](/demo-dc/concepts.md)** - Core Infrahub patterns * **[Developer guide](/demo-dc/developer-guide.md)** - Extending schemas and creating transforms --- # Understanding the concepts This document explains the architectural patterns, design decisions, and core Infrahub concepts demonstrated in this demo. Read this to understand the "why" behind design-driven automation, composable topologies, generators, and the overall approach to infrastructure management. ## Design-driven automation[​](#design-driven-automation "Direct link to Design-driven automation") Traditional infrastructure automation often works at the device configuration level. You define configurations for individual devices, then deploy them. This approach has limitations when managing large-scale infrastructure. ### The traditional approach[​](#the-traditional-approach "Direct link to The traditional approach") In traditional automation: 1. You manually define each device and its configuration 2. Configurations are tightly coupled to specific devices 3. Scaling requires duplicating configuration patterns 4. Changing architecture means updating many individual configs 5. It's difficult to maintain consistency across the infrastructure ### The design-driven approach[​](#the-design-driven-approach "Direct link to The design-driven approach") Design-driven automation flips this model. Instead of defining devices, you define **intent** at a higher abstraction level: 1. You specify **what** you want (for example, "a 4-spine, 8-leaf data center") 2. Generators translate high-level designs into concrete infrastructure 3. Templates produce device-specific configurations from structured data 4. Scaling means adjusting design parameters, not editing configs 5. Architectural changes propagate automatically The demo implements this pattern. When you load `objects/dc/dc-arista-s.yml`, you're not defining 20 devices. You're defining a **design specification** that a generator transforms into complete infrastructure. ### Benefits[​](#benefits "Direct link to Benefits") * **Abstraction** - Work at the topology level, not device level * **Consistency** - Generators enforce best practices and standards * **Scalability** - Grow from 10 to 1000 devices by adjusting parameters * **Maintainability** - Change design templates, not individual configs * **Reusability** - Same generator can create DC-2, DC-3, DC-4 with different inputs ## Composable topologies[​](#composable-topologies "Direct link to Composable topologies") The demo uses composable building blocks to construct complex network architectures. ### Composition model[​](#composition-model "Direct link to Composition model") ``` Topology Design ├─ Device Templates (spine, leaf, border-leaf) ├─ Connectivity Patterns (fabric peering) ├─ Address Pools (management, loopback, fabric) ├─ Routing Design (eBGP, OSPF) └─ Service Overlays (EVPN, L2VPN, L3VPN) ``` Each layer builds on the previous one: 1. **Device templates** define interface layouts for each device role 2. **Connectivity patterns** define how devices interconnect 3. **Address pools** provide IP address and VLAN allocation 4. **Routing design** specifies underlay and overlay protocols 5. **Service overlays** enable tenant networks and services ### Example: DC-3 composition[​](#example-dc-3-composition "Direct link to Example: DC-3 composition") When you create DC-3, the generator: 1. Selects spine, leaf, and border-leaf **templates** 2. Applies fabric **connectivity pattern** (full mesh spine-to-leaf) 3. Allocates addresses from **pools** (loopback /32s, fabric /31s) 4. Configures **routing** (eBGP underlay, iBGP EVPN overlay) 5. Enables **EVPN service** framework This composition allows you to: * Swap eBGP for OSPF underlay by changing one parameter * Scale from 2 spines to 4 spines by updating device count * Change addressing scheme by modifying pool definitions * Add new device roles without rewriting generators ### Reusability across topologies[​](#reusability-across-topologies "Direct link to Reusability across topologies") The same composable patterns work for different topology types: * **Data centers** use spine-leaf composition with EVPN overlay * **POPs** use edge router composition with BGP peering * **Segments** use service endpoint composition with load balancers This reusability reduces code duplication and makes patterns portable. ## Generators as transformation engines[​](#generators-as-transformation-engines "Direct link to Generators as transformation engines") Generators transform abstract designs into concrete infrastructure. They embody business rules, network design patterns, and operational best practices. ### Generator responsibility[​](#generator-responsibility "Direct link to Generator responsibility") A generator's job is to: 1. **Read design input** - Parse high-level topology specifications 2. **Apply business rules** - Enforce naming conventions, numbering schemes 3. **Create infrastructure** - Generate devices, interfaces, IP addresses 4. **Establish relationships** - Connect devices, assign addresses, configure protocols 5. **Allocate resources** - Draw from IP pools, VLAN pools, ASN pools ### Example: create\_dc generator[​](#example-create_dc-generator "Direct link to Example: create_dc generator") The DC generator implements a spine-leaf fabric pattern: ``` async def generate(self, data: dict) -> None: # Read design parameters spine_count = data["spine_count"] leaf_count = data["leaf_count"] underlay_protocol = data["underlay_protocol"] # Create resource pools await self.create_address_pools() # Create devices from templates await self.create_devices() # Full mesh fabric peering await self.create_fabric_peering() # Configure underlay routing if underlay_protocol == "ospf": await self.create_ospf_underlay() else: await self.create_ebgp_underlay() # Configure overlay routing await self.create_ibgp_overlay() ``` ### Benefits of generators[​](#benefits-of-generators "Direct link to Benefits of generators") * **Encapsulation** - Complex logic in one place * **Testability** - Unit test generator logic separately * **Consistency** - Same generator always produces same output for same input * **Evolvability** - Update generator to change all future topologies * **Auditability** - Generator code documents design decisions ## Resource pools and allocation[​](#resource-pools-and-allocation "Direct link to Resource pools and allocation") Resource pools provide centralized management of scarce resources like IP addresses, VLANs, and ASNs. ### Pool concept[​](#pool-concept "Direct link to Pool concept") A resource pool is a defined range of resources that Infrahub allocates from as needed: ``` - kind: IpamIPPrefix data: - prefix: "10.0.0.0/16" pool_type: "management" description: "Management network pool" ``` When a generator needs an IP address for a management interface, it allocates from this pool. Infrahub tracks allocations and prevents conflicts. ### Pool types in the demo[​](#pool-types-in-the-demo "Direct link to Pool types in the demo") * **Management pools** - Out-of-band management addresses * **Loopback pools** - Router IDs and VTEP addresses * **Fabric pools** - Point-to-point links between switches * **Service pools** - Tenant network addressing * **VLAN pools** - Layer 2 segment identifiers * **ASN pools** - BGP autonomous system numbers ### Benefits[​](#benefits-1 "Direct link to Benefits") * **Automatic allocation** - No manual IP planning * **Conflict prevention** - Infrahub ensures uniqueness * **Visibility** - See which addresses are allocated vs available * **Reclamation** - Deleting devices returns addresses to pool * **Hierarchical allocation** - Pools can allocate from parent pools ## Schema-driven data modeling[​](#schema-driven-data-modeling "Direct link to Schema-driven data modeling") Schemas define the structure of your infrastructure data. They specify object types, attributes, relationships, and constraints. ### Why schemas matter[​](#why-schemas-matter "Direct link to Why schemas matter") Schemas provide: 1. **Type safety** - Attributes have defined types (text, number, IP address) 2. **Validation** - Constraints enforce data integrity 3. **Relationships** - Formalize connections between objects 4. **Extensibility** - Inheritance allows customization without modification 5. **API generation** - Infrahub auto-generates GraphQL API from schemas 6. **Computed attributes** - Automatically derive values from other attributes 7. **Range expansion** - Define multiple items with compact notation ### Schema evolution[​](#schema-evolution "Direct link to Schema evolution") Schemas can evolve over time through: * **Attribute addition** - Add new fields to existing nodes * **Relationship addition** - Create new connections between nodes * **Inheritance** - Extend base nodes with specialized types * **Constraints** - Add uniqueness or validation rules When you extend schemas, existing data remains compatible. This allows iterative refinement. ### Schema organization[​](#schema-organization "Direct link to Schema organization") The demo organizes schemas by domain: * **Base schemas** - Core models used across all topologies * **Extension schemas** - Domain-specific models (routing, security, load balancing) * **Namespaces** - Group related nodes (Dcim, Ipam, Service, Security) This organization supports: * **Modularity** - Enable/disable domains as needed * **Clarity** - Related nodes grouped together * **Maintainability** - Domain experts can own their schemas ### Advanced schema features[​](#advanced-schema-features "Direct link to Advanced schema features") The demo showcases several advanced Infrahub schema capabilities: **Computed attributes** automatically generate values based on other attributes. For example, Autonomous System names are computed from ASN values (AS65000 from ASN 65000) using Jinja2 templates. This ensures naming consistency and eliminates manual data entry. [Learn more about computed attributes](https://docs.infrahub.app/topics/computed-attributes). **Interface range expansion** allows compact definition of multiple interfaces. Instead of defining 30 interfaces individually, you can use `Ethernet1/[1-30]` which automatically expands to `Ethernet1/1` through `Ethernet1/30`. This dramatically reduces YAML verbosity in device templates and topology definitions. [Learn more about range expansion](https://docs.infrahub.app/python-sdk/reference/templating#netutils-filters). ## Branch-based infrastructure changes[​](#branch-based-infrastructure-changes "Direct link to Branch-based infrastructure changes") Infrahub uses Git-like branching for infrastructure data. This enables safe, reviewable changes with rollback capability. ### Branch workflow[​](#branch-workflow "Direct link to Branch workflow") 1. **Create branch** - Isolate changes from main branch 2. **Make modifications** - Add devices, change configs, update relationships 3. **Validate changes** - Run checks and generate artifacts 4. **Review changes** - Examine diffs and validation results 5. **Merge or discard** - Apply changes to main or abandon the branch ### Why branches matter[​](#why-branches-matter "Direct link to Why branches matter") Without branches, every change immediately affects the production infrastructure. This makes it risky to experiment or test ideas. Branches provide: * **Safety** - Test changes without affecting main * **Collaboration** - Multiple teams can work on different branches * **Review** - Examine changes before they take effect * **Rollback** - Discard unsuccessful experiments * **History** - Track what changed and when ### Proposed changes[​](#proposed-changes "Direct link to Proposed changes") Proposed changes are Infrahub's equivalent of pull requests. They: 1. Show diffs between source and destination branches 2. Run validation checks automatically 3. Regenerate affected artifacts 4. Provide a review interface for approvals 5. Merge atomically when approved This workflow brings software development practices (code review, CI/CD, version control) to infrastructure management. ## Artifacts and configuration generation[​](#artifacts-and-configuration-generation "Direct link to Artifacts and configuration generation") Artifacts are the final outputs that deploy to devices. The demo generates configurations, topology files, and documentation. ### Artifact generation flow[​](#artifact-generation-flow "Direct link to Artifact generation flow") ``` GraphQL Query → Transform → Template → Artifact ``` 1. **Query** - Fetch device data from Infrahub 2. **Transform** - Process data into template-friendly structure 3. **Template** - Render Jinja2 template with processed data 4. **Artifact** - Store generated configuration in Infrahub ### Why separate transforms and templates[​](#why-separate-transforms-and-templates "Direct link to Why separate transforms and templates") Separating Python transforms from Jinja2 templates provides: * **Reusability** - Same template works with different transforms * **Testability** - Test transform logic and template rendering independently * **Maintainability** - Network engineers can edit templates without Python knowledge * **Flexibility** - Swap templates for different vendor platforms ### Artifact regeneration[​](#artifact-regeneration "Direct link to Artifact regeneration") Artifacts automatically regenerate when: * Data changes (device attributes, relationships) * Templates change (update Jinja2 files) * Transforms change (update Python logic) * Proposed changes are created (for review) This ensures artifacts always reflect current state. ## Validation and checks[​](#validation-and-checks "Direct link to Validation and checks") Checks validate infrastructure before deployment. They catch errors, enforce policies, and ensure consistency. ### Check types[​](#check-types "Direct link to Check types") * **Configuration validation** - Verify device configs are valid * **Connectivity validation** - Ensure devices are properly connected * **Policy enforcement** - Check against organizational standards * **Best practice validation** - Verify design patterns are followed ### When checks run[​](#when-checks-run "Direct link to When checks run") Checks execute: 1. **On proposed change creation** - Before merge 2. **On branch commits** - During development 3. **On demand** - Manual execution 4. **On schedule** - Periodic validation (if configured) ### Check outcomes[​](#check-outcomes "Direct link to Check outcomes") Checks produce: * **Errors** - Must be fixed before merge * **Warnings** - Should be reviewed but don't block merge * **Info** - Informational messages for context This tiered approach balances strictness with flexibility. ## Integration patterns[​](#integration-patterns "Direct link to Integration patterns") The demo demonstrates integration with external tools and systems. ### Repository integration[​](#repository-integration "Direct link to Repository integration") Infrahub can sync with Git repositories to: * Load generators, transforms, and checks from code repos * Version control infrastructure-as-code components * Enable GitOps workflows * Collaborate using standard Git workflows ### CI/CD integration[​](#cicd-integration "Direct link to CI/CD integration") The demo includes GitHub Actions for: * Linting and type checking * Running unit tests * Running integration tests * Validating schemas and data This ensures code quality and catches issues before deployment. ### Containerlab integration[​](#containerlab-integration "Direct link to Containerlab integration") The demo can generate Containerlab topologies for: * Virtual lab deployment * Testing configurations in simulation * Training and demonstrations * Development and testing This provides a complete development-to-production workflow. ## Patterns for scale[​](#patterns-for-scale "Direct link to Patterns for scale") The demo implements patterns that scale from small to large deployments. ### Batching[​](#batching "Direct link to Batching") Generators use batching to efficiently create many objects: ``` batch = await client.create_batch() for device in devices: batch.add(kind="DcimDevice", data=device) await batch.execute() ``` Batching reduces API calls and improves performance. ### Async operations[​](#async-operations "Direct link to Async operations") All SDK operations use async/await for concurrency: ``` # Sequential (slow) device1 = await create_device("spine1") device2 = await create_device("spine2") # Concurrent (fast) results = await asyncio.gather( create_device("spine1"), create_device("spine2") ) ``` This allows generators to create infrastructure in parallel. ### Hierarchical allocation[​](#hierarchical-allocation "Direct link to Hierarchical allocation") Resource pools support hierarchy for delegation: ``` Root Pool (10.0.0.0/8) ├─ Region 1 (10.0.0.0/16) │ ├─ DC-1 (10.0.0.0/20) │ └─ DC-2 (10.0.16.0/20) └─ Region 2 (10.1.0.0/16) ``` This supports multi-region deployments with delegated address management. ## Mental models[​](#mental-models "Direct link to Mental models") ### Infrastructure as data[​](#infrastructure-as-data "Direct link to Infrastructure as data") Think of infrastructure not as static configurations, but as structured data that can be queried, transformed, and versioned like any other data. Traditional: "Here are 50 configuration files" Design-driven: "Here's data representing 50 devices, query what you need" ### Generators as factories[​](#generators-as-factories "Direct link to Generators as factories") Generators are factories that produce infrastructure from blueprints. The blueprint (design data) specifies what to build. The factory (generator) knows how to build it. Blueprint: "4 spines, 8 leaves, eBGP underlay" Factory: Creates 12 devices with 200+ interfaces and relationships ### Schemas as contracts[​](#schemas-as-contracts "Direct link to Schemas as contracts") Schemas are contracts between producers (generators, users) and consumers (transforms, checks). They define what data must be provided and what shape it takes. Contract: "A device must have a name, role, platform, and location" Producers: Ensure these fields are populated Consumers: Can rely on these fields existing ## Design philosophy[​](#design-philosophy "Direct link to Design philosophy") The demo embodies several design principles: ### Declarative over imperative[​](#declarative-over-imperative "Direct link to Declarative over imperative") Describe the desired state (declarative) rather than steps to achieve it (imperative). Imperative: "Create spine1, create eth1, assign IP 10.0.0.1, enable interface" Declarative: "Spine1 exists with eth1 having IP 10.0.0.1 in enabled state" ### Data-driven over code-driven[​](#data-driven-over-code-driven "Direct link to Data-driven over code-driven") Use data to drive behavior rather than hard-coding logic. Code-driven: `if dc == "DC-3": create_4_spines()` Data-driven: `for _ in range(design.spine_count): create_spine()` ### Separation of concerns[​](#separation-of-concerns "Direct link to Separation of concerns") Separate concerns into focused components: * Schemas define structure * Generators create objects * Transforms produce configs * Templates format output * Checks validate results Each component has a single responsibility. ### Convention over configuration[​](#convention-over-configuration "Direct link to Convention over configuration") Use sensible defaults and conventions to reduce configuration burden. Default: Spine interfaces are "Ethernet1-32" Convention: Spines connect to all leaves Configuration: Override when needed ## Next steps[​](#next-steps "Direct link to Next steps") Now that you understand the concepts, you can: * **Build new topologies** - Apply patterns to different use cases * **Extend generators** - Add new topology types or enhance existing ones * **Customize schemas** - Add organization-specific attributes * **Create custom checks** - Enforce your policies and standards For implementation details, see the [developer guide](/demo-dc/developer-guide.md). --- # Deploy a virtual lab with Containerlab This tutorial walks you through deploying a virtual data center topology using Containerlab. You'll take the configurations generated by Infrahub and spin up a fully functional network lab with Arista cEOS virtual switches. By the end of this tutorial, you'll have a running virtual data center that you can access, test, and validate before deploying to production hardware. ## What you'll accomplish[​](#what-youll-accomplish "Direct link to What you'll accomplish") In this tutorial, you will: 1. Generate a data center topology with Arista switches 2. Extract device configurations and topology files from Infrahub 3. Deploy the virtual lab using Containerlab 4. Verify the deployment and access virtual devices ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before starting this tutorial, ensure you have: * Completed the [installation guide](/demo-dc/install.md) and have Infrahub running * Completed the [user walkthrough](/demo-dc/user-walkthrough.md) to understand the basic workflow * **Docker** installed and running with sufficient resources (at least 16GB RAM recommended to support a few cEOS containers) * **Containerlab** installed ([installation instructions](https://containerlab.dev/install/)) * **sudo access** for running Containerlab (required for network namespace operations) info This tutorial builds on the user walkthrough. You must complete the basic workflow (creating branches, loading designs, running generators) before deploying to Containerlab. ## Understanding the workflow[​](#understanding-the-workflow "Direct link to Understanding the workflow") The Containerlab deployment workflow connects Infrahub's generated artifacts to a virtual lab environment: ``` Infrahub Data → Generator → Artifacts → Containerlab Deployment ↓ Config Files + Topology File ↓ Virtual Network Lab (cEOS) ``` **Key components:** * **Generated configurations** - Device startup configs extracted from Infrahub artifacts * **Containerlab topology file** - YAML definition of virtual lab including device connections * **cEOS images** - Arista Container EOS virtual switches * **Containerlab** - Network lab orchestration tool that creates Docker containers with proper network connectivity ## Step 1: generate the Arista data center topology[​](#step-1-generate-the-arista-data-center-topology "Direct link to Step 1: generate the Arista data center topology") Use the automated demo command to create a complete Arista data center in Infrahub: ``` uv run invoke demo-dc-arista ``` This command: 1. Creates a new branch `add-dc3` 2. Loads the DC-3 Arista design (`objects/dc/dc-arista-s.yml`) 3. Returns immediately after loading (generator runs asynchronously) ### Wait for generator completion[​](#wait-for-generator-completion "Direct link to Wait for generator completion") The generator runs automatically in the background. Monitor its progress: 1. Navigate to `http://localhost:8000` 2. Switch to the `add-dc3` branch using the branch selector 3. Go to **System → Tasks** in the left sidebar 4. Find the `create_dc` generator task 5. Wait for status to change from "Pending" → "Running" → "Completed" Generator execution typically takes 2-3 minutes as it creates devices, interfaces, IP addresses, and routing configurations. tip While waiting, you can review the design parameters at **Network Topologies → Deployments → Data center → DC-3** to see what will be created. ## Step 2: Create a proposed change and merge[​](#step-2-create-a-proposed-change-and-merge "Direct link to Step 2: Create a proposed change and merge") Before extracting configurations, merge the topology to the main branch: 1. Navigate to **Proposed Changes** 2. Click **New Proposed Change** 3. Fill in the form: * **Name**: `Add DC-3 Arista data center` * **Source Branch**: `add-dc3` * **Destination Branch**: `main` * **Description**: `Deploy DC-3 with Arista switches for Containerlab testing` 4. Click **Create Proposed Change** 5. Review validations and artifacts 6. Click **Merge** once all checks pass info You can also extract configurations from the branch without merging, but merging ensures the topology is validated and ready for production use. ## Step 3: Extract configurations and topology files[​](#step-3-extract-configurations-and-topology-files "Direct link to Step 3: Extract configurations and topology files") Run the containerlab deployment command: ``` uv run invoke containerlab ``` This command performs two operations: ### Operation 1: Extract artifacts from Infrahub[​](#operation-1-extract-artifacts-from-infrahub "Direct link to Operation 1: Extract artifacts from Infrahub") The invoke task runs `uv run scripts/get_configs.py --branch add-dc3` which: * Queries Infrahub for all artifacts in the specified branch * Downloads device configurations (startup configs for each switch) * Downloads the Containerlab topology file * Saves everything to the `generated-configs/` directory **Directory structure created:** ``` generated-configs/ ├── clab/ │ └── DC-3.clab.yml # Containerlab topology definition └── devices/ ├── dc3-spine1.cfg # Spine switch configurations ├── dc3-spine2.cfg ├── dc3-leaf1.cfg # Leaf switch configurations ├── dc3-leaf2.cfg ├── dc3-leaf3.cfg ├── dc3-leaf4.cfg ├── dc3-border-leaf1.cfg # Border leaf configurations └── dc3-border-leaf2.cfg ``` ### Operation 2: Deploy with Containerlab[​](#operation-2-deploy-with-containerlab "Direct link to Operation 2: Deploy with Containerlab") The invoke task then runs `sudo -E containerlab deploy -t generated-configs/clab/DC-3.clab.yml` which: * Reads the topology definition file * Pulls the Arista cEOS Docker image from the OpsMill artifact repository * Creates Docker containers for each device * Configures virtual network connections between devices * Loads the generated startup configurations into each device * Starts all devices info The Containerlab topology file references a cEOS Docker image hosted in the OpsMill artifact repository. On first deployment, Docker will download this image (approximately 500MB). The generated configurations are automatically loaded into each cEOS container during instantiation via Containerlab's startup-configuration mechanism. ### Monitor deployment progress[​](#monitor-deployment-progress "Direct link to Monitor deployment progress") Watch the console output as Containerlab: 1. Creates the lab directory 2. Creates network links between devices 3. Creates containers for each device 4. Loads configurations into devices 5. Waits for devices to boot Deployment typically takes 2-4 minutes depending on your system and whether the Docker image is cached. ## Step 4: Verify the deployment[​](#step-4-verify-the-deployment "Direct link to Step 4: Verify the deployment") Once deployment completes, verify the virtual lab is running: ### Check container status[​](#check-container-status "Direct link to Check container status") ``` sudo containerlab inspect --name DC-3 ``` You should see output showing all devices with their management IP addresses and states. ### List running containers[​](#list-running-containers "Direct link to List running containers") ``` docker ps --filter "label=containerlab=DC-3" ``` All device containers should show as "Up" with healthy status. ### Check device connectivity[​](#check-device-connectivity "Direct link to Check device connectivity") ``` # List all Containerlab labs sudo containerlab inspect --all # Show detailed topology sudo containerlab graph --name DC-3 ``` ## Step 5: Access virtual devices[​](#step-5-access-virtual-devices "Direct link to Step 5: Access virtual devices") Connect to any device in your virtual lab using the Containerlab CLI: ### Connect to a spine switch[​](#connect-to-a-spine-switch "Direct link to Connect to a spine switch") ``` sudo containerlab exec --name DC-3 --node dc3-spine1 Cli ``` This drops you into the Arista EOS CLI. Try some commands: ``` # Check device hostname and version show version # Verify interfaces are up show ip interface brief # Check BGP neighbors show ip bgp summary # View running configuration show running-config ``` Type `exit` to return to your shell. ### Connect to a leaf switch[​](#connect-to-a-leaf-switch "Direct link to Connect to a leaf switch") ``` sudo containerlab exec --name DC-3 --node dc3-leaf1 Cli ``` Verify the leaf's connectivity to spines: ``` # Check BGP neighbors (should see both spines) show ip bgp summary # Check VXLAN configuration show vxlan vtep # Verify EVPN routes show bgp evpn summary ``` ### Connect to devices with SSH[​](#connect-to-devices-with-ssh "Direct link to Connect to devices with SSH") Containerlab also provides SSH access to devices. Find the management IPs: ``` sudo containerlab inspect --name DC-3 ``` Then SSH using the displayed IP addresses: ``` ssh admin@ ``` Default credentials are typically `admin` / `admin` for cEOS devices. ## Step 6: Destroy the lab (cleanup)[​](#step-6-destroy-the-lab-cleanup "Direct link to Step 6: Destroy the lab (cleanup)") When you're done testing, destroy the lab to free resources: ``` sudo containerlab destroy --name DC-3 ``` This command: * Stops all containers * Removes containers and networks * Cleans up Containerlab state * Preserves the configuration files in `generated-configs/` tip The generated configuration files remain in `generated-configs/` even after destroying the lab. This allows you to redeploy quickly or version control your configs. ## What you've learned[​](#what-youve-learned "Direct link to What you've learned") Congratulations! You've completed the Containerlab deployment workflow: * ✅ Generated a multi-vendor data center topology in Infrahub * ✅ Extracted device configurations and topology files * ✅ Deployed a virtual network lab with Containerlab * ✅ Verified device connectivity and routing protocols * ✅ Accessed and tested virtual devices * ✅ Cleaned up lab resources ## Advanced usage[​](#advanced-usage "Direct link to Advanced usage") ### Using different branches[​](#using-different-branches "Direct link to Using different branches") Deploy configurations from any branch: ``` uv run invoke containerlab --branch my-branch --topology DC-3 ``` ### Testing configuration changes[​](#testing-configuration-changes "Direct link to Testing configuration changes") Use this workflow to test changes before production: 1. Create a branch in Infrahub 2. Modify device configurations or topology 3. Run generator 4. Extract configs and deploy to Containerlab 5. Test in virtual lab 6. If successful, merge to main and deploy to production ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Deployment fails with "permission denied"[​](#deployment-fails-with-permission-denied "Direct link to Deployment fails with \"permission denied\"") Ensure you're using `sudo` for Containerlab commands: ``` sudo -E containerlab deploy -t generated-configs/clab/DC-3.clab.yml ``` The `-E` flag preserves environment variables (important for Docker access). ### Docker image pull fails[​](#docker-image-pull-fails "Direct link to Docker image pull fails") If the cEOS image pull fails, verify: 1. Docker is running and you have internet connectivity 2. You have sufficient disk space (at least 2GB free) 3. You can access the OpsMill artifact repository ### Devices don't boot or configurations aren't loaded[​](#devices-dont-boot-or-configurations-arent-loaded "Direct link to Devices don't boot or configurations aren't loaded") Check container logs: ``` docker logs clab-DC-3-dc3-spine1 ``` Look for configuration syntax errors or boot failures. ### Out of memory errors[​](#out-of-memory-errors "Direct link to Out of memory errors") Containerlab labs can be resource-intensive. For DC-3 with 8 devices: * Minimum: 4GB RAM * Recommended: 8GB RAM * Optimal: 16GB RAM Reduce device count or increase Docker's memory allocation if needed. ## Next steps[​](#next-steps "Direct link to Next steps") For deeper understanding: * **[Understanding the concepts](/demo-dc/concepts.md)** - Learn about this design-driven automation demo * **[Developer guide](/demo-dc/developer-guide.md)** - Understand how generators and transformations work * **[Containerlab documentation](https://containerlab.dev/)** - Explore advanced Containerlab features --- # Developer guide This guide provides a technical deep-dive into how the Infrahub demo works under the hood. Use this when you want to extend functionality, troubleshoot issues, customize the demo, or understand implementation details. ## Project architecture[​](#project-architecture "Direct link to Project architecture") The demo follows Infrahub's SDK pattern with five core component types working together: ``` Schemas → Data → Generators → Transforms → Configurations ↓ Checks (Validation) ``` ### Component types[​](#component-types "Direct link to Component types") 1. **Schemas** (`schemas/`) - Define data models, relationships, and constraints 2. **Generators** (`generators/`) - Create infrastructure topology programmatically 3. **Transforms** (`transforms/`) - Convert Infrahub data to device configurations 4. **Checks** (`checks/`) - Validate configurations and connectivity 5. **Templates** (`templates/`) - Jinja2 templates for device configurations All components are registered in `.infrahub.yml`, which acts as the configuration hub. ## Project structure[​](#project-structure "Direct link to Project structure") ``` infrahub-demo-dc/ ├── .infrahub.yml # Component registration ├── checks/ # Validation checks │ ├── spine.py │ ├── leaf.py │ ├── edge.py │ └── loadbalancer.py ├── objects/ # Demo data │ ├── bootstrap/ # Initial data (19 files: groups, locations, platforms, roles, devices, etc.) │ ├── cloud_security/ # Cloud security examples (services, devices, gateways) │ ├── dc/ # Data center design files │ │ ├── dc-arista-s.yml # DC-3 design data (Arista) │ │ ├── dc-cisco-s.yml # DC-2 design data (Cisco) │ │ ├── dc-cisco-s-border-leafs.yml # Cisco DC with border leafs │ │ ├── dc-juniper-s.yml # DC-5 design data (Juniper) │ │ └── dc-sonic-border-leafs.yml # DC-4 design data (SONiC with border leafs) │ ├── events/ # Event action definitions │ ├── lb/ # Load balancer configurations │ ├── pop/ # Point of presence design files │ │ ├── pop-1.yml # POP-1 design data │ │ └── pop-2.yml # POP-2 design data │ └── security/ # Security zones, policies, rules (15 files) ├── generators/ # Topology generators │ ├── generate_dc.py # Data center generator │ ├── generate_pop.py # POP generator │ ├── generate_segment.py # Network segment generator │ ├── common.py # Shared utilities │ └── schema_protocols.py # Type protocols ├── menus/ # UI menu definitions │ └── menu-full.yml # Complete menu ├── queries/ # GraphQL queries │ ├── config/ # Configuration queries │ ├── topology/ # Topology queries │ └── validation/ # Validation queries ├── schemas/ # Data model definitions │ ├── base/ # Core models │ │ ├── dcim.yml │ │ ├── ipam.yml │ │ ├── location.yml │ │ └── topology.yml │ └── extensions/ # Extended models │ ├── console/ │ ├── routing/ │ ├── security/ │ ├── service/ │ └── topology/ ├── docs/ # Documentation (Docusaurus) │ ├── docs/ # Documentation content (.mdx files) │ ├── static/ # Static assets │ ├── docusaurus.config.ts # Docusaurus configuration │ └── package.json # Node.js dependencies ├── scripts/ # Automation scripts │ ├── bootstrap.py # Complete setup script │ ├── populate_security_relationships.py # Security data relationships │ ├── create_proposed_change.py # Create proposed changes │ └── get_configs.py # Retrieve device configurations ├── service_catalog/ # Streamlit Service Catalog application │ ├── pages/ # Streamlit pages │ │ └── 1_Create_DC.py # DC creation UI │ ├── utils/ # Utility modules │ │ ├── api.py # Infrahub API client │ │ ├── config.py # Configuration │ │ └── ui.py # UI helpers │ ├── Home.py # Main application page │ └── Dockerfile # Container definition ├── templates/ # Jinja2 config templates ├── tests/ # Test suite │ ├── integration/ # Integration tests │ ├── smoke/ # Smoke tests │ └── unit/ # Unit tests ├── transforms/ # Config transforms │ ├── edge.py │ ├── leaf.py │ ├── loadbalancer.py │ └── spine.py └── tasks.py # Invoke task definitions ``` ## Schemas[​](#schemas "Direct link to Schemas") Schemas define the data model using YAML. They specify nodes (object types), attributes, relationships, and constraints. ### Schema naming conventions[​](#schema-naming-conventions "Direct link to Schema naming conventions") * **Nodes**: PascalCase (for example, `DcimDevice`) * **Attributes**: snake\_case (for example, `device_type`) * **Relationships**: snake\_case (for example, `parent_location`) * **Namespaces**: PascalCase (for example, `Dcim`, `Ipam`, `Service`) ### Schema example[​](#schema-example "Direct link to Schema example") ``` nodes: - name: GenericDevice namespace: Dcim description: "A network device" inherit_from: - DcimDevice attributes: - name: hostname kind: Text optional: false unique: true - name: device_type kind: Text optional: true relationships: - name: location peer: LocationBuilding cardinality: one optional: false - name: interfaces peer: DcimInterface cardinality: many kind: Component ``` ### Computed attributes[​](#computed-attributes "Direct link to Computed attributes") Infrahub supports [computed attributes](https://docs.infrahub.app/topics/computed-attributes) that automatically generate values based on other attributes using Jinja2 templates. The demo uses this feature in the BGP schema for Autonomous System names. **Example from `schemas/extensions/routing/bgp.yml`:** ``` attributes: - name: name kind: Text computed_attribute: kind: Jinja2 jinja2_template: "AS{{asn__value}}" read_only: true optional: false - name: asn kind: Number description: "Autonomous System Number" ``` When you create an Autonomous System with ASN 65000, the `name` attribute is automatically computed as "AS65000". This ensures consistency and reduces manual data entry errors. **Benefits of computed attributes:** * **Consistency** - Standardized naming conventions enforced automatically * **Reduced errors** - No manual entry of derived values * **Dynamic updates** - Values recompute when dependencies change * **Read-only enforcement** - Prevents manual modification of computed values ### Schema types[​](#schema-types "Direct link to Schema types") The demo includes schemas for: * **DCIM** (Data Center Infrastructure Management) - Devices, interfaces, racks * **IPAM** (IP Address Management) - IP addresses, prefixes, VLANs * **Location** - Sites, buildings, rooms * **Topology** - Data centers, POPs, deployments * **Routing** - BGP, OSPF, routing policies * **Security** - Zones, policies, firewall rules * **Service** - Load balancers, segments, services ### Loading schemas[​](#loading-schemas "Direct link to Loading schemas") ``` uv run infrahubctl schema load schemas --branch main ``` Schemas are loaded into Infrahub and become the foundation for all data. ## Generators[​](#generators "Direct link to Generators") Generators create infrastructure topology programmatically from high-level design inputs. They inherit from `InfrahubGenerator` and implement the `generate()` method. ### Generator pattern[​](#generator-pattern "Direct link to Generator pattern") ``` from infrahub_sdk.generators import InfrahubGenerator from typing import Any class DCTopologyGenerator(InfrahubGenerator): async def generate(self, data: dict[str, Any]) -> None: """Generate data center topology based on design data.""" # 1. Query design data # 2. Create devices # 3. Create interfaces # 4. Create IP addresses # 5. Create routing configurations pass ``` ### DC generator workflow[​](#dc-generator-workflow "Direct link to DC generator workflow") The `create_dc` generator in `generators/generate_dc.py`: 1. **Queries the topology design** - Reads DC-3 parameters like spine count, leaf count, underlay protocol 2. **Creates resource pools** - Sets up IP prefix pools and VLAN pools 3. **Creates devices** - Generates spine, leaf, and border-leaf switches with correct roles and platforms 4. **Creates interfaces** - Adds physical interfaces, loopbacks, and sub-interfaces 5. **Creates connections** - Establishes fabric peering between spines and leaves 6. **Configures routing** - Sets up BGP or OSPF underlay and BGP EVPN overlay 7. **Assigns IP addresses** - Allocates addresses from pools for all interfaces ### Generator registration[​](#generator-registration "Direct link to Generator registration") Generators are registered in `.infrahub.yml`: ``` generator_definitions: - name: create_dc file_path: generators/generate_dc.py targets: topologies_dc query: topology_dc class_name: DCTopologyGenerator parameters: name: name__value ``` * **targets** - GraphQL query that selects which objects trigger the generator * **query** - GraphQL query providing input data * **parameters** - Parameters passed from triggering object ### Running generators[​](#running-generators "Direct link to Running generators") Generators can be triggered: 1. **Manually** via the web UI (Actions → Generator Definitions) 2. **Via API** using GraphQL mutations 3. **Automatically** via event actions (if configured) ## Transforms[​](#transforms "Direct link to Transforms") Transforms convert Infrahub data into device configurations. They inherit from `InfrahubTransform` and use Jinja2 templates. ### Transform pattern[​](#transform-pattern "Direct link to Transform pattern") ``` from infrahub_sdk.transforms import InfrahubTransform from typing import Any class SpineTransform(InfrahubTransform): query = "spine_config" # GraphQL query name async def transform(self, data: Any) -> Any: """Transform InfrahubHub data to spine configuration.""" device = data["DcimDevice"]["edges"][0]["node"] # Process data context = self.prepare_context(device) # Render template return self.render_template( template="spine.j2", data=context ) def prepare_context(self, device: Any) -> dict[str, Any]: """Prepare template context from device data.""" return { "hostname": device["name"]["value"], "interfaces": self.process_interfaces(device["interfaces"]), "bgp": self.process_bgp(device), } ``` ### Transform workflow[​](#transform-workflow "Direct link to Transform workflow") 1. **Query data** - Fetch device and related data via GraphQL 2. **Process data** - Transform into template-friendly structure 3. **Render template** - Use Jinja2 to generate configuration 4. **Return artifact** - Provide configuration as string ### Transform registration[​](#transform-registration "Direct link to Transform registration") Transforms are registered in `.infrahub.yml`: ``` python_transforms: - name: spine class_name: Spine file_path: transforms/spine.py artifact_definitions: - name: spine_config artifact_name: spine content_type: text/plain targets: spines # GraphQL query selecting devices transformation: spine # Transform name parameters: device: name__value ``` ## Templates[​](#templates "Direct link to Templates") Jinja2 templates generate device configurations from structured data. ### Template example[​](#template-example "Direct link to Template example") ``` hostname {{ hostname }} {% for interface in interfaces %} interface {{ interface.name }} {% if interface.description %} description {{ interface.description }} {% endif %} {% if interface.ip_address %} ip address {{ interface.ip_address }} {% endif %} {% if interface.enabled %} no shutdown {% endif %} {% endfor %} router bgp {{ bgp.asn }} {% for neighbor in bgp.neighbors %} neighbor {{ neighbor.ip }} remote-as {{ neighbor.asn }} {% endfor %} ``` Templates use standard Jinja2 syntax with filters and control structures. ## Checks[​](#checks "Direct link to Checks") Checks validate configurations and connectivity. They inherit from `InfrahubCheck` and implement the `check()` method. ### Check pattern[​](#check-pattern "Direct link to Check pattern") ``` from infrahub_sdk.checks import InfrahubCheck from typing import Any class CheckSpine(InfrahubCheck): query = "spine_validation" async def check(self, data: Any) -> None: """Validate spine device configuration.""" device = data["DcimDevice"]["edges"][0]["node"] # Validation logic if not self.has_required_interfaces(device): self.log_error( "Missing required interfaces", object_id=device["id"], object_type="DcimDevice" ) if not self.has_bgp_config(device): self.log_warning( "BGP not configured", object_id=device["id"] ) ``` ### Check registration[​](#check-registration "Direct link to Check registration") ``` check_definitions: - name: validate_spine class_name: CheckSpine file_path: checks/spine.py targets: spines parameters: device: name__value ``` ## GraphQL queries[​](#graphql-queries "Direct link to GraphQL queries") Queries are defined in `.gql` files and referenced by name in transforms and checks. ### Query example[​](#query-example "Direct link to Query example") ``` query GetSpineConfig($device_name: String!) { DcimDevice(name__value: $device_name) { edges { node { id name { value } role { value } platform { value } interfaces { edges { node { name { value } description { value } ip_addresses { edges { node { address { value } } } } } } } } } } } ``` ### Query registration[​](#query-registration "Direct link to Query registration") ``` queries: - name: spine_config file_path: queries/config/spine.gql ``` ## Bootstrap data[​](#bootstrap-data "Direct link to Bootstrap data") Bootstrap data provides initial objects like locations, platforms, and device types. ### Bootstrap structure[​](#bootstrap-structure "Direct link to Bootstrap structure") ``` objects/bootstrap/ ├── 01_organizations.yml # Organizations ├── 02_asn_pools.yml # BGP ASN pools ├── 03_locations.yml # Sites and buildings ├── 04_platforms.yml # Device platforms ├── 05_roles.yml # Device roles ├── 06_device_types.yml # Device models ├── 07_device_templates.yml # Interface templates └── ... ``` Files are numbered to ensure correct loading order due to dependencies. ### Interface range expansion[​](#interface-range-expansion "Direct link to Interface range expansion") The bootstrap data uses Infrahub's [interface range expansion feature](https://docs.infrahub.app/python-sdk/reference/templating#netutils-filters) to efficiently define multiple interfaces with compact syntax. This feature automatically expands range notation into individual interfaces. **Example from `objects/bootstrap/10_physical_device_templates.yml`:** ``` interfaces: kind: TemplateInterfacePhysical data: - template_name: N9K-C9336C-FX2_SPINE_Ethernet1/[1-30] name: Ethernet1/[1-30] role: leaf - template_name: N9K-C9336C-FX2_SPINE_Ethernet1/[31-36] name: Ethernet1/[31-36] role: uplink ``` When loaded, `Ethernet1/[1-30]` expands to 30 individual interfaces: `Ethernet1/1`, `Ethernet1/2`, ... `Ethernet1/30`. This dramatically reduces YAML verbosity when defining device templates with many interfaces. **Benefits of range expansion:** * **Compact notation** - Define dozens of interfaces in a single line * **Reduced errors** - Less repetitive typing means fewer mistakes * **Simplified maintenance** - Update interface ranges without editing individual entries * **Vendor compatibility** - Supports common interface naming patterns (Ethernet, GigabitEthernet, et-, ge-, etc.) This feature is used extensively throughout the bootstrap data for device templates, physical devices, and topology definitions. ### Loading bootstrap data[​](#loading-bootstrap-data "Direct link to Loading bootstrap data") ``` uv run infrahubctl object load objects/bootstrap --branch main ``` ## Testing[​](#testing "Direct link to Testing") The demo includes comprehensive tests: ### Unit tests[​](#unit-tests "Direct link to Unit tests") Located in `tests/unit/`, these test individual functions and classes: ``` def test_topology_creator(): """Test topology creator utility.""" creator = TopologyCreator(client, data) result = creator.create_devices() assert len(result) == expected_count ``` Run unit tests: ``` uv run pytest tests/unit/ ``` ### Integration tests[​](#integration-tests "Direct link to Integration tests") Integration tests validate end-to-end workflows by spinning up a complete Infrahub instance, loading schemas and data, and executing realistic user scenarios. These tests ensure that both the demo-dc code and the Infrahub platform work correctly together. #### How integration tests work[​](#how-integration-tests-work "Direct link to How integration tests work") Integration tests use **infrahub-testcontainer**, a Python package that provides Docker-based test fixtures. This approach: 1. **Spins up Infrahub in CI/CD** - Launches a full Infrahub instance (server, database, cache) in Docker containers 2. **Loads schemas** - Installs all schema definitions from `schemas/` 3. **Loads bootstrap data** - Populates initial data from `objects/bootstrap/` 4. **Adds repository** - Registers the demo repository for generators and transforms 5. **Executes workflows** - Runs complete user scenarios (create branch, load design, run generator, create proposed change, merge) 6. **Validates results** - Verifies that all expected objects and configurations were created This approach mirrors exactly what an end user would do, providing high confidence that the system works as documented. #### Example integration test workflow[​](#example-integration-test-workflow "Direct link to Example integration test workflow") The main integration test in `tests/integration/test_workflow.py` validates the complete DC-3 deployment workflow: ``` class TestDCWorkflow(TestInfrahubDockerWithClient): """Test the complete DC-3 workflow from the demo.""" async def test_01_schema_load(self, client_main): """Load all schemas into Infrahub.""" # Executes: infrahubctl schema load schemas pass async def test_02_bootstrap_load(self, client_main): """Load bootstrap data.""" # Executes: infrahubctl object load objects/bootstrap pass def test_05_repository_add(self, client_main): """Add the demo repository.""" # Executes: infrahubctl repository add DEMO ... pass def test_06_create_branch(self, client_main, default_branch): """Create a new branch for the DC-3 deployment.""" # Executes: infrahubctl branch create add-dc3 pass async def test_07_load_dc3_design(self, client_main, default_branch): """Load DC-3 design data onto the branch.""" # Executes: infrahubctl object load objects/dc/dc-arista-s.yml --branch add-dc3 pass async def test_09_run_generator(self, async_client_main, default_branch): """Run the create_dc generator for DC-3.""" # Triggers generator via GraphQL mutation # Waits for completion (up to 30 minutes for complex topologies) pass async def test_10_verify_devices_created(self, async_client_main, default_branch): """Verify that devices were created by the generator.""" devices = await client.all(kind="DcimDevice") assert devices, "No devices found after generator run" pass def test_11_create_diff(self, client_main, default_branch): """Create a diff between the branch and main.""" pass async def test_12_create_proposed_change(self, async_client_main, default_branch): """Create a proposed change for the branch.""" # Creates PC via GraphQL mutation # Waits for checks to run pass def test_13_merge_proposed_change(self, client_main, default_branch): """Merge the proposed change.""" # Merges PC to main branch pass async def test_14_verify_merge_to_main(self, async_client_main): """Verify that DC-3 and devices exist in main branch.""" dc3_main = await client.get(kind="TopologyDataCenter", name__value="DC-3") assert dc3_main, "DC-3 not found in main branch after merge" pass ``` Each test method represents one step in the workflow, ensuring that the entire process works correctly from start to finish. #### Running integration tests[​](#running-integration-tests "Direct link to Running integration tests") Run all integration tests: ``` uv run pytest tests/integration/ ``` Run a specific test: ``` uv run pytest tests/integration/test_workflow.py::TestDCWorkflow::test_09_run_generator -v ``` Watch test execution with verbose output: ``` uv run pytest tests/integration/ -vv -s ``` #### Benefits for OpsMill and customers[​](#benefits-for-opsmill-and-customers "Direct link to Benefits for OpsMill and customers") Integration tests provide critical value for both OpsMill (the company behind Infrahub) and customers using this bundle: ##### Testing new Infrahub versions[​](#testing-new-infrahub-versions "Direct link to Testing new Infrahub versions") When OpsMill releases a new version of Infrahub, the integration tests verify backward compatibility: ``` # Test with Infrahub 1.0.0 INFRAHUB_VERSION=1.0.0 uv run pytest tests/integration/ # Test with Infrahub 1.1.0 INFRAHUB_VERSION=1.1.0 uv run pytest tests/integration/ ``` If tests pass, the new Infrahub version is compatible with existing workflows. If tests fail, OpsMill knows exactly which workflows broke and can fix regressions before release. ##### Validating customer deployments[​](#validating-customer-deployments "Direct link to Validating customer deployments") Customers can run integration tests to verify their environment before deploying changes: * **Before upgrading Infrahub** - Run tests to ensure the new version won't break existing generators, transforms, or checks * **After customizing the bundle** - Run tests to ensure modifications didn't break core functionality * **In CI/CD pipelines** - Automatically test every code change before merging ##### Continuous integration[​](#continuous-integration "Direct link to Continuous integration") Integration tests run automatically in GitHub Actions on every commit and pull request: ``` name: Integration Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Run integration tests run: uv run pytest tests/integration/ ``` This catches breaking changes early, before they reach production. ##### Regression prevention[​](#regression-prevention "Direct link to Regression prevention") As bugs are discovered and fixed, new integration tests are added to prevent regressions: 1. Bug discovered: "Generator fails when creating more than 10 devices" 2. Fix applied to generator code 3. Integration test added to verify fix 4. Future changes can't reintroduce the bug without failing the test #### Test isolation and cleanup[​](#test-isolation-and-cleanup "Direct link to Test isolation and cleanup") Integration tests use Docker containers that are created fresh for each test run and destroyed afterward. This ensures: * **Clean state** - No leftover data from previous runs * **Reproducibility** - Tests produce the same results every time * **Parallelization** - Multiple test runs can execute simultaneously without conflicts The testcontainer automatically handles: * Starting Infrahub server, database, and cache containers * Waiting for services to be ready * Providing API clients pre-configured to connect to the test instance * Stopping and removing containers after tests complete #### Understanding test failures[​](#understanding-test-failures "Direct link to Understanding test failures") When an integration test fails, the error message indicates which step failed: ``` FAILED test_09_run_generator - Generator task finished with state FAILED FAILED test_14_verify_merge_to_main - DC-3 not found in main branch after merge ``` Enhanced logging in the tests shows: * Task states and completion status * GraphQL query results * API responses * Object counts and names This makes it straightforward to identify whether the issue is: * A schema problem (objects can't be created) * A generator bug (topology creation fails) * A transform issue (artifacts aren't generated) * A check failure (validation rejects valid configs) * An Infrahub platform bug (API errors, task failures) #### Writing new integration tests[​](#writing-new-integration-tests "Direct link to Writing new integration tests") When adding new features, create integration tests that: 1. **Test the happy path** - Verify the feature works when used correctly 2. **Test error handling** - Verify graceful failures for invalid inputs 3. **Test edge cases** - Verify behavior at boundaries (zero devices, maximum devices, etc.) Example integration test for a new generator: ``` async def test_new_generator_workflow(self, async_client_main): """Test the new topology generator.""" # Create branch branch_name = "test-new-generator" await client.branch.create(branch_name=branch_name) # Load design data self.execute_command( f"infrahubctl object load objects/new_topology.yml --branch {branch_name}" ) # Run generator definition = await client.get("CoreGeneratorDefinition", name__value="create_new_topology") topology = await client.get(kind="TopologyNewType", name__value="TEST-1", branch=branch_name) mutation = Mutation( mutation="CoreGeneratorDefinitionRun", input_data={"data": {"id": definition.id, "nodes": [topology.id]}}, query={"ok": None, "task": {"id": None}}, ) response = await client.execute_graphql(query=mutation.render()) task = await client.task.wait_for_completion( id=response["CoreGeneratorDefinitionRun"]["task"]["id"], timeout=600 ) # Verify results assert task.state == TaskState.COMPLETED devices = await client.all(kind="DcimDevice", branch=branch_name) assert len(devices) > 0, "Generator should create devices" ``` Integration tests are the highest-confidence verification that the bundle works correctly end-to-end. ### Code quality[​](#code-quality "Direct link to Code quality") The project enforces code quality with: ``` # Type checking uv run mypy . # Linting uv run ruff check . # Formatting uv run ruff format . # All checks uv run invoke validate ``` ## Development workflow[​](#development-workflow "Direct link to Development workflow") ### Setting up for development[​](#setting-up-for-development "Direct link to Setting up for development") ``` # Clone repository git clone https://github.com/opsmill/infrahub-demo-dc.git cd infrahub-demo-dc # Install dependencies uv sync # Start Infrahub uv run invoke start # Optional: Enable Service Catalog in .env echo "INFRAHUB_SERVICE_CATALOG=true" >> .env uv run invoke restart # Load bootstrap data uv run invoke bootstrap ``` ### Making changes[​](#making-changes "Direct link to Making changes") 1. **Create a feature branch** in Git 2. **Modify code** (generators, transforms, checks, schemas, Service Catalog) 3. **Add tests** for new functionality 4. **Run quality checks** (`uv run invoke validate`) 5. **Test locally** in Infrahub * For Service Catalog changes: use `uv run invoke start --rebuild` 6. **Commit changes** with descriptive messages 7. **Create pull request** for review ### Adding a new generator[​](#adding-a-new-generator "Direct link to Adding a new generator") 1. Create Python file in `generators/` 2. Implement `InfrahubGenerator` class 3. Register in `.infrahub.yml` under `generator_definitions` 4. Create associated GraphQL query in `queries/` 5. Add unit tests 6. Test manually in Infrahub ### Adding a new transform[​](#adding-a-new-transform "Direct link to Adding a new transform") 1. Create Python file in `transforms/` 2. Implement `InfrahubTransform` class 3. Create Jinja2 template in `templates/` 4. Register in `.infrahub.yml` under `python_transforms` and `artifact_definitions` 5. Create GraphQL query in `queries/` 6. Add unit tests 7. Test artifact generation ### Adding a new check[​](#adding-a-new-check "Direct link to Adding a new check") 1. Create Python file in `checks/` 2. Implement `InfrahubCheck` class 3. Register in `.infrahub.yml` under `check_definitions` 4. Create GraphQL query in `queries/` 5. Add unit tests 6. Test in proposed change workflow ### Service catalog development[​](#service-catalog-development "Direct link to Service catalog development") The Service Catalog is a Streamlit application that runs in a Docker container. When making changes to the Service Catalog code, you need to rebuild the container image. #### Making changes to the service catalog[​](#making-changes-to-the-service-catalog "Direct link to Making changes to the service catalog") 1. **Edit Service Catalog code** in `service_catalog/`: * `Home.py` - Main landing page * `pages/1_Create_DC.py` - DC creation form * `utils/` - Utility modules (api.py, config.py, ui.py) 2. **Rebuild and restart** the Service Catalog container: ``` uv run invoke start --rebuild ``` The `--rebuild` flag forces Docker to rebuild the Service Catalog image with your code changes before starting the containers. #### When to use --rebuild[​](#when-to-use---rebuild "Direct link to When to use --rebuild") Use the `--rebuild` flag when you modify: * Streamlit page files (`Home.py`, `pages/*.py`) * Service Catalog utilities (`service_catalog/utils/`) * Service Catalog dependencies (if you modify `service_catalog/requirements.txt`) * Service Catalog Dockerfile #### Testing service catalog changes[​](#testing-service-catalog-changes "Direct link to Testing service catalog changes") 1. Make your code changes in `service_catalog/` 2. Rebuild and start with `uv run invoke start --rebuild` 3. Access the Service Catalog at `http://localhost:8501` 4. Test your changes in the web interface 5. Check logs for errors: ``` docker logs infrahub-demo-dc-service-catalog-1 ``` #### Service catalog environment variables[​](#service-catalog-environment-variables "Direct link to Service catalog environment variables") Configure the Service Catalog behavior via `.env`: ``` INFRAHUB_SERVICE_CATALOG=true # Enable the service catalog DEFAULT_BRANCH=main # Default branch to show GENERATOR_WAIT_TIME=60 # Seconds to wait for generator API_TIMEOUT=30 # API request timeout API_RETRY_COUNT=3 # Number of API retries ``` Changes to environment variables do not require `--rebuild`, just restart: ``` uv run invoke restart ``` ## Extending schemas[​](#extending-schemas "Direct link to Extending schemas") ### Adding new attributes[​](#adding-new-attributes "Direct link to Adding new attributes") ``` nodes: - name: GenericDevice namespace: Dcim attributes: - name: serial_number # New attribute kind: Text optional: true unique: true ``` ### Adding new relationships[​](#adding-new-relationships "Direct link to Adding new relationships") ``` relationships: - name: backup_device # New relationship peer: DcimDevice cardinality: one optional: true description: "Backup device for redundancy" ``` ### Creating new node types[​](#creating-new-node-types "Direct link to Creating new node types") ``` nodes: - name: Router # New node type namespace: Dcim inherit_from: - DcimDevice attributes: - name: routing_instance kind: Text optional: false ``` After modifying schemas, reload them: ``` uv run infrahubctl schema load schemas --branch main ``` ## Common development tasks[​](#common-development-tasks "Direct link to Common development tasks") ### Debugging generators[​](#debugging-generators "Direct link to Debugging generators") Add logging to see execution flow: ``` import logging logger = logging.getLogger(__name__) class MyGenerator(InfrahubGenerator): async def generate(self, data: dict) -> None: logger.info(f"Processing topology: {data}") # ... generator logic ``` ### Testing transforms locally[​](#testing-transforms-locally "Direct link to Testing transforms locally") ``` # Create test data test_data = { "DcimDevice": { "edges": [{"node": {"name": {"value": "spine1"}}}] } } # Initialize transform transform = SpineTransform(client=client) # Run transform result = await transform.transform(test_data) print(result) ``` ### Validating templates[​](#validating-templates "Direct link to Validating templates") Use Jinja2 directly to test templates: ``` from jinja2 import Template template = Template(open("templates/spine.j2").read()) config = template.render(hostname="spine1", interfaces=[...]) print(config) ``` ## Additional resources[​](#additional-resources "Direct link to Additional resources") * **Infrahub documentation**: [docs.infrahub.app/](https://docs.infrahub.app/) --- # Using Infrahub Enterprise This guide explains how to configure the demo environment to use Infrahub Enterprise edition instead of the Community edition. Enterprise edition provides additional features including increased performance, enhanced security, and enterprise support. ## Understanding the editions[​](#understanding-the-editions "Direct link to Understanding the editions") Infrahub is available in two editions: * **Community edition** - The standard open-source version with core functionality * **Enterprise edition** - The commercial version with additional enterprise features The demo environment supports both editions and can be switched between them with a single configuration change. ## Configuration[​](#configuration "Direct link to Configuration") Edition selection is controlled by environment variables in the `.env` file at the root of the demo repository. The key variable is `INFRAHUB_ENTERPRISE`: ``` # Community edition (default) export INFRAHUB_ENTERPRISE="false" # Enterprise edition export INFRAHUB_ENTERPRISE="true" ``` ### Environment variables reference[​](#environment-variables-reference "Direct link to Environment variables reference") | Variable | Default | Description | | --------------------- | ----------------------- | ------------------------------------------------------------------- | | `INFRAHUB_ENTERPRISE` | `false` | Set to `true` to use Enterprise edition | | `INFRAHUB_VERSION` | `stable` | Infrahub version to use (for example, `1.5.1`, `stable`, `develop`) | | `INFRAHUB_ADDRESS` | `http://localhost:8000` | Infrahub API address | | `INFRAHUB_API_TOKEN` | (demo token) | API authentication token | ## Switching to Enterprise edition[​](#switching-to-enterprise-edition "Direct link to Switching to Enterprise edition") To switch from Community to Enterprise edition: ### Step 1: update the configuration[​](#step-1-update-the-configuration "Direct link to Step 1: update the configuration") Edit the `.env` file in the root of the demo repository: ``` # Change this line from "false" to "true" export INFRAHUB_ENTERPRISE="true" ``` ### Step 2: source the environment file[​](#step-2-source-the-environment-file "Direct link to Step 2: source the environment file") For the changes to take effect, source the environment file: ``` source .env ``` ### Step 3: verify the configuration[​](#step-3-verify-the-configuration "Direct link to Step 3: verify the configuration") Check that the Enterprise edition will be used: ``` uv run invoke info ``` You should see output similar to: ``` Infrahub Edition: Enterprise Version: 1.5.1 Command: curl -s https://infrahub.opsmill.io/enterprise/1.5.1 | ... ``` ### Step 4: restart Infrahub[​](#step-4-restart-infrahub "Direct link to Step 4: restart Infrahub") To apply the change, destroy the existing environment and start fresh: ``` # Stop and remove all containers and volumes uv run invoke destroy # Start with Enterprise edition uv run invoke start ``` tip The `destroy` command removes all containers and data volumes. If you have data you want to preserve, consider exporting it first using `infrahubctl`. ### Step 5: reload your data[​](#step-5-reload-your-data "Direct link to Step 5: reload your data") After switching editions, you'll need to reload your schemas and data: ``` uv run invoke bootstrap ``` ## Switching back to Community edition[​](#switching-back-to-community-edition "Direct link to Switching back to Community edition") To switch from Enterprise back to Community edition, follow the same process but set `INFRAHUB_ENTERPRISE="false"`: ``` # Edit .env and change to "false" source .env # Verify configuration uv run invoke info # Restart uv run invoke destroy uv run invoke start ``` ## How it works[​](#how-it-works "Direct link to How it works") When you run `uv run invoke start`, the system constructs the appropriate Docker Compose command based on your configuration: **Community edition URL pattern:** ``` https://infrahub.opsmill.io/{VERSION} ``` **Enterprise edition URL pattern:** ``` https://infrahub.opsmill.io/enterprise/{VERSION} ``` The invoke tasks in `tasks.py` automatically detect the `INFRAHUB_ENTERPRISE` setting and use the correct URL to download the appropriate Docker Compose configuration. ## Checking your current edition[​](#checking-your-current-edition "Direct link to Checking your current edition") At any time, you can check which edition you're configured to use: ``` source .env && uv run invoke info ``` This displays: * The edition (Community or Enterprise) * The version being used * The full Docker Compose command ## Enterprise features[​](#enterprise-features "Direct link to Enterprise features") Enterprise edition includes additional capabilities not available in Community edition. For details on specific enterprise features, refer to the [Infrahub documentation](https://docs.infrahub.app). info Enterprise edition requires valid licensing for production usage. Contact OpsMill for enterprise licensing information. ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Changes not taking effect[​](#changes-not-taking-effect "Direct link to Changes not taking effect") If you change the `INFRAHUB_ENTERPRISE` setting but the wrong edition starts: 1. Ensure you sourced the `.env` file: `source .env` 2. Verify the setting with `uv run invoke info` 3. Make sure you destroyed the old environment before starting: `uv run invoke destroy` ### Environment file not found[​](#environment-file-not-found "Direct link to Environment file not found") If you get errors about missing environment variables: 1. Ensure the `.env` file exists in the root of the demo repository 2. Make sure you're running commands from the demo repository root 3. Source the file explicitly: `source .env` ## Best practices[​](#best-practices "Direct link to Best practices") * **Use explicit versions** - Instead of `stable`, specify exact versions like `1.5.1` for reproducible environments * **Document your choice** - Note which edition you're using in project documentation * **Test on Community first** - Validate your schemas and generators on Community edition before deploying to Enterprise * **Keep configurations in sync** - If working in a team, ensure everyone uses the same edition for consistency ## Additional resources[​](#additional-resources "Direct link to Additional resources") * [Infrahub documentation](https://docs.infrahub.app) * [OpsMill website](https://opsmill.com) * [Enterprise edition details](https://opsmill.com/pricing) --- # Installation guide This guide walks you through setting up the Infrahub demo environment on your local system. By the end, you'll have a fully functional Infrahub instance with pre-loaded schemas, data, and generators ready to explore. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before you begin, ensure you have the following installed on your system: ### Required software[​](#required-software "Direct link to Required software") * **Python 3.11 or 3.12** - The demo requires a recent Python version * **[uv](https://github.com/astral-sh/uv)** - Fast Python package manager for dependency management * **Docker** - For running Infrahub containers and optionally Containerlab (or equivalent such as OrbStack) * **Git** - For cloning the repository ### System requirements[​](#system-requirements "Direct link to System requirements") * **Operating system**: macOS, Linux, or Windows (with WSL2) * **Memory**: At least 12GB RAM (16GB recommended) * **Disk space**: At least 10GB free space * **Network**: Internet connection for downloading dependencies and Docker images ## Step 1: install uv[​](#step-1-install-uv "Direct link to Step 1: install uv") If you don't have `uv` installed, follow the installation instructions for your operating system: ``` # macOS and Linux curl -LsSf https://astral.sh/uv/install.sh | sh # Windows (PowerShell) powershell -c "irm https://astral.sh/uv/install.ps1 | iex" ``` Verify the installation: ``` uv --version ``` ## Step 2: clone the repository[​](#step-2-clone-the-repository "Direct link to Step 2: clone the repository") Clone the demo repository to your local machine: ``` git clone https://github.com/opsmill/infrahub-demo-dc.git cd infrahub-demo-dc ``` ## Step 3: install Python dependencies[​](#step-3-install-python-dependencies "Direct link to Step 3: install Python dependencies") Use `uv` to install all required Python packages: ``` uv sync ``` This creates a virtual environment and installs all necessary packages including the Infrahub SDK. ## Step 4: set up environment variables[​](#step-4-set-up-environment-variables "Direct link to Step 4: set up environment variables") Configure environment variables for connecting to Infrahub and customizing the demo environment. Copy the example file and customize as needed: ``` cp .env.example .env ``` Then edit the `.env` file or export the variables directly in your shell. ### Core Infrahub variables[​](#core-infrahub-variables "Direct link to Core Infrahub variables") These variables configure connectivity to the Infrahub API: ``` # Infrahub API server address # Used by CLI tools and local scripts to connect to Infrahub # Default: http://localhost:8000 INFRAHUB_ADDRESS=http://localhost:8000 # Infrahub API authentication token # Generate a token in the Infrahub UI under Settings > API Tokens # For demo purposes, use the provided token below INFRAHUB_API_TOKEN=06438eb2-8019-4776-878c-0941b1f1d1ec # Infrahub UI URL for browser links # Used by the Service Catalog to generate clickable links to Infrahub UI pages # Should be the URL accessible from your browser # Default: http://localhost:8000 INFRAHUB_UI_URL=http://localhost:8000 ``` info The API token `06438eb2-8019-4776-878c-0941b1f1d1ec` is a demo token for local development only. Do not use this in production environments. Generate a new token in the Infrahub UI under Settings > API Tokens for production use. ### Infrahub version and features[​](#infrahub-version-and-features "Direct link to Infrahub version and features") Control which version of Infrahub to run and which features to enable: ``` # Infrahub version to use # Specify the version of Infrahub to run (for example, 1.5.1, stable, latest) # Default: 1.5.1 INFRAHUB_VERSION=1.5.1 # Enable Infrahub Enterprise features # Set to "true" to enable enterprise features, "false" for community edition # Default: false INFRAHUB_ENTERPRISE=false ``` ### Service catalog configuration[​](#service-catalog-configuration "Direct link to Service catalog configuration") Enable and configure the optional Service Catalog Streamlit application: ``` # Enable the Service Catalog Streamlit application # Set to "true" to start the service catalog container with 'uv run invoke start' # Set to "false" (default) to run only the core Infrahub services # The service catalog provides a web UI for managing data center deployments INFRAHUB_SERVICE_CATALOG=false # Default branch to use when the service catalog application starts # Default: main DEFAULT_BRANCH=main # Time to wait (in seconds) for the Infrahub generator to complete after creating a DC # Increase this value if your generators take longer to complete # Default: 60 GENERATOR_WAIT_TIME=60 # API request timeout in seconds # Maximum time to wait for API requests to complete # Default: 30 API_TIMEOUT=30 # Number of retry attempts for failed API requests # Default: 3 API_RETRY_COUNT=3 ``` ### Managing environment variables with direnv[​](#managing-environment-variables-with-direnv "Direct link to Managing environment variables with direnv") [direnv](https://direnv.net/) is a helpful tool for automatically loading environment variables from `.env` files when you enter the project directory. While not required, it streamlines the development workflow. To use direnv: 1. Install direnv following the instructions at [direnv.net](https://direnv.net/docs/installation.html) 2. Create a `.envrc` file in the project root: ``` dotenv ``` 3. Allow direnv to load the file: ``` direnv allow ``` Now your environment variables will load automatically when you `cd` into the project directory. tip Other alternatives for managing environment variables include: * Using your shell's profile file (`.bashrc`, `.zshrc`) * Docker Compose environment files (`docker-compose.override.yml`) * Shell scripts that source the `.env` file * IDE/editor plugins for environment variable management ## Step 5: start Infrahub[​](#step-5-start-infrahub "Direct link to Step 5: start Infrahub") Start the Infrahub containers using the included invoke task: ``` uv run invoke start ``` This command downloads and starts all required Docker containers. The first run may take several minutes as Docker images are pulled. Wait for all containers to be healthy. You can check the status with: ``` docker ps ``` You should see containers for: * `infrahub-server` - Main Infrahub API server * `infrahub-database` - PostgreSQL database * `infrahub-cache` - Redis cache * `infrahub-message-queue` - RabbitMQ message broker * `infrahub-git` - Git repository server ## Step 6: access the Infrahub web interface[​](#step-6-access-the-infrahub-web-interface "Direct link to Step 6: access the Infrahub web interface") Once the containers are running, open your web browser and navigate to: ``` http://localhost:8000 ``` You should see the Infrahub login screen. Use the default credentials: * **Username**: `admin` * **Password**: `infrahub` ### Demo user accounts[​](#demo-user-accounts "Direct link to Demo user accounts") After running the bootstrap process (Step 7), additional user accounts are created to demonstrate role-based access control. These accounts provide different levels of access to Infrahub: #### Emma - read-only user[​](#emma---read-only-user "Direct link to Emma - read-only user") * **Username**: `emma` * **Password**: `emma123` * **Access level**: Read-only * **Permissions**: * View all objects across all branches * Cannot create, update, or delete any objects * Cannot manage schemas or merge proposed changes **Use this account to**: * Demonstrate read-only access for auditors or viewers * Show how Infrahub prevents unauthorized modifications * Test queries and views without risk of changing data #### Otto - schema reviewer[​](#otto---schema-reviewer "Direct link to Otto - schema reviewer") * **Username**: `otto` * **Password**: `otto123` * **Access level**: Schema management and full object access * **Permissions**: * Manage schemas (create, update, delete) * Review and approve proposed changes * Full CRUD (create, read, update, delete) operations on all objects **Use this account to**: * Demonstrate schema management workflows * Show the proposed change review process * Test advanced configurations with full object permissions info These demo accounts are created automatically when you run `uv run invoke bootstrap` (Step 7). The account creation script is located at `scripts/create_users_roles.py`. Security notice The passwords for these demo accounts are intentionally weak for demonstration purposes. In production environments, use strong passwords and integrate with your organization's authentication system (LDAP, SAML, or OIDC). ## Step 7: bootstrap the demo data[​](#step-7-bootstrap-the-demo-data "Direct link to Step 7: bootstrap the demo data") Now that Infrahub is running, load the demo schemas and data. You can use the automated bootstrap script or run the steps manually. * Automated (Recommended) * Manual Run the bootstrap command to load everything in one step: ``` uv run invoke bootstrap ``` This command performs all the steps in the Manual tab automatically. Run each step individually: ``` # 1. Load schemas uv run infrahubctl schema load schemas # 2. Load menu definitions uv run infrahubctl menu load menus/menu-full.yml # 3. Load bootstrap data (locations, device types, platforms) uv run infrahubctl object load objects/bootstrap # 4. Load security data (optional) uv run infrahubctl object load objects/security/ # 5. Populate security relationships (optional, required if you loaded security data) uv run python scripts/populate_security_relationships.py # 6. Add the demo repository uv run infrahubctl repository add DEMO https://github.com/opsmill/infrahub-demo-dc.git --read-only --ref main # 7. Load event actions (optional, enables automatic generator execution) uv run infrahubctl object load objects/events/ ``` tip Loading schemas and data can take several minutes. Watch the console output to monitor progress. ## Step 8: verify the installation[​](#step-8-verify-the-installation "Direct link to Step 8: verify the installation") To verify that everything is set up correctly: 1. **Check the web interface** - Navigate to `http://localhost:8000` and log in 2. **Browse the schema** - Go to **Schema** in the left sidebar and verify that you see schemas like `DcimDevice`, `IpamIPPrefix`, `TopologyDataCenter` 3. **View bootstrap data** - Navigate to **Infrastructure → Locations** and verify you see sample locations 4. **Check generators** - Go to **Actions → Generator Definitions** and verify you see `create_dc` and `create_pop` generators ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Port conflicts[​](#port-conflicts "Direct link to Port conflicts") If port 8000 is already in use, you'll see an error when starting Infrahub. To resolve this: 1. Stop the conflicting service using port 8000 2. Or modify the port mapping in your Docker Compose configuration ### Dependency issues[​](#dependency-issues "Direct link to Dependency issues") If you encounter Python dependency errors: ``` # Clear the virtual environment and reinstall rm -rf .venv uv sync ``` ### Docker issues[​](#docker-issues "Direct link to Docker issues") If Docker containers fail to start: ``` # Stop and remove all containers uv run invoke destroy # Restart uv run invoke start ``` ### Schema loading fails[​](#schema-loading-fails "Direct link to Schema loading fails") If schema loading fails with validation errors, ensure you're using a compatible version of Infrahub. This demo is tested with Infrahub 1.5.1 and later. ## Next steps[​](#next-steps "Direct link to Next steps") Now that you have Infrahub running with the demo data loaded, you're ready to: * Follow the **[user walkthrough](/demo-dc/user-walkthrough.md)** to create your first data center topology * Explore the **[concepts](/demo-dc/concepts.md)** to understand the architecture * Review the **[developer guide](/demo-dc/developer-guide.md)** to understand how the demo works under the hood ## Managing your environment[​](#managing-your-environment "Direct link to Managing your environment") ### Stopping Infrahub[​](#stopping-infrahub "Direct link to Stopping Infrahub") To stop the containers without removing data: ``` uv run invoke stop ``` ### Destroying the environment[​](#destroying-the-environment "Direct link to Destroying the environment") To completely remove containers and volumes (fresh start): ``` uv run invoke destroy ``` ### Restarting a specific component[​](#restarting-a-specific-component "Direct link to Restarting a specific component") To restart a single container: ``` uv run invoke restart ``` For example, to restart the API server: ``` uv run invoke restart infrahub-server ``` --- # Working with security management This tutorial walks you through Infrahub's security management capabilities. You'll explore how security policies are modeled as structured data, how they relate to firewalls, and how Infrahub transforms security policy objects into vendor-specific firewall configurations. By the end of this tutorial, you'll understand how to manage firewall policies using Infrahub's security schema and generate deployment-ready configurations. ## What you'll accomplish[​](#what-youll-accomplish "Direct link to What you'll accomplish") In this tutorial, you will: 1. Explore the security management schema structure 2. Review the corp-firewall device and its security policy 3. Understand how security objects relate to each other 4. Generate firewall configuration from security policy data 5. Modify security rules and regenerate configurations ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before starting this tutorial, ensure you have: * Completed the [installation guide](/demo-dc/install.md) and have Infrahub running * Loaded the bootstrap data and security data (`uv run invoke bootstrap`) * Access to the Infrahub web interface at `http://localhost:8000` info The security data is loaded as part of the bootstrap process. If you haven't loaded it yet, run `uv run infrahubctl object load objects/security/` followed by `uv run python scripts/populate_security_relationships.py` to populate the many-to-many relationships. ## Understanding the demo security schema[​](#understanding-the-demo-security-schema "Direct link to Understanding the demo security schema") The demo security management schema in this repository provides an example data model for firewall policies, zones, services, and applications. The schema is organized into several categories of security objects that work together to define complete security policies. info As Infrahub provides the user a flexible schema, this specific security schema implementation serves as an example of what is possible, not a finished solution. The value of Infrahub is the ability to create a specific schema to the needs of your organization. ### Security schema architecture[​](#security-schema-architecture "Direct link to Security schema architecture") **Key relationships:** * **SecurityPolicy** contains multiple **SecurityPolicyRule** objects * **SecurityPolicyRule** references zones, address groups, service groups, applications, and profiles * **SecurityPolicy** is applied to one or more **SecurityFirewall** devices * **SecurityAddressGroup** aggregates IP addresses, prefixes, ranges, and FQDNs * **SecurityServiceGroup** aggregates services and service ranges This hierarchical model separates concerns (addresses, services, applications) and allows reuse of security objects across multiple policies and rules. ## Step 1: explore the corp-firewall device[​](#step-1-explore-the-corp-firewall-device "Direct link to Step 1: explore the corp-firewall device") Navigate to the corp-firewall device in the Infrahub web interface: 1. Go to `http://localhost:8000` 2. Navigate to **Device Management → Infrastructure → Firewalls** 3. Click on **corp-firewall** ### Device details[​](#device-details "Direct link to Device details") The corp-firewall is a Juniper SRX-1500 firewall with the following configuration: * **Device type**: SRX-1500 * **Platform**: Juniper JunOS * **Role**: edge\_firewall * **Location**: PAR-1 (Paris building) * **Status**: active ### Interfaces[​](#interfaces "Direct link to Interfaces") The firewall has several interfaces defined: * **fxp0** - Management interface * **ge-0/0/0** through **ge-0/0/3** - Leaf connection interfaces These interfaces connect the firewall to the network infrastructure and provide connectivity for security policy enforcement. ## Step 2: explore security management objects[​](#step-2-explore-security-management-objects "Direct link to Step 2: explore security management objects") The security policy for corp-firewall is built from objects organized under the **Security Management** menu in the Infrahub UI. ### Navigate security objects[​](#navigate-security-objects "Direct link to Navigate security objects") 1. In the left sidebar, find **Security Management** 2. Explore the following sections: #### Security zones[​](#security-zones "Direct link to Security zones") Go to **Security Management → Security Zones** Security zones represent trust boundaries: * **untrust** - External/untrusted networks (trust level: 0) * **trust** - Internal/trusted networks (trust level: 100) * **dmz** - Demilitarized zone (trust level: 50) Zones are used in policy rules to define traffic flow direction (for example, from untrust to dmz). #### Address objects[​](#address-objects "Direct link to Address objects") Go to **Security Management → Address Objects** You'll find several types of address objects: * **SecurityIPAddress** - Individual IP addresses (for example, web-server-01: 10.100.1.10) * **SecurityPrefix** - Network prefixes (for example, internal-network: 10.0.0.0/8) * **SecurityAddressGroup** - Groups of addresses (for example, web-servers, internet) Address groups simplify policy management by allowing you to reference multiple addresses with a single object. #### Service objects[​](#service-objects "Direct link to Service objects") Go to **Security Management → Service Objects** Service objects define network protocols and ports: * **SecurityService** - Individual services (for example, https: TCP/443, dns: UDP/53) * **SecurityServiceGroup** - Groups of services (for example, web-services containing http and https) Service groups make it straightforward to create rules that allow multiple related services. #### Applications[​](#applications "Direct link to Applications") Go to **Security Management → Applications** Application objects enable application-aware security: * **web-browsing** - HTTP/HTTPS web traffic * **ssl** - SSL/TLS encrypted traffic * **dns** - Domain name resolution Applications can be used in policy rules for more granular control than port-based services. #### Security policies[​](#security-policies "Direct link to Security policies") Go to **Security Management → Security Policies** You'll see **corporate-firewall-policy** which contains the rules applied to corp-firewall. Click on the policy to view its details including: * **Name**: corporate-firewall-policy * **Description**: Security policy for corporate edge firewall * **Firewalls**: corp-firewall (the policy is applied to this device) * **Rules**: One or more policy rules defining allowed/denied traffic #### Policy rules[​](#policy-rules "Direct link to Policy rules") Within the corporate-firewall-policy, you'll find policy rules like **allow-web-traffic**: * **Index**: 10 (lower numbers are processed first) * **Action**: permit * **Source zone**: untrust (internet) * **Destination zone**: dmz * **Source addresses**: internet (address group) * **Destination addresses**: web-servers (address group) * **Services**: web-services (service group containing http/https) * **Applications**: web-browsing * **Log**: true (log matching traffic) This rule allows internet users to access web servers in the DMZ using HTTP/HTTPS protocols. ## Step 3: understand the policy structure[​](#step-3-understand-the-policy-structure "Direct link to Step 3: understand the policy structure") The security policy demonstrates Infrahub's composable approach to policy management: ``` SecurityPolicy (corporate-firewall-policy) │ ├─ Applied to: corp-firewall │ └─ Contains Rules: │ └─ PolicyRule (allow-web-traffic) ├─ Source Zone: untrust ├─ Destination Zone: dmz ├─ Source Addresses: internet (AddressGroup) │ └─ Contains: 0.0.0.0/0 (SecurityPrefix) ├─ Destination Addresses: web-servers (AddressGroup) │ ├─ web-server-01 (SecurityIPAddress: 10.100.1.10) │ └─ web-server-02 (SecurityIPAddress: 10.100.1.20) ├─ Services: web-services (ServiceGroup) │ ├─ http (SecurityService: TCP/80) │ └─ https (SecurityService: TCP/443) ├─ Applications: web-browsing ├─ Action: permit └─ Log: true ``` **Key benefits of this structure:** * **Reusability** - Address groups, service groups, and applications can be reused across multiple rules * **Maintainability** - Update an address group once, and all rules using it automatically reflect the change * **Clarity** - Named objects make policies more readable than raw IP addresses and port numbers * **Validation** - Infrahub validates relationships ensuring policies reference valid objects ## Step 4: view the generated firewall configuration[​](#step-4-view-the-generated-firewall-configuration "Direct link to Step 4: view the generated firewall configuration") Infrahub transforms the security policy data into vendor-specific firewall configurations using templates and transforms. ### Access the firewall configuration artifact[​](#access-the-firewall-configuration-artifact "Direct link to Access the firewall configuration artifact") 1. While viewing **corp-firewall**, look for the **Artifacts** section or tab 2. You should see an artifact named **edge** or **firewall-configuration** 3. Click on the artifact to view the generated Juniper JunOS configuration ### Configuration sections[​](#configuration-sections "Direct link to Configuration sections") The generated configuration includes several sections: **Security zones:** ``` security { zones { security-zone untrust { interfaces { ge-0/0/0.0; } } security-zone dmz { interfaces { ge-0/0/1.0; } } security-zone trust { interfaces { ge-0/0/2.0; } } } } ``` **Address book entries:** ``` security { address-book { global { address web-server-01 10.100.1.10/32; address web-server-02 10.100.1.20/32; address-set web-servers { address web-server-01; address web-server-02; } } } } ``` **Security policies:** ``` security { policies { from-zone untrust to-zone dmz { policy allow-web-traffic { match { source-address internet; destination-address web-servers; application [ junos-http junos-https ]; } then { permit; log { session-init; session-close; } } } } } } ``` The configuration is generated from the structured security policy data using Jinja2 templates that understand Juniper JunOS syntax. ## Step 5: modify a security policy[​](#step-5-modify-a-security-policy "Direct link to Step 5: modify a security policy") Let's modify the security policy to add a new rule. ### Create a new policy rule[​](#create-a-new-policy-rule "Direct link to Create a new policy rule") 1. Navigate to **Security Management → Security Policy Rules** 2. Click **Add Security Policy Rule** 3. Fill in the form: * **Index**: 20 (processed after rule 10) * **Name**: allow-dns * **Policy**: corporate-firewall-policy * **Source Zone**: untrust * **Destination Zone**: dmz * **Source Addresses**: internet * **Destination Addresses**: web-servers * **Services**: (create or select a DNS service group) * **Action**: permit * **Log**: true 4. Click **Save** ### Regenerate the configuration[​](#regenerate-the-configuration "Direct link to Regenerate the configuration") After creating the rule: 1. Create a proposed change to review the impact 2. Navigate to **Proposed Changes** 3. Create a new proposed change comparing your branch to main 4. View the **Artifacts** tab to see the updated firewall configuration 5. The configuration should now include the new DNS rule tip Always use proposed changes when modifying security policies. This allows you to review the generated configuration changes before deploying to production firewalls. ## Step 6: understand the transformation process[​](#step-6-understand-the-transformation-process "Direct link to Step 6: understand the transformation process") The transformation from security policy objects to firewall configuration follows this flow: ``` Security Policy Data (Infrahub) ↓ GraphQL Query (fetch policy, rules, zones, addresses, services) ↓ Python Transform (transforms/edge.py) ↓ Jinja2 Template (templates/edge.j2) ↓ Juniper JunOS Configuration (artifact) ``` ### The transform[​](#the-transform "Direct link to The transform") The transform (`transforms/edge.py`) queries Infrahub for: * Device details * Security zones * Address objects and groups * Service objects and groups * Policy rules * Applications It then processes this data into a structure suitable for template rendering. ### The template[​](#the-template "Direct link to The template") The template (`templates/edge.j2`) contains Juniper JunOS-specific syntax and logic: * Iterates over zones to create zone configurations * Builds address books from address groups * Generates policy rules from SecurityPolicyRule objects * Maps Infrahub applications to Junos applications (for example, web-browsing → junos-http/junos-https) **Vendor flexibility:** The same security policy data could be transformed to different vendors by creating different templates: * Juniper JunOS (current implementation) * Cisco ASA * Palo Alto PAN-OS * Fortinet FortiGate This demonstrates Infrahub's vendor-neutral approach - the security policy is defined once and can be rendered for any supported platform. ## What you've learned[​](#what-youve-learned "Direct link to What you've learned") Congratulations! You've explored Infrahub's security management capabilities: * ✅ Understood the security schema architecture * ✅ Explored security zones, addresses, services, and applications * ✅ Reviewed the corp-firewall device and its security policy * ✅ Examined the relationship between policy objects * ✅ Viewed generated Juniper JunOS firewall configurations * ✅ Modified security policies and regenerated configurations * ✅ Understood the transformation process from data to configuration ## Advanced usage[​](#advanced-usage "Direct link to Advanced usage") ### Creating complex policies[​](#creating-complex-policies "Direct link to Creating complex policies") Build more sophisticated security policies by: * **Combining multiple address groups** in a single rule * **Using application objects** for layer 7 filtering * **Adding URL categories** for web filtering * **Applying security profiles** for IPS/antivirus scanning * **Creating time-based rules** with schedules ### Multi-firewall deployments[​](#multi-firewall-deployments "Direct link to Multi-firewall deployments") Apply the same security policy to multiple firewalls: 1. Create additional SecurityFirewall objects 2. Associate them with the same SecurityPolicy 3. Generate configurations for each firewall 4. Maintain consistency across your firewall fleet ### Policy validation[​](#policy-validation "Direct link to Policy validation") Use Infrahub's validation capabilities to ensure: * No overlapping rules with conflicting actions * All referenced objects exist (addresses, services, applications) * Required fields are populated * Trust levels between zones are logical ### Integration with CI/CD[​](#integration-with-cicd "Direct link to Integration with CI/CD") Automate security policy management: 1. Store security objects in Git repositories 2. Use proposed changes for peer review 3. Run validation checks in CI pipelines 4. Deploy approved configurations automatically ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Security objects not appearing[​](#security-objects-not-appearing "Direct link to Security objects not appearing") If you don't see security objects in the UI: 1. Verify security schemas are loaded: `uv run infrahubctl schema load schemas` 2. Load security data: `uv run infrahubctl object load objects/security/` 3. Populate relationships: `uv run python scripts/populate_security_relationships.py` 4. Refresh your browser ### Configuration not generating[​](#configuration-not-generating "Direct link to Configuration not generating") If firewall configurations aren't being generated: 1. Check that the SecurityPolicy is associated with the SecurityFirewall 2. Verify the transform and template are registered in `.infrahub.yml` 3. Look for errors in the Infrahub task logs 4. Ensure all referenced objects (zones, addresses, services) exist ### Many-to-many relationships missing[​](#many-to-many-relationships-missing "Direct link to Many-to-many relationships missing") If address groups or service groups appear empty: 1. Run the populate\_security\_relationships script 2. This script manually creates the many-to-many relationships that can't be loaded via YAML 3. Verify in the UI that groups now show their members ## Next steps[​](#next-steps "Direct link to Next steps") Now that you understand security management in Infrahub, you can: * **Model your organization's security policies** using the security schema * **Create vendor-neutral firewall policies** that can be rendered for any platform * **Implement change management workflows** using branches and proposed changes * **Integrate with automation tools** to deploy configurations automatically * **Extend the schema** with custom security objects specific to your needs For deeper understanding: * **[Understanding the concepts](/demo-dc/concepts.md)** - Learn about schema-driven data modeling * **[Developer guide](/demo-dc/developer-guide.md)** - Understand how transformations and templates work * **[Infrahub security schema](https://github.com/opsmill/infrahub-demo-dc/blob/main/schemas/extensions/security/security.yml)** - Review the complete schema definition --- # Using the service catalog This tutorial walks you through using Infrahub's Service Catalog web interface to manage data center and colocation center deployments. The Service Catalog provides a user-friendly web application built with Streamlit that simplifies infrastructure provisioning by automating branch creation, data loading, and generator execution. By the end of this tutorial, you'll understand how to enable the Service Catalog, navigate the interface, view existing infrastructure, and create new data centers through a guided form-based workflow. ## What you'll accomplish[​](#what-youll-accomplish "Direct link to What you'll accomplish") In this tutorial, you will: 1. Enable the Service Catalog container 2. Access the web interface 3. Switch between Infrahub branches 4. View existing data centers and colocation centers 5. Visualize rack layouts with device placement 6. Create a new data center using the web form 7. Understand the automated workflow behind the scenes ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before starting this tutorial, ensure you have: * Completed the [installation guide](/demo-dc/install.md) and have Infrahub running * Completed the [user walkthrough](/demo-dc/user-walkthrough.md) tutorial * Docker and Docker Compose installed and configured * Access to modify environment variables or Docker Compose configuration tip The Service Catalog is an optional component that simplifies infrastructure provisioning. It's particularly useful for teams who want a web-based interface for infrastructure requests. ## Step 1: enable the service catalog[​](#step-1-enable-the-service-catalog "Direct link to Step 1: enable the service catalog") The Service Catalog runs as a separate Docker container that can be enabled through environment variables or Docker Compose profiles. * Environment Variables (Recommended) * Docker Compose Profiles 1. Open your `.env` file (or create it from `.env.example`) 2. Set the Service Catalog variable to true: ``` INFRAHUB_SERVICE_CATALOG=true ``` 3. Restart Infrahub to apply the changes: ``` uv run invoke restart ``` Start Infrahub with the Service Catalog profile: ``` docker-compose --profile service-catalog up -d ``` ### Verify the container is running[​](#verify-the-container-is-running "Direct link to Verify the container is running") Check that the service-catalog container is running: ``` docker ps --filter "name=service-catalog" ``` You should see a container named `infrahub-demo-dc-service-catalog-1` or similar in the output. ## Step 2: access the service catalog[​](#step-2-access-the-service-catalog "Direct link to Step 2: access the service catalog") Once the container is running, access the Service Catalog web interface: 1. Open your web browser 2. Navigate to `http://localhost:8501` 3. The Service Catalog landing page should load The interface displays: * **Branch selector** - Dropdown to switch between Infrahub branches * **Data centers** - List of existing TopologyDataCenter objects * **Colocation centers** - List of existing TopologyColocationCenter objects * **Create new data center** - Button to access the DC creation form info If the page doesn't load, verify the container is running and check the logs with `docker logs infrahub-demo-dc-service-catalog-1`. ## Step 3: switch branches and view infrastructure[​](#step-3-switch-branches-and-view-infrastructure "Direct link to Step 3: switch branches and view infrastructure") The Service Catalog allows you to view infrastructure across different Infrahub branches. ### Switch branches[​](#switch-branches "Direct link to Switch branches") 1. Locate the **branch selector** dropdown at the top of the page 2. Click the dropdown to see all available branches 3. Select a branch (for example, main, add-dc3, etc.) 4. The page will refresh and display infrastructure from the selected branch ### View existing infrastructure[​](#view-existing-infrastructure "Direct link to View existing infrastructure") After selecting a branch, you'll see two lists: #### Data centers[​](#data-centers "Direct link to Data centers") Each data center card displays: * Data center name (for example, DC-1, DC-2, DC-3) * Location information * Link to view in Infrahub UI #### Colocation centers[​](#colocation-centers "Direct link to Colocation centers") Each colocation center card displays: * Colocation center name (for example, PAR-1, LON-1) * Location information * Link to view in Infrahub UI tip The lists automatically update when you switch branches, allowing you to see infrastructure in different states (main, proposed changes, feature branches). ## Step 4: visualize rack layouts[​](#step-4-visualize-rack-layouts "Direct link to Step 4: visualize rack layouts") The Service Catalog includes a rack visualization page that displays physical rack layouts with mounted devices, similar to NetBox's rack elevation diagrams. This feature helps you understand device placement, rack occupancy, and physical infrastructure organization. ### Access rack visualization[​](#access-rack-visualization "Direct link to Access rack visualization") 1. Navigate to the sidebar in the Service Catalog 2. Click **Rack Visualization** in the navigation menu 3. The rack visualization page will load ### Understanding the rack view[​](#understanding-the-rack-view "Direct link to Understanding the rack view") The rack visualization displays: * **Physical rack diagrams** - Visual representations of racks with numbered rack units (U1-U42) * **Mounted devices** - Colored rectangles showing device positions and heights * **Device information** - Names, types, and rack unit positions * **Role-based colors** - Visual differentiation by device role ### Select a location row[​](#select-a-location-row "Direct link to Select a location row") Before racks appear, you need to select a location row: 1. Use the **Select Location Row** dropdown at the top of the page 2. Choose a row (for example, Row-1 in Pod-1) 3. All racks in that row will display in a grid layout info Location rows are created automatically by the DC generator. Each data center has a Pod (for example, Pod-1) containing a Row (for example, Row-1) with multiple racks. ### Reading rack diagrams[​](#reading-rack-diagrams "Direct link to Reading rack diagrams") Each rack diagram shows: #### Rack units[​](#rack-units "Direct link to Rack units") * Numbered from bottom (U1) to top (U42) * Each unit represents 1.75 inches of vertical space * Devices mount at specific U positions #### Device rectangles[​](#device-rectangles "Direct link to Device rectangles") Devices appear as colored rectangles with: * **Height** - 1U, 2U, 4U devices shown proportionally * **Position** - Starting U position labeled (for example, U42, U41) * **Name or type** - Text showing device identifier * **Role color** - Background color indicating device function For 1U devices, only the device name or type is shown. For multi-unit devices (2U+), both the primary label and secondary information are displayed. #### Color coding by role[​](#color-coding-by-role "Direct link to Color coding by role") The rack visualization uses role-based colors to help identify device types at a glance: * **Green** - Leaf switches (access layer) * **Blue** - Spine switches (aggregation layer) * **Purple** - Border leaf switches (edge connectivity) * **Orange** - Console servers (out-of-band management) * **Brown** - OOB switches (management network) * **Cyan** - Edge routers (WAN connectivity) * **Red** - Firewalls (security devices) A legend at the bottom of the page explains the color mappings. ### Toggle device labels[​](#toggle-device-labels "Direct link to Toggle device labels") You can change what information displays on device rectangles: 1. Locate the **Display Options** section in the sidebar 2. Use the **Device Label** dropdown 3. Choose between: * **Hostname** (default) - Shows device name (for example, dc-3-leaf-01) * **Device Type** - Shows hardware model (for example, Arista DCS-7280SR-48C6) For multi-unit devices, the secondary label shows the alternate information. For instance, if you select "Device Type", the secondary label shows the hostname. ### Device placement logic[​](#device-placement-logic "Direct link to Device placement logic") The DC generator assigns devices to racks based on predefined rules: #### Leaf devices[​](#leaf-devices "Direct link to Leaf devices") * Assigned to rack numbers matching their device number * For example, dc-3-leaf-01 → Rack-1, dc-3-leaf-02 → Rack-2 * Positioned at the top of the rack (U42 for 1U devices) #### Infrastructure devices[​](#infrastructure-devices "Direct link to Infrastructure devices") Spine switches, border leafs, console servers, and OOB devices are assigned to middle racks: * **Spine switches** - Positioned below leaf devices * **Border leaf switches** - Positioned below spines * **Console servers** - Positioned below border leafs * **OOB switches** - Positioned at the bottom This placement strategy ensures infrastructure devices are centrally located for optimal cabling. ### Clickable device links[​](#clickable-device-links "Direct link to Clickable device links") Each device in the rack diagram is clickable: 1. Click any device rectangle 2. A new browser tab opens showing the device in Infrahub UI 3. View full device details, interfaces, and relationships The link preserves the current branch context, so you see the device as it exists in the selected branch. ### Example workflow[​](#example-workflow "Direct link to Example workflow") Here's how to use rack visualization after creating a data center: 1. Create a new DC using the Service Catalog form (for example, DC-3) 2. Wait for the generator to complete 3. Navigate to **Rack Visualization** 4. Switch to the branch containing your DC (for example, add-dc3) 5. Select the location row (for example, Row-1) 6. View the generated racks with mounted devices 7. Verify device placement matches your design 8. Click devices to inspect details in Infrahub tip Rack visualization is particularly useful during proposed change review. Switch to the branch, view rack layouts, and verify devices are positioned correctly before merging to main. ## Step 5: create a new data center[​](#step-5-create-a-new-data-center "Direct link to Step 5: create a new data center") The Service Catalog provides a guided form for creating new data centers. ### Access the creation form[​](#access-the-creation-form "Direct link to Access the creation form") 1. Click the **Create New Data Center** button 2. The form page will load with several input fields ### Fill in the form[​](#fill-in-the-form "Direct link to Fill in the form") Complete each field in the form: #### 1. Data center name[​](#1-data-center-name "Direct link to 1. Data center name") * Enter a unique name for your data center (for example, DC-6) * This will be used in the branch name and as the DC identifier #### 2. Location[​](#2-location "Direct link to 2. Location") * Select a location from the dropdown * Options include existing LocationBuilding objects (for example, PAR-1, LON-1) * The location determines where the data center will be deployed #### 3. Provider[​](#3-provider "Direct link to 3. Provider") * Select a provider from the dropdown * Options include existing ProviderNetworkProvider objects * The provider supplies connectivity and services #### 4. Design[​](#4-design "Direct link to 4. Design") * Select a design template from the dropdown * Options include predefined data center architectures * Different designs support different vendors and topologies #### 5. Management subnet[​](#5-management-subnet "Direct link to 5. Management subnet") * Select an IP prefix for management interfaces * Used for out-of-band device management * Choose an available /24 or appropriate size #### 6. Underlay subnet[​](#6-underlay-subnet "Direct link to 6. Underlay subnet") * Select an IP prefix for underlay routing * Used for spine-leaf physical connectivity * Typically a /24 or /16 depending on DC size #### 7. Overlay subnet[​](#7-overlay-subnet "Direct link to 7. Overlay subnet") * Select an IP prefix for overlay services * Used for VXLAN and EVPN addressing * Usually a /16 or larger for tenant networks ### Submit the request[​](#submit-the-request "Direct link to Submit the request") 1. Review all form fields for accuracy 2. Click the **Submit** button 3. The Service Catalog will display a progress indicator ## Step 6: understand the behind-the-scenes workflow[​](#step-6-understand-the-behind-the-scenes-workflow "Direct link to Step 6: understand the behind-the-scenes workflow") When you submit the form, the Service Catalog executes an automated workflow that: ### 1. Creates a branch[​](#1-creates-a-branch "Direct link to 1. Creates a branch") The Service Catalog creates a new branch in Infrahub with the naming pattern `add-{dc_name}`: ``` Branch name: add-dc6 (if you named your DC "DC-6") ``` This branch isolates your new infrastructure from the main branch, allowing review before merging. ### 2. Loads data to the branch[​](#2-loads-data-to-the-branch "Direct link to 2. Loads data to the branch") The Service Catalog constructs a DesignTopologyDesign YAML object with your form inputs and loads it to the new branch: ``` nodes: - kind: DesignTopologyDesign data: name: DC-6 location: PAR-1 provider: provider-1 design_template: dc-design-template-1 mgmt_prefix: 10.0.6.0/24 underlay_prefix: 192.168.6.0/24 overlay_prefix: 172.16.0.0/16 ``` ### 3. Waits for the generator[​](#3-waits-for-the-generator "Direct link to 3. Waits for the generator") After loading data, the Service Catalog waits for Infrahub's generator to execute: * Default wait time: 60 seconds (configurable via `GENERATOR_WAIT_TIME`) * The generator (for example, `create_dc`) processes the design data * Devices, interfaces, cables, and IP addresses are automatically created ### 4. Creates a proposed change[​](#4-creates-a-proposed-change "Direct link to 4. Creates a proposed change") Once the generator completes, the Service Catalog creates a Proposed Change in Infrahub: * Compares the new branch to the main branch * Shows all differences (new devices, interfaces, configurations) * Allows review before merging to production ### View the results[​](#view-the-results "Direct link to View the results") After the workflow completes: 1. Navigate to **Proposed Changes** in the Infrahub UI (`http://localhost:8000`) 2. Find the proposed change for your DC (for example, "Add DC-6") 3. Review the **Data** tab to see new objects 4. Review the **Artifacts** tab to see generated configurations 5. Approve and merge when ready tip This workflow demonstrates infrastructure-as-code principles: declarative design data is transformed into imperative implementation through automated generators. ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Service catalog container not starting[​](#service-catalog-container-not-starting "Direct link to Service catalog container not starting") If the container fails to start: 1. Check Docker logs: ``` docker logs infrahub-demo-dc-service-catalog-1 ``` 2. Verify environment variables in `.env`: ``` INFRAHUB_SERVICE_CATALOG=true INFRAHUB_ADDRESS=http://infrahub-server:8000 ``` 3. Ensure Infrahub server is running and accessible: ``` docker ps --filter "name=infrahub-server" ``` ### Cannot access `http://localhost:8501`[​](#cannot-access-httplocalhost8501 "Direct link to cannot-access-httplocalhost8501") If the web interface doesn't load: 1. Verify port 8501 is not in use by another application 2. Check container port mapping: ``` docker ps --format "table {{.Names}}\t{{.Ports}}" --filter "name=service-catalog" ``` 3. Try accessing via container IP directly: ``` docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' infrahub-demo-dc-service-catalog-1 ``` ### Branch selector is empty[​](#branch-selector-is-empty "Direct link to Branch selector is empty") If no branches appear in the dropdown: 1. Verify Infrahub server connection from the container: ``` docker exec infrahub-demo-dc-service-catalog-1 curl http://infrahub-server:8000/api/schema ``` 2. Check that branches exist in Infrahub: ``` uv run infrahubctl branch list ``` 3. Review Service Catalog logs for API errors: ``` docker logs infrahub-demo-dc-service-catalog-1 | grep -i error ``` ### Generator timeout errors[​](#generator-timeout-errors "Direct link to Generator timeout errors") If DC creation fails with timeout errors: 1. Increase the wait time in `.env`: ``` GENERATOR_WAIT_TIME=120 # Increase to 120 seconds ``` 2. Check generator status in Infrahub UI under **Task Management** 3. Verify generator definition is registered in `.infrahub.yml` ### Form submission fails[​](#form-submission-fails "Direct link to Form submission fails") If the submit button doesn't work: 1. Check browser console for JavaScript errors (F12 → Console) 2. Verify all required fields are filled 3. Check that selected options (location, provider, design) exist in Infrahub 4. Review Service Catalog logs during submission: ``` docker logs -f infrahub-demo-dc-service-catalog-1 ``` ## Advanced usage[​](#advanced-usage "Direct link to Advanced usage") ### Custom default branch[​](#custom-default-branch "Direct link to Custom default branch") Change the default branch shown in the branch selector: ``` # In .env DEFAULT_BRANCH=production ``` ### Adjust timeouts[​](#adjust-timeouts "Direct link to Adjust timeouts") Tune timeouts for your environment: ``` # In .env GENERATOR_WAIT_TIME=90 # Generator execution wait API_TIMEOUT=30 # API request timeout API_RETRY_COUNT=3 # API retry attempts ``` ### Integration with CI/CD[​](#integration-with-cicd "Direct link to Integration with CI/CD") The Service Catalog workflow can be automated via API: 1. POST form data to Service Catalog endpoint 2. Wait for branch creation 3. Run validation checks 4. Auto-approve proposed changes if checks pass This enables self-service infrastructure provisioning with approval gates. ## What you've learned[​](#what-youve-learned "Direct link to What you've learned") Congratulations! You've explored Infrahub's Service Catalog: * ✅ Enabled the Service Catalog container * ✅ Accessed the web interface at `http://localhost:8501` * ✅ Switched between branches to view different infrastructure states * ✅ Viewed existing data centers and colocation centers * ✅ Visualized rack layouts with color-coded device placement * ✅ Created a new data center using the guided form * ✅ Understood the automated workflow (branch → data → generator → proposed change) ## Next steps[​](#next-steps "Direct link to Next steps") Now that you understand the Service Catalog, you can: * **Customize the form** - Modify `service_catalog/pages/create_dc.py` to add fields * **Add validation** - Implement checks before submission * **Extend to other infrastructure types** - Create forms for POPs, segments, or services * **Integrate with approval workflows** - Connect to ticketing or change management systems * **Build custom dashboards** - Add Streamlit pages for monitoring and reporting For deeper understanding: * **[Developer guide](/demo-dc/developer-guide.md)** - Learn about generators and transforms * **[Containerlab deployment](/demo-dc/containerlab-deployment.md)** - Test generated configurations in virtual labs * **[Security management](/demo-dc/security-management.md)** - Manage firewall policies through Infrahub --- # User walkthrough This tutorial guides you through the complete workflow of creating a data center topology using Infrahub's design-driven automation. You'll create a new branch, load a topology design, run a generator to create infrastructure, review the changes, and merge them to the main branch. By the end of this walkthrough, you'll understand how to use Infrahub's branch-based workflow to safely design, validate, and deploy network infrastructure. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before starting this tutorial, ensure you have: * Completed the [installation guide](/demo-dc/install.md) and have Infrahub running * Loaded the bootstrap data and schemas * Access to the Infrahub web interface at `http://localhost:8000` ## What you'll accomplish[​](#what-youll-accomplish "Direct link to What you'll accomplish") In this tutorial, you will: 1. Create a new branch for your changes 2. Load a data center design (DC-3) 3. Wait for the automatic generator to create devices, interfaces, and connections 4. Create a proposed change 5. Review the generated artifacts and validation results 6. Merge the changes to the main branch ## Step 1: Create a new branch[​](#step-1-create-a-new-branch "Direct link to Step 1: Create a new branch") All changes in Infrahub happen in branches. This allows you to work on infrastructure changes safely without affecting the main branch until you're ready. * Web Interface * CLI 1. Navigate to `http://localhost:8000` 2. Click on the branch selector in the top-right corner (shows "main" by default) 3. Click **Create Branch** 4. Enter branch name: `add-dc3` 5. Click **Create** You should see a success message confirming the branch was created. Create the branch from the command line: ``` uv run infrahubctl branch create add-dc3 ``` You should see a success message confirming the branch was created. tip Branch names should be descriptive and indicate what changes you're making, like `add-dc3`, `update-security-policies`, or `fix-bgp-config`. ## Step 2: Switch to your new branch[​](#step-2-switch-to-your-new-branch "Direct link to Step 2: Switch to your new branch") In the web interface: 1. Click the branch selector in the top-right 2. Select `add-dc3` from the list 3. The interface should now show `add-dc3` as the active branch info All subsequent actions will be performed on the `add-dc3` branch until you switch back to another branch. ## Step 3: load the DC-3 design[​](#step-3-load-the-dc-3-design "Direct link to Step 3: load the DC-3 design") The DC-3 design is a pre-configured data center topology specification that defines: * A data center named "DC-3" * Spine-leaf fabric architecture * Device roles and platform assignments * IP address pools and subnets * BGP autonomous system configuration Load the design data onto your branch: ``` uv run infrahubctl object load objects/dc/dc-arista-s.yml --branch add-dc3 ``` Watch the output for success messages. You should see confirmations that the topology data has been loaded. ### Verify the design loaded[​](#verify-the-design-loaded "Direct link to Verify the design loaded") In the web interface: 1. Ensure you're on the `add-dc3` branch 2. Navigate to **Network Topologies → Deployments → Data center** 3. You should see a new entry for **DC-3** 4. Click on DC-3 to view its properties The design specifies high-level parameters like: * Data center name and location * Number of spines and leaves * Underlay routing protocol (eBGP or OSPF) * Addressing schemes ### Multi-vendor support[​](#multi-vendor-support "Direct link to Multi-vendor support") This demo supports multiple network device vendors with pre-configured data center designs. The DC-3 topology you just loaded uses Arista EOS switches, but the demo can generate configurations for several other vendors. **Available vendor designs:** * **Arista EOS** - The default DC-3 design with Arista switches (`objects/dc/dc-arista-s.yml`) * **Cisco NX-OS** - The DC-2 design (`objects/dc/dc-cisco-s.yml`) * **Juniper JunOS** - DC-5 design with Juniper switches * **SONiC NOS** - DC-4 design with EdgeCore switches running SONiC **Using alternative vendor designs:** To explore Arista EOS configurations, you can use the automated demo command: ``` uv run invoke demo-dc-arista ``` This command creates a branch, loads the Arista design, waits for generator completion, then creates a proposed change. For other vendors, load the design files directly: ``` # Juniper design (DC-5) uv run infrahubctl object load objects/dc/dc-juniper-s.yml --branch add-dc5 # SONiC design (DC-4) uv run infrahubctl object load objects/dc/dc-sonic-border-leafs.yml --branch add-dc4 ``` Each vendor design generates appropriate configurations for that platform's CLI syntax and features. The same workflow (branch creation, design loading, generator execution, proposed change, merge) applies regardless of vendor. ## Step 4: Wait for automatic generator execution[​](#step-4-wait-for-automatic-generator-execution "Direct link to Step 4: Wait for automatic generator execution") When you loaded the DC-3 design in Step 3, Infrahub automatically triggered a generator action that transforms the abstract design into concrete infrastructure objects including devices, interfaces, IP addresses, and routing configurations. This automation is configured through an event action (defined in `objects/events/98_generator_action.yml`) that runs the `create_dc` generator whenever a data center topology is created or updated. ### Monitor generator progress[​](#monitor-generator-progress "Direct link to Monitor generator progress") Generator execution can take several minutes as it creates hundreds of objects. Monitor the task status: 1. Navigate to **System → Tasks** in the left sidebar 2. Find your generator task in the list (look for `create_dc`) 3. Watch the status change from "Pending" to "Running" to "Completed" info Generator actions are event-driven automations configured in Infrahub. When you load a data center topology object, the event system automatically triggers the `create_dc` generator. This eliminates the need for manual generator execution and ensures consistency across deployments. tip Generators create infrastructure following best practices. For DC-3, the create\_dc generator will create spine switches, leaf switches, border leafs, management connections, fabric peering, loopback interfaces, and BGP configurations. ## Step 5: Review generated infrastructure[​](#step-5-review-generated-infrastructure "Direct link to Step 5: Review generated infrastructure") Once the generator completes, explore what was created: ### View devices[​](#view-devices "Direct link to View devices") 1. Navigate to **Device Management → Infrastructure → All Devices** 2. You should see multiple devices created: * Spine switches (typically 2 or more) * Leaf switches (typically 4 or more) * Border leaf switches (typically 2) ### View interfaces[​](#view-interfaces "Direct link to View interfaces") Click on any device to see its details including: * Physical and loopback interfaces * IP addresses assigned to each interface * Interface descriptions and roles ### View IP prefixes[​](#view-ip-prefixes "Direct link to View IP prefixes") Navigate to **IP Address Management → IP Prefixes** to see: * Management network prefixes * Loopback prefixes * Underlay point-to-point link prefixes ### View routing[​](#view-routing "Direct link to View routing") Navigate to **Routing** sections to see: * BGP peer groups * BGP sessions between spines and leaves * Autonomous system assignments ## Step 6: Create a proposed change[​](#step-6-create-a-proposed-change "Direct link to Step 6: Create a proposed change") Now that you've made changes on your branch, create a proposed change to merge them into main. Proposed changes provide a review mechanism with diffs, validation checks, and artifact generation. ### Create the proposed change[​](#create-the-proposed-change "Direct link to Create the proposed change") 1. Navigate to **Proposed Changes** 2. Click **New Proposed Change** 3. Fill in the form: * **Name**: `Add DC-3 data center` * **Source Branch**: `add-dc3` * **Destination Branch**: `main` * **Description** (optional): `This change adds the DC-3 data center with spine-leaf fabric` 4. Click **Create Proposed Change** ### Understanding what happens next[​](#understanding-what-happens-next "Direct link to Understanding what happens next") When you create a proposed change, Infrahub automatically: * Creates a diff showing all objects added, modified, or deleted * Runs validation checks defined in your checks * Regenerates all artifacts affected by the changes * Runs repository checks if configured ## Step 7: Review validations and artifacts[​](#step-7-review-validations-and-artifacts "Direct link to Step 7: Review validations and artifacts") Navigate to your proposed change to review the results: ### View the diff[​](#view-the-diff "Direct link to View the diff") 1. Click on the **Files** or **Diff** tab 2. Review all the objects that will be created when you merge 3. You should see hundreds of additions including: * Devices * Interfaces * IP addresses * BGP configurations * Routing protocols ### Check validation results[​](#check-validation-results "Direct link to Check validation results") 1. Click on the **Checks** or **Validations** tab 2. Review the validation results 3. All checks should pass with a green checkmark 4. If any checks fail, review the error messages and fix issues before merging Common validations include: * **Spine connectivity checks** - Verify all spines are properly connected * **Leaf connectivity checks** - Verify all leaves are connected to all spines * **IP address uniqueness** - Ensure no duplicate IP assignments * **BGP configuration** - Validate BGP peer groups and sessions ### View generated artifacts[​](#view-generated-artifacts "Direct link to View generated artifacts") 1. Click on the **Artifacts** tab 2. You should see generated artifacts including: * **Device configurations** (one per device) * Spine, leaf, and border-leaf configurations in native vendor CLI format * For Arista and Juniper leaf switches, additional OpenConfig-formatted configurations demonstrating multi-format output * **Topology cabling matrix** (CSV format) - Complete cabling plan with source/destination interfaces * **Containerlab topology file** - Virtual lab topology definition for testing configurations Click on any artifact to view its content. For example, view a spine configuration to see the generated CLI commands. **Multi-format configuration rendering:** Infrahub's transformation system can generate multiple configuration formats from the same source data. For Arista EOS and Juniper JunOS leaf switches, you'll find both: * **Native CLI format** - Vendor-specific command syntax ready for deployment * **OpenConfig format** - Standardized, vendor-neutral YANG-based configuration This demonstrates how a single data model in Infrahub can produce multiple output formats, enabling flexibility in deployment methods (traditional CLI, NETCONF, gNMI, etc.) without duplicating source data. **Infrastructure artifacts:** Beyond device configurations, the generator creates supporting artifacts: * **CSV cabling matrix** - Documents physical connectivity between all devices and interfaces, useful for operations teams during installation and troubleshooting * **Containerlab topology file** - Enables spinning up virtual lab environments that mirror your production topology for testing and validation tip Artifacts are regenerated automatically when you create a proposed change. This ensures they always reflect the latest data. ## Step 8: Merge the proposed change[​](#step-8-merge-the-proposed-change "Direct link to Step 8: Merge the proposed change") Once you've reviewed everything and all validations pass, you're ready to merge: 1. In the proposed change view, click the **Merge** button 2. Confirm the merge in the dialog 3. Wait for the merge task to complete The merge process: * Applies all changes from the source branch to the destination branch * Runs any post-merge hooks or actions * Updates the main branch with your new infrastructure ### Verify the merge[​](#verify-the-merge "Direct link to Verify the merge") 1. Switch back to the **main** branch using the branch selector 2. Navigate to **Device Management → Infrastructure → All Devices** 3. You should now see all the DC-3 devices in the main branch 4. Navigate to **Services → Topology Deployments → Data center** 5. Verify that DC-3 appears in the main branch ## Step 9: View device configurations[​](#step-9-view-device-configurations "Direct link to Step 9: View device configurations") Now that DC-3 is in the main branch, view the generated device configurations: 1. Ensure you're on the **main** branch 2. Navigate to **Device Management → Infrastructure → All Devices** 3. Click on a spine device (for example, `dc3-spine1`) 4. Look for an **Artifacts** section or tab 5. Click on the spine configuration artifact You should see a complete device configuration including: * Interface configurations * IP addressing * BGP configuration * OSPF configuration (if using OSPF underlay) * VxLAN EVPN configuration ## What you've learned[​](#what-youve-learned "Direct link to What you've learned") Congratulations! You've completed the full workflow: * ✅ Created a branch for your changes * ✅ Loaded a topology design * ✅ Monitored automatic generator execution to create infrastructure * ✅ Created a proposed change with automatic validation * ✅ Reviewed diffs, checks, and artifacts * ✅ Merged changes to the main branch * ✅ Viewed generated device configurations ## Next steps[​](#next-steps "Direct link to Next steps") Now that you understand the basic workflow, you can: * **Explore variations** - Try creating DC-3 or a POP topology * **Modify designs** - Edit the objects/dc/dc-arista-s.yml file to change the topology * **Add network services** - Create Layer 2 or Layer 3 services on top of the fabric * **Customize configurations** - Modify templates in the `templates/` directory * **Add validation checks** - Create custom checks in the `checks/` directory For a deeper understanding, continue to: * **[Understanding the concepts](/demo-dc/concepts.md)** - Learn about design-driven automation and composable topologies * **[Developer guide](/demo-dc/developer-guide.md)** - Understand how generators, transforms, and checks work ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Generator fails or times out[​](#generator-fails-or-times-out "Direct link to Generator fails or times out") If the generator task fails: 1. Check the task logs in **System → Tasks** 2. Look for error messages indicating what went wrong 3. Common issues include missing data, incorrect schema relationships, or resource allocation failures ### Validation checks fail[​](#validation-checks-fail "Direct link to Validation checks fail") If validation checks fail: 1. Review the check output to understand what failed 2. Fix the underlying data issues 3. Re-run validations by updating the proposed change ### Merge conflicts[​](#merge-conflicts "Direct link to Merge conflicts") If you encounter merge conflicts: 1. Review the conflicting objects 2. Resolve conflicts manually if needed 3. You may need to rebase your branch or create a new branch --- ## Infrahub - PoC of a service catalog[​](#infrahub---poc-of-a-service-catalog "Direct link to Infrahub - PoC of a service catalog") This repository demonstrates a proof of concept for a service catalog using Infrahub and Streamlit. For more information about the business case and the development of this PoC, read the [blog post](https://opsmill.com/blog/how-to-turn-your-source-of-truth-into-a-service-factory/) on the OpsMill website. [Blog posthttps://opsmill.com/blog/how-to-turn-your-source-of-truth-into-a-service-factory/](https://opsmill.com/blog/how-to-turn-your-source-of-truth-into-a-service-factory/) ## The problem[​](#the-problem "Direct link to The problem") Organizations strive to deliver services efficiently, as this is where value is created. The stakes are high: the structure of a service determines everything downstream—from invoicing and lifecycle management to resource allocation and capacity planning. A poorly designed service layer can lead to inefficiencies and challenges at every stage. The service catalog forms the foundation of this structure, serving as a blueprint for how services are defined, managed, and delivered. It enables infrastructure self-service across the organization to streamline operations and enhance service delivery. ## Use case[​](#use-case "Direct link to Use case") ![Otternet](/assets/images/otternet-491510e07a59395b882e6f8abe580b5f.png) This demo follows a fictional ISP, Otter-net, which provides standard internet connectivity. ![Backbone](/assets/images/network-backbone-230ca1a3cfa0412b7a5a7825ea721e8c.png) The company operates multiple points of presence across Europe and the United States. Currently, it offers a single service: dedicated internet access. This service provides customers with a physical port and a set of public IP addresses for hosting services. Additional services are planned for the future! ![Service](/assets/images/network-customer-service-355b7b6ffae51cc19bb9cc1d0468d873.png) The operational team at Otter-net is divided into two groups: * Network Architects: Experts with extensive networking experience, responsible for operating and maintaining the backbone network. * Service Delivery Team: Customer-facing professionals responsible for provisioning and connecting services to the backbone. The goal The goal of the company is to automate the service delivery process, allowing the service delivery team to request new services without needing to involve network architects for every request. This will enable faster and more efficient service delivery. ## The solution[​](#the-solution "Direct link to The solution") Implementing a service catalog is a complex operation that many organizations struggle with. It demands a deep understanding of the product lifecycle, the interplay of various components, and coordination among numerous stakeholders. Beyond that, it requires a robust technical implementation to automate all the associated rules and processes properly. In many ways, Infrahub is the perfect tool to support your service catalog implementation: * **Flexible Schema**: Infrahub's schema can be tailored to fit the specific needs of your service catalog, allowing you to define services, components, and their relationships in a way that makes sense for your organization. * **Version Control**: Infrahub's native version control capabilities enable you to track changes, ensuring that you have full visibility into the evolution of your network. * **Generators**: Infrahub's generators can be used to codify the rules and processes associated with your service implementation, enabling fast and consistent implementation across the board. * **Resource Managers**: Infrahub's resource managers can be used to automate the allocation and management of resources, ensuring robust and efficient resource management. * **Branching**: Infrahub's branching capabilities allow you to isolate changes in a separate branch, paving the way for testing and validation before deploying changes to production. To complete the workflow from service request to configuration deployment, this demo also integrates [Semaphore](https://semaphoreui.com/), an open-source Ansible automation UI. Once a proposed change is reviewed and merged in Infrahub, Semaphore runs Ansible playbooks that fetch the generated device configurations from Infrahub and deploy them to network devices — closing the loop from self-service request to production deployment. If you want more information about the business case and how this PoC was built, you can read the corresponding [blog post](https://opsmill.com/blog/how-to-turn-your-source-of-truth-into-a-service-factory/) on the OpsMill website. [Blog posthttps://opsmill.com/blog/how-to-turn-your-source-of-truth-into-a-service-factory/](https://opsmill.com/blog/how-to-turn-your-source-of-truth-into-a-service-factory/) --- # Developer Walkthrough In this walkthrough, we detail the key building blocks of the demo, explain how it works, and describe how the components interact. This guide is intended for developers who want to understand the implementation details of the service catalog PoC. [Need help or have questions? Join our Discord community for support!https://discord.gg/opsmill](https://discord.gg/opsmill) ## The schema[​](#the-schema "Direct link to The schema") To structure and store data, we define a schema in Infrahub. You can find the schema files in the `schemas` folder. ### Schema architecture overview[​](#schema-architecture-overview "Direct link to Schema architecture overview") The following diagram illustrates the complete schema architecture, showing how different objects relate to each other in the Service Catalog demo: #### Key concepts in the diagram[​](#key-concepts-in-the-diagram "Direct link to Key concepts in the diagram") 1. **Generic vs Concrete Objects**: * **Generic** (dashed boxes): Abstract base classes that define common attributes * **Concrete** (solid boxes): Actual objects that can be instantiated 2. **Object Categories**: * **Service Layer** (blue): Service definitions and instances * **Location Layer** (pink): Geographic hierarchy and hosting locations * **DCIM Layer** (orange): Physical devices and interfaces * **IPAM Layer** (green): Network resources (IPs, VLANs, prefixes) 3. **Relationship Types**: * **Inheritance** (dashed arrows): Shows class hierarchy * **Associations** (solid arrows): Shows data relationships with cardinality * **Resource Allocation** (dotted arrows): Shows dynamic provisioning flow ### Consuming the schema library[​](#consuming-the-schema-library "Direct link to Consuming the schema library") Much of the schema is based on the [Infrahub schema library](https://github.com/opsmill/schema-library), which provides reusable schema components for quickly scaffolding a schema. * `base`: Contains generic definitions for `IPAM` (IP address, prefix, etc.), `DCIM` (network device, interface, etc.), `location`, and `organization` (provider, manufacturer). Importing this folder is mandatory as it provides the basic definitions required for extensions. * `location_minimal`: Defines a hierarchical tree for country, metro, and site. * `vlan`: Includes nodes for VLANs and L2 domains. * `circuit` (optional): Defines circuits with endpoints for site-to-site connections. This schema is available in `schemas/circuit/` but is not loaded by default. [Learn about schema libraryhttps://docs.infrahub.app/schema-library](https://docs.infrahub.app/schema-library) ### Custom service schema[​](#custom-service-schema "Direct link to Custom service schema") The service layer is unique to each organization, so we define a custom schema to represent services and their components. /schemas/service/service.yml ``` --- # yaml-language-server: $schema=https://schema.infrahub.app/infrahub/schema/latest.json version: "1.0" generics: # To enable future expansions, we define a generic service object. # This object holds all common attributes shared across the services. # Additionally, we can leverage this generic structure to simplify relationships. - name: Generic namespace: Service description: Generic service... label: Service icon: mdi:package-variant include_in_menu: true human_friendly_id: - service_identifier__value order_by: - service_identifier__value display_labels: - service_identifier__value attributes: - name: service_identifier kind: Text unique: true order_weight: 1000 optional: false branch: agnostic - name: account_reference kind: Text order_weight: 1010 optional: false branch: agnostic nodes: # The DedicatedInternet schema node inherits from the generic service object and includes a few additional attributes. # These attributes are relatively high-level (e.g., an ip_package with T-shirt size values) and are primarily intended as inputs for users. - name: DedicatedInternet namespace: Service description: This service provides customers with a dedicated physical port, ensuring complete internet connectivity. label: Dedicated Internet icon: mdi:ethernet menu_placement: ServiceGeneric inherit_from: - ServiceGeneric include_in_menu: true # By default, Infrahub creates data within branches (parallel realities), but it also supports branch-agnostic objects. # A branch-agnostic object is propagated to all branches, regardless of where it was created. # Here, branch-agnostic behavior is applied in the schema to the service object and key attributes, such as service_identifier. # This ensures consistent tracking of a service across all ongoing implementations and branches. branch: agnostic attributes: - name: status kind: Dropdown optional: false default_value: draft order_weight: 1050 # Putting this one as branch aware otherwise generator put it as active in the branch and so on main as well # even tho the service is really active only when the branch is merged... branch: aware choices: - name: draft label: Draft color: "#D3D3D3" - name: in-delivery label: In Delivery color: "#A8E6A2" - name: active label: Active color: "#66CC66" - name: in-decomissioning label: In Decomissioning color: "#FFAB59" - name: decomissioned label: Decomissioned color: "#FF6B6B" - name: bandwidth kind: Dropdown optional: false order_weight: 1100 branch: aware choices: - name: "100" label: Hundred Megabits description: Provides a 100 Mbps bandwidth. - name: "1000" label: One Gigabit description: Provides a 1 Gbps bandwidth. - name: "10000" label: Ten Gigabits description: Provides a 10 Gbps bandwidth. - name: ip_package kind: Dropdown optional: false order_weight: 1120 branch: aware choices: - name: small label: Small description: Provide customer with 6 IPs. color: "#6a5acd" - name: medium label: Medium description: Provide customer with 14 IPs. color: "#9090de" - name: large label: Large description: Provide customer with 30 IPs. color: "#ffa07a" # We implement various relationships to capture all the building blocks of the service (such as prefixes, interfaces, etc.) relationships: # From a site’s perspective, I only need a list of services and do not want multiple relationships for each type of service. # However, for a specific type of service, I want to enforce rules within the relationships. # For example, a distributed service could link to multiple sites, whereas a DedicatedInternet service is tied to a single site. # By configuring directions in relationships to point toward the generic service from a site’s perspective and initiating the relationship in the node pointing toward the site, we achieve the desired behavior. # Using the same identifier in the relationship allows Infrahub to recognize it as a single, unified relationship. - name: location peer: LocationSite order_weight: 1150 cardinality: one direction: inbound identifier: service_site optional: false branch: agnostic - name: dedicated_interfaces peer: DcimInterface kind: Attribute order_weight: 1200 cardinality: many direction: inbound identifier: service_interface - name: vlan peer: IpamVLAN kind: Attribute order_weight: 1300 cardinality: one direction: inbound identifier: service_vlan - name: gateway_ip_address peer: IpamIPAddress order_weight: 1350 cardinality: one direction: inbound identifier: service_ip_address - name: prefix peer: IpamPrefix kind: Attribute order_weight: 1400 cardinality: one direction: inbound identifier: service_prefix extensions: nodes: - kind: LocationSite relationships: - name: services peer: ServiceGeneric cardinality: many direction: outbound identifier: service_site branch: agnostic - kind: DcimInterface relationships: - name: service peer: ServiceGeneric cardinality: one direction: outbound identifier: service_interface - kind: IpamVLAN relationships: - name: service peer: ServiceGeneric cardinality: one direction: outbound identifier: service_vlan - kind: IpamIPAddress relationships: - name: service peer: ServiceGeneric cardinality: one direction: outbound identifier: service_ip_address - kind: IpamPrefix relationships: - name: service peer: ServiceGeneric cardinality: one direction: outbound identifier: service_prefix - kind: CoreProposedChange relationships: - name: tags peer: BuiltinTag cardinality: many ``` success We now have the data model and data to support our use case. It captures everything from services to the backbone, with some abstraction for flexibility. This setup is a strong foundation for automation. [Learn about Infrahub flexible schemahttps://docs.infrahub.app/guides/create-schema](https://docs.infrahub.app/guides/create-schema) ## The generator[​](#the-generator "Direct link to The generator") The generator is a powerful feature of Infrahub that allows you to codify the rules and processes associated with your service implementation. It enables fast and consistent implementation across the board. important We want to build the generator with the concept of **idempotency** in mind, meaning it should be repeatable: it assigns resources the first time it runs, and if run again, it changes nothing if the desired state is already achieved. This approach ensures the code is robust and predictable. Infrahub provides a set of features to help: * Resource manager: It allows users to create pools and allocate resources from them, such as prefixes, IP addresses, or even numbers. We will use this feature to allocate our prefixes/vlan in a branch-agnostic and idempotent way. Learn more about [resource managers](https://docs.infrahub.app/topics/resource-manager). * `allow_upsert=True`: This parameter is provided when saving the node, allowing it to be created if it doesn't exist or updated if it does. This is useful for ensuring that the generator can run multiple times without creating duplicates or errors. /generators/implement\_dedicated\_internet.py ``` from __future__ import annotations import logging import random from infrahub_sdk.generator import InfrahubGenerator from infrahub_sdk.node import InfrahubNode from infrahub_sdk.protocols import CoreIPPrefixPool, CoreNumberPool from service_catalog.protocols_async import ( DcimDevice, DcimInterfaceL3, IpamIPAddress, IpamPrefix, IpamVLAN, ServiceDedicatedInternet, ) ACTIVE_STATUS = "active" SERVICE_VLAN_POOL: str = "Customer vlan pool" SERVICE_PREFIX_POOL: str = "Customer prefixes pool" IP_PACKAGE_TO_PREFIX_SIZE: dict[str, int] = {"small": 29, "medium": 28, "large": 27} class DedicatedInternetGenerator(InfrahubGenerator): customer_service: ServiceDedicatedInternet | None = None allocated_vlan: IpamVLAN | None = None allocated_prefix: IpamPrefix | None = None gateway_ip: IpamIPAddress | None = None log = logging.getLogger("infrahub.tasks") async def generate(self, data: dict) -> None: service_dict: dict = data["ServiceDedicatedInternet"]["edges"][0]["node"] # Translate the dict to proper object self.customer_service: ServiceDedicatedInternet = await InfrahubNode.from_graphql( client=self.client, data=service_dict, branch=self.branch, ) # Move the service as active # TODO: Not happy with having this one here... self.customer_service.status.value = "active" await self.customer_service.save(allow_upsert=True) # Allocate the VLAN to the service await self.allocate_vlan() # Translate teeshirt size to int self.prefix_length: int = IP_PACKAGE_TO_PREFIX_SIZE[self.customer_service.ip_package.value] # Allocate the prefix to the service await self.allocate_prefix() # Allocate port await self.allocate_port() # Create L3 interface for gateway await self.allocate_gateway() async def allocate_vlan(self) -> None: """Create a VLAN with ID coming from the pool provided and assign this VLAN to the service.""" self.log.info("Allocating VLAN to this service...") # Get resource pool resource_pool = await self.client.get( kind=CoreNumberPool, name__value=SERVICE_VLAN_POOL, ) # Craft and save the vlan self.allocated_vlan = await self.client.create( kind=IpamVLAN, name=f"vlan__{self.customer_service.service_identifier.value}", vlan_id=resource_pool, # Here we get the vlan ID from the pool description=f"VLAN allocated to service {self.customer_service.service_identifier.value}", status=ACTIVE_STATUS, role="customer", l2domain=["default"], service=self.customer_service, ) # And save it to Infrahub await self.allocated_vlan.save(allow_upsert=True) self.log.info(f"VLAN `{self.allocated_vlan.name.value}` assigned!") async def allocate_prefix(self) -> None: """Allocate a prefix coming from a resource pool to the service.""" self.log.info("Allocating prefix from pool...") # Get resource pool resource_pool = await self.client.get( kind=CoreIPPrefixPool, name__value=SERVICE_PREFIX_POOL, ) # Craft the data dict for prefix prefix_data: dict = { "status": "active", "description": f"Prefix allocated to service {self.customer_service.service_identifier.value}", "service": [self.customer_service.id], "role": "customer", "vlan": [self.allocated_vlan.id], } # Create resource from the pool self.allocated_prefix = await self.client.allocate_next_ip_prefix( resource_pool, kind=IpamPrefix, data=prefix_data, prefix_length=self.prefix_length, identifier=self.customer_service.service_identifier.value, ) self.log.info(f"Prefix `{self.allocated_prefix}` assigned!") await self.allocated_prefix.save(allow_upsert=True) async def allocate_port(self) -> None: """Allocate a port to the service.""" allocated_port = None self.log.info("Allocating port to this service...") # Fetch interfaces records await self.customer_service.dedicated_interfaces.fetch() self.log.info( f"There are {len(self.customer_service.dedicated_interfaces.peers)} interfaces attached to this service.", ) # If we have any interface attached to the service if len(self.customer_service.dedicated_interfaces.peers) > 0: # Loop over interfaces attached to the service for interface in self.customer_service.dedicated_interfaces.peers: # Get device related to the interface await interface.peer.device.fetch() # If the device is "core" if interface.peer.device.peer.role.value == "core": self.log.info(f"Port `{interface.peer.display_label}` already allocated to the service.") # Big assomption but we assume port is already allocated self.index = interface.peer.device.peer.index.value allocated_port = interface break # If we don't have yet a port, we need to find one if allocated_port is None: self.log.info("Haven't found any port allocated to this service.") # Here, we pick randomly. In a real-life scenario, we might want to give this more thought self.index = random.randint(1, 2) # Find the switch on the site switch = await self.client.get( kind=DcimDevice, location__ids=[self.customer_service.location.id], role__value="core", index__value=self.index, ) self.log.info(f"Looking for port on {switch}...") # Fetch switch interface data await switch.interfaces.fetch() # Find first interface on that switch that is free selected_interface = next( ( interface for interface in switch.interfaces.peers if interface.peer.role.value == "customer" and interface.peer.status.value == "free" and interface.peer.service.id is None ), None, # Default value if no match is found ) # If we don't have any interface available if selected_interface is None: msg: str = f"There is no physical port to allocate to customer on {switch}" self.log.exception(msg) raise Exception(msg) self.log.info(f"Found port {selected_interface.peer.display_label} to allocate to the service.") allocated_port = selected_interface allocated_port = allocated_port.peer # Enforce all params of this interface allocated_port.enabled.value = True allocated_port.status.value = "active" allocated_port.l2_mode.value = "Access" allocated_port.role.value = "customer" allocated_port.description.value = f"Port allocated to service {self.customer_service.service_identifier.value}" allocated_port.speed.value = int(self.customer_service.bandwidth.value) allocated_port.service = self.customer_service allocated_port.untagged_vlan = self.allocated_vlan # Finally save await allocated_port.save(allow_upsert=True) async def allocate_gateway(self) -> None: """Allocate a gateway to the service.""" self.log.info("Allocating gateway to this service...") # Find the corresponding router router = await self.client.get( kind=DcimDevice, location__ids=[self.customer_service.location.id], role__value="edge", index__value=self.index, ) # Work around issue if isinstance(self.allocated_vlan.vlan_id.value, int): vlan_id: int = self.allocated_vlan.vlan_id.value else: vlan_id: int = self.allocated_vlan.vlan_id.value["value"] # Create interface gateway_interface = await self.client.create( kind=DcimInterfaceL3, name=f"vlan_{vlan_id!s}", speed=1000, device=router, status="active", role="customer", description=f"Gateway interface for service {self.customer_service.service_identifier.value}", enabled=True, service=self.customer_service, untagged_vlan=self.allocated_vlan, ) await gateway_interface.save(allow_upsert=True) # Compute the gateway ip address: str = f"{next(self.allocated_prefix.prefix.value.hosts())!s}/{self.prefix_length!s}" # Create IP object self.gateway_ip = await self.client.create( kind=IpamIPAddress, address=address, service=self.customer_service, interface=gateway_interface, ) await self.gateway_ip.save(allow_upsert=True) self.log.info(f"Gateway `{self.gateway_ip.address.value}` assigned!") # Add gateway to prefix self.allocated_prefix.gateway = self.gateway_ip # Save prefix await self.allocated_prefix.save(allow_upsert=True) ``` ### Generator architecture patterns[​](#generator-architecture-patterns "Direct link to Generator architecture patterns") 1. **Resource Tracking**: Every allocation linked to the service 2. **Relationship Integrity**: Bi-directional references maintained 3. **Error Handling**: Fail fast with clear messages 4. **Logging**: Detailed progress tracking for troubleshooting ### Service provisioning flow[​](#service-provisioning-flow "Direct link to Service provisioning flow") Here's a focused view of how resources flow during service provisioning: ### Summary: From request to reality[​](#summary-from-request-to-reality "Direct link to Summary: From request to reality") The generator transforms a service request: ``` Service: DI-12345 Location: Paris Bandwidth: 1 Gbps IP Package: Medium ``` Into fully provisioned infrastructure: * VLAN 142 allocated and configured * IP prefix 10.0.42.0/28 assigned * Switch port ge-0/0/5 configured * Router interface vlan\_142 with IP 10.0.42.1/28 All in seconds, consistently, and idempotently! [Learn about Infrahub generatorshttps://docs.infrahub.app/topics/generator](https://docs.infrahub.app/topics/generator) ## The service portal architecture[​](#the-service-portal-architecture "Direct link to The service portal architecture") ### Streamlit for the user interface[​](#streamlit-for-the-user-interface "Direct link to Streamlit for the user interface") The demo uses [Streamlit](https://streamlit.io/) for the user interface because it: * Builds web UIs with pure Python (no JavaScript required) * Provides ready-made components for forms and data display * Integrates seamlessly with the Infrahub Python SDK * Enables rapid prototyping of service catalogs ### Portal design patterns[​](#portal-design-patterns "Direct link to Portal design patterns") #### Infrahub SDK integration[​](#infrahub-sdk-integration "Direct link to Infrahub SDK integration") The portal uses several patterns to efficiently interact with Infrahub: Client Caching Pattern ``` @st.cache_resource def get_client(branch: str = "main") -> InfrahubClientSync: """Create and cache Infrahub client.""" address = get_instance_address() return InfrahubClientSync( address=address, config=Config(default_branch=branch) ) ``` Why Cache the Client? The `@st.cache_resource` decorator: * Creates client once and reuses it * Reduces connection overhead * Maintains consistent state * Improves page load performance [Learn about Streamlithttps://streamlit.io/](https://streamlit.io/) ## The deployment layer[​](#the-deployment-layer "Direct link to The deployment layer") ### Semaphore for Ansible automation[​](#semaphore-for-ansible-automation "Direct link to Semaphore for Ansible automation") [Semaphore](https://semaphoreui.com/) (SemaphoreUI) is an open-source web UI for running Ansible playbooks. In this demo, it serves as the deployment step: after a service is provisioned in Infrahub and the proposed change is merged, Semaphore runs playbooks that fetch the generated device configurations and deploy them to network equipment. ### Dynamic inventory[​](#dynamic-inventory "Direct link to Dynamic inventory") The inventory file (`ansible/inventory.yml`) uses the `opsmill.infrahub.inventory` plugin to build the device inventory dynamically from Infrahub at runtime. Instead of maintaining a static list of hosts, the plugin queries Infrahub for all `DcimDevice` nodes and composes host variables from their attributes: ansible/inventory.yml ``` plugin: opsmill.infrahub.inventory api_endpoint: "http://infrahub-server:8000" nodes: DcimDevice: include: - name - location.name - role - device_type.name - primary_address.address - status compose: hostname: name ansible_host: primary_address.address | ansible.utils.ipaddr('address') platform: device_type.platform.ansible_network_os location: location.name ``` This means any device added or modified in Infrahub is automatically reflected in the Ansible inventory — no manual synchronization required. ### The deployment playbook[​](#the-deployment-playbook "Direct link to The deployment playbook") The `ansible/deploy.yml` playbook performs two tasks per device: 1. **Fetch configuration artifact** — Uses `opsmill.infrahub.artifact_fetch` to retrieve the `startup-config` artifact from Infrahub, identified by the device's node ID 2. **Save configuration** — Writes the fetched configuration to a local file (`/tmp/_startup_config.txt`) ansible/deploy.yml ``` - name: Deploy configuration to network devices hosts: all connection: local gather_facts: false tasks: - name: "Fetch configuration artifact from Infrahub" opsmill.infrahub.artifact_fetch: artifact_name: "startup-config" target_id: "{{ id }}" register: artifact - name: Save configs to localhost ansible.builtin.copy: content: "{{ artifact.text }}" dest: "/tmp/{{ inventory_hostname }}_startup_config.txt" ``` ### Semaphore container setup[​](#semaphore-container-setup "Direct link to Semaphore container setup") The Semaphore container (`semaphore/Dockerfile`) extends the official image with the dependencies needed to interact with Infrahub: * **infrahub-sdk**: Python SDK installed via pip, required by the Ansible collection * **`opsmill.infrahub` collection**: Ansible collection installed via `ansible-galaxy`, providing the `inventory` plugin and `artifact_fetch` module At runtime, the container mounts the `ansible/` directory as its playbook source, so any changes to playbooks or inventory are reflected immediately without rebuilding. ## Architecture takeaways[​](#architecture-takeaways "Direct link to Architecture takeaways") ### Key design decisions[​](#key-design-decisions "Direct link to Key design decisions") 1. **Schema as Foundation**: Well-designed schema enables everything else 2. **Automation through Generators**: Complex provisioning made straightforward 3. **Resource Management**: Centralized pools prevent conflicts 4. **Branch-Based Workflows**: Safe testing and rollback capabilities 5. **User-Friendly Abstractions**: Hide complexity behind clear choices 6. **Deployment Automation**: Semaphore bridges the gap between source of truth and network devices, using dynamic inventory and artifact-based configuration delivery ### Extending the demo[​](#extending-the-demo "Direct link to Extending the demo") Consider these enhancements for production use: * Additional service types (MPLS, Cloud Connect, etc.) * Approval workflows with human checkpoints * Integration with ITSM platforms * Automated testing of provisioned services * Rollback and decommissioning automation * Event-driven deployment: trigger Semaphore runs automatically when proposed changes are merged in Infrahub ## Next steps[​](#next-steps "Direct link to Next steps") Now that you understand the architecture: 1. **Run the Demo**: Follow the [installation guide](/demo-service-catalog/getting-started/installation.md) to set up locally 2. **Try the Portal**: Walk through the [user experience](/demo-service-catalog/getting-started/user-walkthrough.md) 3. **Explore Further**: Modify the schema or create new service types 4. **Build Your Own**: Apply these patterns to your infrastructure needs The combination of Infrahub's flexible schema, powerful generators, and Python-based portals enables you to build sophisticated service automation tailored to your organization's needs. --- # How to Install the Service Catalog Demo This guide walks you through installing the Service Catalog demo, which demonstrates how to build a self-service portal on top of Infrahub's source of truth system. By the end of this guide, you'll have a working environment where users can request network services through a web interface. [Need help or have questions? Join our Discord community for support!https://discord.gg/opsmill](https://discord.gg/opsmill) ## What you'll achieve[​](#what-youll-achieve "Direct link to What you'll achieve") After completing this installation: * **Infrahub instance** running with network infrastructure data * **Service Catalog portal** accessible for requesting services * **Demo data** loaded including locations, devices, and network resources * **Git integration** configured for infrastructure as code workflows * **Semaphore automation** ready to deploy configurations to network devices ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before you get started, make sure you have these tools installed: ### Required software[​](#required-software "Direct link to Required software") * 🐳 **Docker**: Container runtime for running Infrahub and its dependencies * [Docker Desktop](https://docs.docker.com/desktop/) for local development * Or Docker Engine on a Linux VM for server deployments * Minimum 4GB RAM allocated to Docker * 🐍 **uv**: Fast Python package and project manager * Install via the [official installation guide](https://docs.astral.sh/uv/getting-started/installation/) * Used to manage Python packages and virtual environments * Version 0.4.0 or higher recommended ### System requirements[​](#system-requirements "Direct link to System requirements") * **Operating System**: macOS, Linux, or Windows with WSL2 * **Memory**: At least 8GB RAM (16GB recommended) * **Storage**: 10GB free disk space * **Network**: Internet connection for downloading dependencies ## Installation steps[​](#installation-steps "Direct link to Installation steps") ### Step 1: Clone the repository[​](#step-1-clone-the-repository "Direct link to Step 1: Clone the repository") First, download the demo code from GitHub. This repository contains all the necessary configuration files, schemas, and demo data. ``` git clone https://github.com/opsmill/infrahub-demo-service-catalog.git cd infrahub-demo-service-catalog ``` What's in the Repository? The repository includes: * **Infrahub schemas** defining the data models for network infrastructure * **Demo data** with sample locations, devices, and network resources * **Service Catalog application** built with Streamlit * **Docker configuration** for running all components ### Step 2: Configure environment variables[​](#step-2-configure-environment-variables "Direct link to Step 2: Configure environment variables") Set up the connection details for Infrahub. These variables tell the Service Catalog application how to communicate with the Infrahub API. ``` export INFRAHUB_ADDRESS="http://localhost:8000" export INFRAHUB_API_TOKEN="06438eb2-8019-4776-878c-0941b1f1d1ec" ``` #### Understanding the variables[​](#understanding-the-variables "Direct link to Understanding the variables") * **`INFRAHUB_ADDRESS`**: The URL where Infrahub's API is accessible * Default uses `localhost:8000` for local development * Change this if running Infrahub on a different host or port * **`INFRAHUB_API_TOKEN`**: Authentication token for API access * The demo uses a pre-configured token for convenience * In production, use secure token generation and storage ### Step 3: Install Python dependencies[​](#step-3-install-python-dependencies "Direct link to Step 3: Install Python dependencies") Use uv to create a virtual environment and install all required Python packages. ``` uv sync ``` This command: * Creates an isolated Python virtual environment * Installs the Infrahub SDK for API interactions * Installs Streamlit for the web portal interface * Sets up development tools (pytest, ruff, mypy) First time using uv? uv automatically manages virtual environments. All subsequent commands should be run with `uv run` prefix to ensure they use the correct environment. ### Step 4: Start the application stack[​](#step-4-start-the-application-stack "Direct link to Step 4: Start the application stack") Launch Infrahub and the Service Catalog using Docker Compose. This single command starts multiple services. ``` uv run invoke start ``` Why Use Invoke? The `invoke start` command is a wrapper that: * Ensures the correct Docker Compose configuration is used * Mounts the `service_catalog` module into the Infrahub container * Starts Infrahub, the Streamlit application, and Semaphore * Sets up networking between containers #### What gets started[​](#what-gets-started "Direct link to What gets started") This command launches: * **Infrahub Core**: The main application server (port 8000) * **PostgreSQL**: Database for storing infrastructure data * **Redis**: Cache and message broker * **RabbitMQ**: Task queue for asynchronous operations * **Service Catalog**: Streamlit web portal (port 8501) * **Semaphore**: Ansible automation UI (port 3000) ### Step 5: Initialize the demo environment[​](#step-5-initialize-the-demo-environment "Direct link to Step 5: Initialize the demo environment") Once Infrahub is up and running, initialize the demo by loading the repository, permissions, and seeding Semaphore. ``` uv run invoke init ``` This single command performs three things: 1. **Seeds Semaphore** with the project, SSH key, repository, inventory, environment, and task template (idempotent — safe to run multiple times) 2. **Loads the repository** configuration from `bootstrap/repository.yaml` into Infrahub — a read-write `CoreRepository` that references the local checkout mounted at `/upstream` in the container 3. **Loads permissions** from `bootstrap/permissions.yml`, creating an operator account (`john` / `infrahub`) with permissions to manage repositories, merge branches, and review proposed changes #### What the repository provides[​](#what-the-repository-provides "Direct link to What the repository provides") Once Infrahub imports the repository, it picks up: * **Generator Scripts**: Python code that automates service provisioning * **Configuration Templates**: Jinja2 templates for device configurations * **Schema**: Data models for DCIM, IPAM, services, and locations * **Objects and data**: Demo infrastructure loaded from YAML fixtures * **Menu**: Custom navigation structure for the Infrahub UI Understanding the Data Model The repository imports: * **Locations**: Sites in Paris, Brussels, New York, and Dallas * **Devices**: Core switches and edge routers at each site * **Providers**: Transit and peering providers (Cogent, Zayo, Colt, Lumen, Equinix) * **Network Resources**: VLAN pools, IP prefix pools, interface profiles * **Templates**: Device configurations and service definitions The schema uses a hierarchical structure: * Locations contain devices * Devices have interfaces and connections * Services allocate resources from pools * Everything is version-controlled and auditable ## Verify your installation[​](#verify-your-installation "Direct link to Verify your installation") Confirm everything is working by accessing both interfaces: ### Infrahub web UI[​](#infrahub-web-ui "Direct link to Infrahub web UI") * **URL**: * **Admin account**: `admin` / `infrahub` * **Operator account**: `john` / `infrahub` (limited permissions) * **What to Check**: * Navigate to the Schema page to see loaded models * Check the Objects page to verify demo data is present * Confirm the repository appears under Repositories * Log in as `john` to verify operator-level access works ### Service catalog portal[​](#service-catalog-portal "Direct link to Service catalog portal") * **URL**: * **What to Check**: * Home page displays available services * Service request forms are accessible * No connection errors appear ### Semaphore automation UI[​](#semaphore-automation-ui "Direct link to Semaphore automation UI") * **URL**: * **Username**: `admin` * **Password**: `semaphore` * **What to Check**: * Login page loads successfully * The "Service Catalog" project is visible (created by `invoke init`) * The "Deploy" task template and "Infrahub" inventory are configured ## Troubleshooting common issues[​](#troubleshooting-common-issues "Direct link to Troubleshooting common issues") ### Services won't start[​](#services-wont-start "Direct link to Services won't start") * Ensure Docker is running and has sufficient resources * Check for port conflicts (3000, 8000, 8501, 5432, 6379) * Run `uv run invoke destroy` then `uv run invoke start` to reset ### Schema load fails[​](#schema-load-fails "Direct link to Schema load fails") * Wait for Infrahub to fully start (check logs with `docker compose logs infrahub`) * Retry the schema load command after 30 seconds ### Can't access web interfaces[​](#cant-access-web-interfaces "Direct link to Can't access web interfaces") * Verify environment variables are set correctly * Check firewall rules if running on a remote server * Ensure all containers are running: `docker compose ps` ## Next steps[​](#next-steps "Direct link to Next steps") Now that your environment is running: 1. **For Users**: Follow the [user walkthrough](/demo-service-catalog/getting-started/user-walkthrough.md) to learn how to request services 2. **For Developers**: Check the [developer walkthrough](/demo-service-catalog/getting-started/developer-walkthrough.md) to understand the architecture 3. **Explore Further**: Browse the Infrahub UI to understand the data model and relationships --- # How to Use the Service Catalog Demo This guide demonstrates the complete service delivery workflow through two user perspectives: requesting services through the catalog and managing them through Infrahub. You'll see how automated provisioning transforms service requests into fully configured network services. [Need help or have questions? Join our Discord community for support!https://discord.gg/opsmill](https://discord.gg/opsmill) ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before starting, ensure you have: * Completed the [installation guide](/demo-service-catalog/getting-started/installation.md) * Both services running: * Service Catalog at `http://localhost:8501` * Infrahub at `http://localhost:8000` ## Understanding the workflow[​](#understanding-the-workflow "Direct link to Understanding the workflow") The demo simulates a typical enterprise service delivery process: 1. **Service Request**: Customer/engineer requests a service through self-service portal 2. **Automatic Processing**: Infrahub generates proposed changes with allocated resources 3. **Review & Approval**: Network architect reviews and approves the implementation 4. **Resource Allocation**: System automatically provisions VLANs, IPs, and configurations 5. **Service Delivery**: Customer receives access details for their new service 6. **Automated Deployment**: Semaphore runs Ansible playbooks to fetch generated configurations from Infrahub and deploy them to network devices [Blog posthttps://opsmill.com/blog/how-to-turn-your-source-of-truth-into-a-service-factory/](https://opsmill.com/blog/how-to-turn-your-source-of-truth-into-a-service-factory/) ## Step 1: Request a new service (service engineer role)[​](#step-1-request-a-new-service-service-engineer-role "Direct link to Step 1: Request a new service (service engineer role)") ### Understanding the service catalog interface[​](#understanding-the-service-catalog-interface "Direct link to Understanding the service catalog interface") The Service Catalog provides a simplified, user-friendly interface for requesting network services without requiring deep technical knowledge. ### Create a dedicated internet service request[​](#create-a-dedicated-internet-service-request "Direct link to Create a dedicated internet service request") 1. **Access the Service Catalog** * Open your browser to `http://localhost:8501` * You'll see the main dashboard with available service types 2. **Navigate to Service Creation** * Click on the "🔌 Dedicated Internet" card from the home page * Or use the sidebar navigation to go to "Dedicated Internet" 3. **Fill Out the Service Request Form** The form presents user-friendly options that hide technical complexity: * **Service Identifier**: Choose a unique name (for example: "DI-DEMO-001") * **Account Reference**: Customer or project identifier * **Location**: Select a site within a location (for example: "Paris 1", "Brussels 1", "New York 1", "Dallas 1") * **Bandwidth**: Choose from human-readable options: * "Ten Gigabits" (10 Gbps) * "One Gigabit" (1 Gbps) * "Hundred Megabits" (100 Mbps) * **IP Package**: Select based on customer needs: * "Small" (6 usable IPs) * "Medium" (14 usable IPs) * "Large" (30 usable IPs) 4. **Submit the Request** * Click "Submit" to create the service request * The system creates a new branch in Infrahub for this change ![Streamlit Form](/assets/images/form-dfaa078ad523dfcaa985c5bc4388cb42.png) What Happens Behind the Scenes When you submit the request: 1. A new git branch is created in Infrahub 2. The service object is created with "draft" status 3. A generator is triggered to allocate resources 4. A "Proposed Change" is created for review ### Track your service requests[​](#track-your-service-requests "Direct link to Track your service requests") 1. **View All Requests** * Navigate to "📦 Service Requests" in the sidebar * You'll see a table of all service requests with their current status 2. **Monitor Request Progress** The table shows key information: * **Service ID**: Your unique identifier * **Status**: Current state (Draft, In Delivery, Active) * **Location**: Where the service will be deployed * **Bandwidth & IP Package**: Technical specifications * **Account**: Customer reference 3. **Understanding Status Progression** * **Draft**: Initial request submitted, awaiting processing * **In Delivery**: Resources allocated, implementation in progress * **Active**: Service fully provisioned and operational In this example, the Service Request is in Draft state. ![Service Requests](/assets/images/service_requests-7add0941927656196a3950d16a0fc29b.png) Why Two Interfaces? The Service Catalog provides a simplified view for business users, while Infrahub offers detailed technical views for network engineers. This separation allows each role to work with appropriate tools. ## Step 2: Review infrastructure state (network architect role)[​](#step-2-review-infrastructure-state-network-architect-role "Direct link to Step 2: Review infrastructure state (network architect role)") ### Understanding Infrahub's role[​](#understanding-infrahubs-role "Direct link to Understanding Infrahub's role") Infrahub serves as the "source of truth" for your network infrastructure, maintaining detailed models of: * Physical locations and equipment * Network configurations and relationships * Service allocations and dependencies * Change history and version control ### Access the Infrahub interface[​](#access-the-infrahub-interface "Direct link to Access the Infrahub interface") 1. **Log into Infrahub** * Navigate to `http://localhost:8000` * Username: `admin` * Password: `infrahub` 2. **Explore the Infrastructure Model** The left sidebar organizes infrastructure data by category: **Locations** * Countries, metros, and sites * Geographic hierarchy for service placement **DCIM (Data Center Infrastructure Management)** * Devices (switches, routers, servers) * Interfaces and connections * Hardware specifications **IPAM (IP Address Management)** * IP prefixes and addresses * VLANs and L2 domains * Resource pools for allocation **Services** * Service definitions and instances * Resource assignments * Customer relationships 3. **Examine Site Details** * Click `Location` > `Site` to see all sites * Select a site (for example: "Paris 1") * Review the site's components: * Associated devices and their roles * Current VLAN assignments * Available resource pools * Existing services ![Site View](/assets/images/site-da02a5777ea882adf9d47fc0a172674c.png) Infrastructure as Code Benefits By modeling infrastructure in Infrahub: * All changes are version-controlled * Relationships between components are maintained * Resource conflicts are automatically prevented * Changes can be tested before implementation ### Review and approve service implementation[​](#review-and-approve-service-implementation "Direct link to Review and approve service implementation") 1. **Access Proposed Changes** * Navigate to `Proposed Changes` in the left sidebar * You'll see the service request you created earlier * Each request creates a separate branch for safe testing 2. **Examine the Automated Provisioning** Click on your service request to see detailed tabs: **Overview Tab** * Summary of the proposed change * Author and timestamp information * Current status and available actions **Checks Tab** * Automated validation results * Generator execution status (should show success) * Any constraint violations or conflicts **Data Tab** * Original service request data * Your form inputs (location, bandwidth, etc.) * Service metadata and relationships 3. **Review Resource Allocation** * Click "Refresh Diff" to see what the generator created * Examine the provisioned resources: **VLAN Assignment** ``` New VLAN: vlan__DI-DEMO-001 ID: 142 (allocated from pool) Role: customer Status: active ``` **IP Prefix Allocation** ``` New Prefix: 10.0.42.0/28 Gateway: 10.0.42.1 Usable IPs: 10.0.42.2 - 10.0.42.14 ``` **Interface Configuration** ``` Switch Port: ge-0/0/5 on core-switch-1 Speed: 1000 Mbps VLAN: 142 (untagged) Status: active ``` **Router Interface** ``` Interface: vlan_142 on edge-router-1 IP Address: 10.0.42.1/28 Status: active ``` 4. **Approve the Implementation** * Return to the "Overview" tab * Review the summary of changes * Click "Merge" to approve and implement * The branch merges into main, making changes live ![Proposed Change](/assets/images/proposed_change-c128332802247f048fd4d9b2e4810332.png) Approval Workflow Benefits This review process provides: * **Safety**: Changes tested in isolation before going live * **Visibility**: Full resource allocation visible before approval * **Audit Trail**: Complete history of who approved what and when * **Rollback**: Ability to revert changes if issues arise ## Step 3: Verify service delivery[​](#step-3-verify-service-delivery "Direct link to Step 3: Verify service delivery") ### Confirm active service[​](#confirm-active-service "Direct link to Confirm active service") 1. **Check Service Status** * Return to the Service Catalog (`http://localhost:8501`) * Navigate to "Service Requests" * Your service should now show "Active" status 2. **Review Allocated Resources** * In Infrahub, navigate to the service under `Service` > `Dedicated Internet` * Click on your service to see all allocated components * Verify relationships to VLANs, interfaces, and IP addresses 3. **Understand the Customer Experience** At this point, a real customer would receive: * **Connection Details**: Switch port and patch panel location * **IP Configuration**: Gateway IP, subnet mask, and usable IP range * **VLAN Information**: VLAN ID for their traffic * **Support Contacts**: Who to contact for service issues ## Step 4: Deploy the configurations[​](#step-4-deploy-the-configurations "Direct link to Step 4: Deploy the configurations") After the proposed change is merged, you can use Semaphore to deploy the generated configurations to network devices. ### Access the Semaphore UI[​](#access-the-semaphore-ui "Direct link to Access the Semaphore UI") 1. **Open Semaphore** * Navigate to `http://localhost:3000` * Username: `admin` * Password: `semaphore` 2. **Locate the Deployment Task** * Find the deployment project in the Semaphore dashboard * The project is configured with the `deploy.yml` playbook and the dynamic Infrahub inventory ### Run the deployment playbook[​](#run-the-deployment-playbook "Direct link to Run the deployment playbook") 1. **Start a Task Run** * Click "Run" to launch the deployment playbook * Semaphore uses the dynamic inventory plugin to query Infrahub for all devices and their attributes 2. **Monitor Real-Time Output** * Watch the live output as Ansible connects to each device * The playbook fetches the `startup-config` artifact from Infrahub for each device * Configurations are saved locally (and in a real environment, pushed to devices) 3. **Verify Deployment Success** * Check that all tasks complete with "ok" or "changed" status * Review the output for any errors or skipped hosts * Confirm that configuration files were generated for each device What the Playbook Does The `deploy.yml` playbook performs two tasks per device: 1. **Fetch configuration artifact** — Uses the `opsmill.infrahub.artifact_fetch` module to retrieve the `startup-config` artifact from Infrahub 2. **Save configuration** — Writes the configuration to a local file for deployment Workflow Complete! You've successfully: 1. ✅ Requested a service through the self-service portal 2. ✅ Reviewed the automated resource allocation 3. ✅ Approved the implementation as a network architect 4. ✅ Delivered a fully configured network service 5. ✅ Deployed configurations to network devices via Semaphore All in minutes, with zero manual configuration errors! ## Key takeaways[​](#key-takeaways "Direct link to Key takeaways") ### Business benefits[​](#business-benefits "Direct link to Business benefits") * **Speed**: Service delivery in minutes instead of days/weeks * **Consistency**: Standardized configurations eliminate human error * **Self-Service**: Reduces bottlenecks and improves customer satisfaction * **Visibility**: Complete audit trail and resource tracking ### Technical benefits[​](#technical-benefits "Direct link to Technical benefits") * **Resource Management**: Automatic allocation prevents conflicts * **Version Control**: All changes tracked and reversible * **Validation**: Automated checks prevent invalid configurations * **Integration**: API-driven architecture enables broader automation ### Next steps[​](#next-steps "Direct link to Next steps") Now that you understand the user experience: * Explore the [developer walkthrough](/demo-service-catalog/getting-started/developer-walkthrough.md) to understand the technical implementation * Try creating additional service types or modifying existing ones * Consider how similar patterns could apply to your organization's services --- # Welcome to Emma Emma is an experimental AI-powered assistant designed to help you interact with Infrahub, OpsMill's next-generation infrastructure management platform. Emma helps you manage infrastructure schemas and data through an intuitive web interface. ![Emma Homepage](/assets/images/home_page-688ffc9365ffba7db2bec7b3ee5c6c4f.png) Experimental Software Emma is experimental by design and may not work as expected. She tests out new ideas before her big brother Otto gets involved and implements them in Infrahub. ## What Emma can do[​](#what-emma-can-do "Direct link to What Emma can do") Emma provides powerful tools for infrastructure management: ### 🏗️ **Schema management**[​](#️-schema-management "Direct link to ️-schema-management") * **[AI Schema Builder](/emma/features/schema-builder.md)** - Create schemas using natural language descriptions * **[Schema Library](https://docs.infrahub.app/schema-library)** - Browse and use community-validated schemas * **[Schema Loader](/emma/features/schema-management.md)** - Load schemas into your Infrahub instance * **[Schema Visualizer](/emma/features/schema-management.md)** - Visual representation of schema relationships ### 📊 **Data operations**[​](#-data-operations "Direct link to -data-operations") * **[Data Import](/emma/features/data-import-export.md)** - Import CSV data into Infrahub with intelligent mapping * **[Data Export](/emma/features/data-import-export.md)** - Export Infrahub data to CSV for analysis and backup ### 🔬 **Experimental features**[​](#-experimental-features "Direct link to -experimental-features") * **Query Builder** - Interactive GraphQL query interface (feature flag required) * **Template Builder** - Create and manage infrastructure templates (feature flag required) ## Quick start[​](#quick-start "Direct link to Quick start") Ready to get started? Choose your preferred installation method: ### 🚀 **Fastest setup**[​](#-fastest-setup "Direct link to -fastest-setup") Use the one-command quickstart to get Emma and Infrahub running together: ``` curl https://infrahub.opsmill.io/latest-emma | docker compose -f - up -d ``` Then open [Emma](http://localhost:8501) and [Infrahub](http://localhost:8000) in your browser. ### 📚 **Full installation guide**[​](#-full-installation-guide "Direct link to -full-installation-guide") For detailed installation instructions, including local development setup and production deployments, see the **[Installation Guide](/emma/getting-started/installation.md)**. ## Documentation structure[​](#documentation-structure "Direct link to Documentation structure") ### 🚀 Getting started[​](#-getting-started "Direct link to 🚀 Getting started") Perfect for new users: * **[Installation](/emma/getting-started/installation.md)** - Set up Emma in various environments * **[Configuration](/emma/getting-started/configuration.md)** - Configure Emma and connect to Infrahub * **[First Steps](/emma/getting-started/first-steps.md)** - Your first experience with Emma ### ⚙️ Features[​](#️-features "Direct link to ⚙️ Features") Deep dives into Emma's capabilities: * **[AI Schema Builder](/emma/features/schema-builder.md)** - AI-powered schema creation * **[Data Import & Export](/emma/features/data-import-export.md)** - Comprehensive data management * **[Schema Management](/emma/features/schema-management.md)** - Complete schema lifecycle * **[Schema Library](https://docs.infrahub.app/schema-library)** - Community schema collection ### 📖 Guides[​](#-guides "Direct link to 📖 Guides") Step-by-step tutorials: * **[Building Your First Schema](/emma/guides/building-your-first-schema.md)** - Complete walkthrough ### 📚 Reference[​](#-reference "Direct link to 📚 Reference") Complete reference materials: * **[Configuration Reference](/emma/reference/configuration.md)** - All configuration options * **[Feature Flags](/emma/reference/feature-flags.md)** - Experimental feature management * **[Troubleshooting](/emma/reference/troubleshooting.md)** - Solutions to common issues ## Common use cases[​](#common-use-cases "Direct link to Common use cases") ### Infrastructure modeling[​](#infrastructure-modeling "Direct link to Infrastructure modeling") Start with Emma's Schema Library to model your infrastructure: 1. Browse [pre-built schemas](https://docs.infrahub.app/schema-library) for common components 2. Use the [AI Schema Builder](/emma/features/schema-builder.md) for custom requirements 3. [Load schemas](/emma/features/schema-management.md) into your Infrahub instance 4. [Visualize relationships](/emma/features/schema-management.md) to understand your model ### Data migration[​](#data-migration "Direct link to Data migration") Migrate from existing systems using Emma's data tools: 1. Export data from your current systems to CSV 2. Use Emma's [Data Importer](/emma/features/data-import-export.md) to map and load data 3. Validate and clean data during the import process 4. Export data for backup and integration with other tools ### Schema development[​](#schema-development "Direct link to Schema development") Develop and iterate on infrastructure schemas: 1. [Plan your schema](/emma/guides/building-your-first-schema.md) requirements 2. Use AI assistance to generate initial schema definitions 3. Test and refine schemas with sample data 4. Share schemas with your team through the library ## Getting help[​](#getting-help "Direct link to Getting help") ### 📖 Documentation[​](#-documentation "Direct link to 📖 Documentation") This documentation covers everything you need to know about Emma. Use the navigation menu to explore topics in detail. ### 🐛 Issues and support[​](#-issues-and-support "Direct link to 🐛 Issues and support") * **Bug Reports**: [GitHub Issues](https://github.com/opsmill/emma/issues) * **General Questions**: [OpsMill Discord Community](https://discord.gg/opsmill) ### 🔗 Related resources[​](#-related-resources "Direct link to 🔗 Related resources") * **[Infrahub Documentation](https://docs.infrahub.app)** - Complete Infrahub documentation * **[Schema Library Repository](https://docs.infrahub.app/schema-library)** - Community schema collection * **[OpsMill Website](https://opsmill.com)** - Learn more about OpsMill ## Next steps[​](#next-steps "Direct link to Next steps") 1. **New to Emma?** Start with the [Installation Guide](/emma/getting-started/installation.md) 2. **Ready to build?** Try [Building Your First Schema](/emma/guides/building-your-first-schema.md) 3. **Need schemas?** Explore the [Schema Library](https://docs.infrahub.app/schema-library) 4. **Have data to import?** Check out [Data Import & Export](/emma/features/data-import-export.md) Emma is designed to make infrastructure management more accessible and efficient. Whether you're modeling a basic network or a complex multi-cloud environment, Emma provides the tools you need to succeed. Happy infrastructure modeling! --- # Data Import and Export Emma provides powerful tools for importing data into Infrahub from CSV files and exporting Infrahub data back to CSV format. This enables efficient migration from existing systems and data sharing between tools. ![Data Exporter](/assets/images/data_exporter-5758ce92cd8b320f30c4da3baca07860.png) ## Data import[​](#data-import "Direct link to Data import") The Data Importer allows you to upload CSV files and map their columns to Infrahub schema attributes, enabling you to bulk-load infrastructure data. ### Supported file formats[​](#supported-file-formats "Direct link to Supported file formats") * **CSV files** with headers * **UTF-8 encoding** (recommended) * **Common delimiters**: comma, semicolon, tab * **File size**: Up to 100MB per upload ### Import process[​](#import-process "Direct link to Import process") #### Step 1: Prepare your data[​](#step-1-prepare-your-data "Direct link to Step 1: Prepare your data") Ensure your CSV file: * **Has column headers** that roughly match your schema attributes * **Contains clean data** with consistent formatting * **Includes required fields** as defined by your schema * **Uses consistent values** for relationships and enums **Example CSV structure:** ``` name,model,serial_number,location,ip_address,status switch-01,Cisco 3850,FCW2140L0EF,datacenter-1,192.168.1.10,active switch-02,Cisco 3850,FCW2140L0EG,datacenter-1,192.168.1.11,active ``` #### Step 2: Upload and map[​](#step-2-upload-and-map "Direct link to Step 2: Upload and map") 1. **Select your CSV file** using the file upload interface 2. **Choose the target schema** from your Infrahub instance 3. **Map CSV columns** to schema attributes using the dropdown selectors 4. **Configure relationship handling** for foreign key references 5. **Set data validation options** like duplicate handling #### Step 3: Preview and validate[​](#step-3-preview-and-validate "Direct link to Step 3: Preview and validate") Emma provides a preview showing: * **Mapped data** - How your CSV data will appear in Infrahub * **Validation results** - Any data quality issues or constraint violations * **Relationship resolution** - How foreign keys will be resolved * **Import statistics** - Number of records to be created/updated #### Step 4: execute import[​](#step-4-execute-import "Direct link to Step 4: execute import") After reviewing the preview: 1. **Confirm the import** if everything looks correct 2. **Monitor progress** through the real-time status updates 3. **Review results** including success/failure counts and any error details ### Advanced import features[​](#advanced-import-features "Direct link to Advanced import features") * Relationship Handling * Data Validation * Performance Options Emma intelligently handles relationships between objects: **By Name/ID Lookup:** * Reference existing objects by name or ID * Automatic resolution of relationship targets * Error reporting for unresolved references **Nested Object Creation:** * Create related objects inline during import * Support for hierarchical data structures * Maintains referential integrity **Example:** ``` device_name,interface_name,ip_address,vlan_name switch-01,GigabitEthernet1/0/1,192.168.1.10,production switch-01,GigabitEthernet1/0/2,192.168.1.11,management ``` Emma validates data during import: **Schema Validation:** * Required field checking * Data type validation * Format validation (IP addresses, emails, etc.) * Enum value verification **Business Rules:** * Uniqueness constraints * Custom validation rules * Cross-field dependencies **Error Handling:** * Detailed error reporting * Skip invalid records option * Partial import support For large datasets: **Batch Processing:** * Configurable batch sizes * Memory-efficient processing * Progress tracking **Incremental Updates:** * Update existing records * Insert new records only * Upsert functionality **Parallel Processing:** * Multiple worker threads * Optimized for large files * Automatic resource management ## Data export[​](#data-export "Direct link to Data export") The Data Exporter extracts data from Infrahub and formats it as CSV files for use in other tools or for backup purposes. ### Export options[​](#export-options "Direct link to Export options") #### Schema-based export[​](#schema-based-export "Direct link to Schema-based export") Export all objects of a specific schema type: 1. **Select schema** from your Infrahub instance 2. **Choose attributes** to include in the export 3. **Configure relationship handling** (include related object details) 4. **Set filtering criteria** to limit exported records #### Query-based export[​](#query-based-export "Direct link to Query-based export") Export data using custom filters: * **Field filters** - Filter by specific attribute values * **Date ranges** - Export data from specific time periods * **Relationship filters** - Filter based on related object properties * **Complex queries** - Combine multiple filter criteria #### Bulk export[​](#bulk-export "Direct link to Bulk export") Export multiple schema types in a single operation: * **Related schemas** - Export parent and child objects together * **Dependency order** - Automatic ordering for reimport compatibility * **Consistent snapshots** - Ensure data consistency across exports ### Export formats[​](#export-formats "Direct link to Export formats") Emma supports multiple CSV format options: **Standard CSV:** * Comma-separated values * Header row with attribute names * UTF-8 encoding **Customizable Format:** * Custom delimiters * Quote character options * Line ending preferences * Character encoding selection **Relationship Handling:** * Flatten relationships into columns * Include relationship IDs * Export related object details ### Advanced export features[​](#advanced-export-features "Direct link to Advanced export features") #### Incremental export[​](#incremental-export "Direct link to Incremental export") Export only changes since last export: * **Timestamp-based** - Export records modified after a specific date * **Change tracking** - Export based on Infrahub's change tracking * **Delta exports** - Include only modified fields #### Scheduled exports[​](#scheduled-exports "Direct link to Scheduled exports") Configure automated exports: * **Recurring schedules** - Daily, weekly, monthly exports * **Event-triggered** - Export on data changes * **Multiple formats** - Generate different exports for different consumers ## Data quality and validation[​](#data-quality-and-validation "Direct link to Data quality and validation") ### Import validation[​](#import-validation "Direct link to Import validation") Emma validates imported data against: * **Schema definitions** - Ensure data matches expected structure * **Data types** - Validate numeric, date, and other typed fields * **Constraints** - Check uniqueness, required fields, and custom rules * **Relationships** - Verify referenced objects exist ### Export verification[​](#export-verification "Direct link to Export verification") Exported data includes: * **Metadata** - Export timestamp, schema versions, record counts * **Integrity checks** - Checksums for data verification * **Audit trail** - Information about data source and transformation ## Best practices[​](#best-practices "Direct link to Best practices") ### Import best practices[​](#import-best-practices "Direct link to Import best practices") 1. **Validate data quality** before import using external tools 2. **Start with small batches** to test mappings and validations 3. **Use consistent naming** for relationship references 4. **Clean up data** to remove duplicates and inconsistencies 5. **Backup Infrahub** before large imports ### Export best practices[​](#export-best-practices "Direct link to Export best practices") 1. **Document export purposes** to choose appropriate options 2. **Test import compatibility** if exporting for reimport 3. **Use incremental exports** for regular synchronization 4. **Include metadata** for audit and tracking purposes 5. **Validate exported data** before using in downstream systems ### Performance optimization[​](#performance-optimization "Direct link to Performance optimization") 1. **Use appropriate batch sizes** (typically 100-1000 records) 2. **Limit concurrent operations** to avoid overwhelming Infrahub 3. **Monitor system resources** during large operations 4. **Use filtering** to export only necessary data 5. **Schedule large operations** during low-usage periods ## Integration scenarios[​](#integration-scenarios "Direct link to Integration scenarios") ### Common use cases[​](#common-use-cases "Direct link to Common use cases") **Migration from Legacy Systems:** * Export data from existing tools to CSV * Transform and clean data as needed * Import into Infrahub schemas **Data Synchronization:** * Regular exports to external systems * Incremental updates based on changes * Bidirectional synchronization workflows **Backup and Recovery:** * Full data exports for backup purposes * Schema-consistent exports for disaster recovery * Point-in-time data snapshots **Reporting and Analytics:** * Export data for business intelligence tools * Create regular data extracts for reporting * Integration with data lakes and warehouses ### Integration with other Emma features[​](#integration-with-other-emma-features "Direct link to Integration with other Emma features") * **Schema Library** - Use library schemas as import targets * **Schema Builder** - Create schemas for imported data * **Schema Visualizer** - Understand data relationships before import/export ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Common import issues[​](#common-import-issues "Direct link to Common import issues") **File Upload Problems:** * Check file size limits (100MB maximum) * Verify file format and encoding * Ensure proper CSV structure with headers **Mapping Errors:** * Verify schema attribute names * Check data type compatibility * Review required field mappings **Validation Failures:** * Check data quality and formatting * Verify relationship references exist * Review constraint violations **Performance Issues:** * Reduce batch sizes * Check network connectivity * Monitor Infrahub resource usage ### Common export issues[​](#common-export-issues "Direct link to Common export issues") **Missing Data:** * Verify user permissions for accessed schemas * Check filtering criteria * Review relationship configurations **Format Problems:** * Verify encoding settings * Check delimiter configuration * Review quote character handling **Performance Issues:** * Use filtering to reduce data volume * Increase timeout settings * Monitor export progress For detailed troubleshooting, see the [Troubleshooting Guide](/emma/reference/troubleshooting.md). --- # AI Schema Builder Emma's Schema Builder is an AI-powered tool that helps you create Infrahub schemas using natural language descriptions. Instead of manually writing YAML schema definitions, you can describe what you need and let Emma's AI assistant generate the appropriate schema structure. ![Schema Builder](/assets/images/schema_builder-525dc0ecdbbdd761a54677abe2f9f557.png) ## How it works[​](#how-it-works "Direct link to How it works") The Schema Builder leverages OpenAI's language models to understand infrastructure concepts and translate natural language requirements into properly structured Infrahub schemas. It understands: * Common infrastructure components (devices, interfaces, IP addresses, etc.) * Relationships between infrastructure objects * Industry-standard naming conventions and best practices * Infrahub schema syntax and requirements ## Getting started[​](#getting-started "Direct link to Getting started") ### Prerequisites[​](#prerequisites "Direct link to Prerequisites") To use the AI Schema Builder, you need: * Basic understanding of your infrastructure requirements * Knowledge of how your schema fits into your overall infrastructure model ### Basic workflow[​](#basic-workflow "Direct link to Basic workflow") 1. **Describe your schema** in natural language 2. **Review the AI-generated schema** for accuracy and completeness 3. **Refine your description** if needed to get better results 4. **Generate the final schema** and optionally load it into Infrahub ## Creating your first schema[​](#creating-your-first-schema "Direct link to Creating your first schema") ### Step 1: Describe your requirements[​](#step-1-describe-your-requirements "Direct link to Step 1: Describe your requirements") In the Schema Builder interface, describe what you want to model. Be specific about: * **Object type**: What kind of infrastructure component * **Attributes**: What properties it should have * **Relationships**: How it connects to other objects * **Constraints**: Any validation rules or requirements **Example descriptions:** ``` Create a schema for network switches that includes: - Basic device information (name, model, serial number) - Management IP address - Physical location reference - Multiple network interfaces - VLAN configuration capabilities ``` ``` Model a cloud instance with: - Instance type and size - Operating system details - Network interfaces with public/private IPs - Associated security groups - Tags for metadata ``` ### Step 2: Review generated schema[​](#step-2-review-generated-schema "Direct link to Step 2: Review generated schema") The AI will generate a complete Infrahub schema in YAML format. Review: * **Schema name and namespace** - Ensure they follow your naming conventions * **Attributes** - Check data types and validation rules * **Relationships** - Verify connections to other schemas make sense * **Inheritance** - Confirm the schema inherits from appropriate base schemas ### Step 3: Refine and iterate[​](#step-3-refine-and-iterate "Direct link to Step 3: Refine and iterate") If the generated schema isn't quite right: * **Add more details** to your description * **Specify constraints** more clearly * **Mention missing attributes** or relationships * **Reference existing schemas** that should be related The AI learns from your feedback and will generate improved schemas. ## Advanced features[​](#advanced-features "Direct link to Advanced features") ### Schema inheritance[​](#schema-inheritance "Direct link to Schema inheritance") Emma understands Infrahub's inheritance model. You can specify base schemas: ``` Create a server schema that inherits from the base Device schema and adds CPU, memory, and storage specifications ``` ### Complex relationships[​](#complex-relationships "Direct link to Complex relationships") Define sophisticated relationships between schemas: ``` Create a cluster schema that: - Has multiple nodes (many-to-one relationship with servers) - Belongs to a single environment - Can span multiple availability zones - Has a load balancer that references multiple backend servers ``` ### Validation rules[​](#validation-rules "Direct link to Validation rules") Specify validation requirements: ``` Create an IP address schema with: - IPv4 and IPv6 address validation - Subnet mask validation - Ensure addresses are within allowed ranges - Prevent duplicate address assignment ``` ## Best practices[​](#best-practices "Direct link to Best practices") ### Writing effective descriptions[​](#writing-effective-descriptions "Direct link to Writing effective descriptions") 1. **Be specific**: Vague descriptions lead to generic schemas 2. **Use industry terms**: The AI understands networking, cloud, and infrastructure terminology 3. **Mention relationships**: Always describe how your schema connects to others 4. **Consider the data lifecycle**: Think about how data will be created, updated, and deleted ### Schema design principles[​](#schema-design-principles "Direct link to Schema design principles") 1. **Start with basics**: Begin with basic attributes and add complexity iteratively 2. **Follow conventions**: Use consistent naming and follow Infrahub best practices 3. **Consider inheritance**: Leverage base schemas to avoid duplication 4. **Plan relationships**: Think about how schemas connect before creating them ### Integration with existing schemas[​](#integration-with-existing-schemas "Direct link to Integration with existing schemas") * **Reference the Schema Library**: Mention existing schemas in your descriptions * **Check dependencies**: Ensure required base schemas are loaded first * **Validate relationships**: Verify that referenced schemas exist in your Infrahub instance * **Test thoroughly**: Always test generated schemas before using in production ## Limitations and considerations[​](#limitations-and-considerations "Direct link to Limitations and considerations") ### AI limitations[​](#ai-limitations "Direct link to AI limitations") * **Context awareness**: The AI doesn't know your specific environment setup * **Validation**: Always review generated schemas for accuracy * **Best practices**: The AI follows general practices but may not match your specific standards * **Complex logic**: Very complex business rules may need manual refinement ### Schema complexity[​](#schema-complexity "Direct link to Schema complexity") * **Start with core attributes**: Complex schemas with many relationships can be challenging * **Iterate**: Build complex schemas in stages * **Test**: Validate each iteration before adding more complexity ### Performance considerations[​](#performance-considerations "Direct link to Performance considerations") * **API calls**: Each generation requires an OpenAI API call * **Response time**: Complex descriptions may take longer to process * **Rate limits**: Be aware of OpenAI API rate limitations ## Integration with other Emma features[​](#integration-with-other-emma-features "Direct link to Integration with other Emma features") The Schema Builder integrates seamlessly with other Emma features: * **Schema Loader**: Generated schemas can be loaded directly into Infrahub * **Schema Visualizer**: View the structure and relationships of generated schemas * **Data Importer**: Use generated schemas as targets for data import * **Schema Library**: Save generated schemas to your local library ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Common issues[​](#common-issues "Direct link to Common issues") **AI generates incorrect schemas:** * Provide more specific descriptions * Include examples of desired attributes * Reference existing schemas for context **Missing relationships:** * Explicitly mention all required relationships * Verify referenced schemas exist * Check relationship cardinality requirements **Validation errors:** * Review Infrahub schema requirements * Check attribute data types * Verify namespace and naming conventions **Performance issues:** * Simplify complex descriptions * Break large schemas into smaller components * Check network connectivity to OpenAI API For more troubleshooting help, see the [Troubleshooting Guide](/emma/reference/troubleshooting.md). ## Next steps[​](#next-steps "Direct link to Next steps") * Try the [Building Your First Schema](/emma/guides/building-your-first-schema.md) guide * Explore the [Schema Library](https://docs.infrahub.app/schema-library) for examples and templates * Learn about [Schema Management](/emma/features/schema-management.md) best practices --- # Schema Management Emma provides comprehensive tools for managing Infrahub schemas throughout their lifecycle. From loading and visualizing schemas to organizing them in libraries, Emma makes schema management accessible and efficient. ## Overview[​](#overview "Direct link to Overview") Schema management in Emma encompasses: * **Schema Loading** - Installing schemas into your Infrahub instance * **Schema Visualization** - Understanding schema relationships and structure * **Schema Library** - Organizing and browsing available schemas * **Version Control** - Managing schema changes over time ## Schema loader[​](#schema-loader "Direct link to Schema loader") The Schema Loader allows you to install schemas into your Infrahub instance from various sources. ![Schema Loader](/assets/images/schema_loader-48332680a3b72f079c92cf7f1df1b076.png) ### Loading sources[​](#loading-sources "Direct link to Loading sources") * Schema Library * YAML Editor * File Upload Load schemas from Emma's built-in library: 1. **Browse available schemas** in categories (Base, Extensions, Experimental) 2. **Select schemas** to load (multiple selection supported) 3. **Review dependencies** - Emma shows required base schemas 4. **Load in dependency order** - Emma automatically orders schema loading **Benefits:** * Pre-tested, community-validated schemas * Proper dependency management * Documentation and examples included * Regular updates and improvements Load schemas from YAML definitions: 1. **Paste or type YAML** directly into the editor 2. **Validate syntax** - Emma checks YAML formatting 3. **Preview schema structure** before loading 4. **Load individual or multiple schemas** from a single YAML file **Use cases:** * Loading custom schemas * Testing schema modifications * Importing schemas from other sources * Quick schema prototyping Upload schema files from your local system: 1. **Select YAML files** (single or multiple) 2. **Review file contents** before loading 3. **Validate and load** with dependency checking 4. **Track loading progress** for multiple files **Supported formats:** * Individual .yaml or .yml files * Multiple files in zip archives * Schema collections from exports ### Dependency management[​](#dependency-management "Direct link to Dependency management") Emma automatically handles schema dependencies: **Dependency Detection:** * Analyzes `inherit_from` relationships * Identifies referenced schemas in relationships * Detects circular dependencies **Load Ordering:** * Loads base schemas before dependent schemas * Provides clear dependency visualization * Prevents load failures due to missing dependencies **Conflict Resolution:** * Warns about schema name conflicts * Handles version differences * Provides override options for updates ### Validation and error handling[​](#validation-and-error-handling "Direct link to Validation and error handling") The Schema Loader validates schemas before installation: **Syntax Validation:** * YAML formatting and structure * Required field presence * Data type correctness **Semantic Validation:** * Schema naming conventions * Relationship target validity * Attribute definition consistency **Infrahub Validation:** * Compatibility with Infrahub version * Permission checks * Namespace conflicts ## Schema visualizer[​](#schema-visualizer "Direct link to Schema visualizer") The Schema Visualizer provides graphical representations of your schema structure and relationships. ![Schema Visualizer](/assets/images/schema_visualizer-7b17e46228491d6e7a2c4f3e29e05ae1.png) ### Visualization types[​](#visualization-types "Direct link to Visualization types") **Graph View:** * Interactive network diagram of schemas * Shows inheritance and relationship connections * Allows zooming and panning for large schema sets * Color-coded by schema type or namespace **Tree View:** * Hierarchical display of schema inheritance * Expandable/collapsible nodes * Shows parent-child relationships clearly * Useful for understanding schema hierarchies **List View:** * Tabular display of all schemas * Sortable and filterable columns * Shows key schema metadata * Quick access to schema details ### Interactive features[​](#interactive-features "Direct link to Interactive features") **Schema Exploration:** * Click schemas to view detailed information * Hover for quick attribute and relationship previews * Expand/collapse related schema groups * Navigate between connected schemas **Filtering and Search:** * Filter by schema type, namespace, or category * Search by schema name or description * Hide/show specific relationship types * Focus on schema subsets **Export Options:** * Export visualizations as images (PNG, SVG) * Generate schema documentation * Create relationship reports * Export schema lists as CSV ### Analysis tools[​](#analysis-tools "Direct link to Analysis tools") **Impact Analysis:** * Show what schemas depend on a selected schema * Identify unused schemas * Find circular dependencies * Analyze schema complexity metrics **Relationship Analysis:** * Count incoming and outgoing relationships * Identify highly connected schemas * Find isolated schema groups * Analyze relationship patterns ## Schema library integration[​](#schema-library-integration "Direct link to Schema library integration") Emma's Schema Library provides curated, ready-to-use schemas for common infrastructure components. ### Library organization[​](#library-organization "Direct link to Library organization") **Base Schemas:** * Fundamental infrastructure components * Required by most other schemas * Examples: Device, Location, Organization, Person **Extensions:** * Specialized schemas for specific domains * Build on base schemas * Examples: Routing protocols, Security policies, Cloud resources **Experimental:** * Cutting-edge schemas for new technologies * Community contributions * May change significantly ### Using library schemas[​](#using-library-schemas "Direct link to Using library schemas") **Browse and Discover:** * Search by category, technology, or use case * View schema descriptions and examples * Check compatibility with your environment * Review community ratings and feedback **Customize and Extend:** * Copy library schemas as starting points * Modify attributes and relationships * Add organization-specific extensions * Maintain local variations **Contribute Back:** * Share useful schemas with the community * Submit improvements to existing schemas * Participate in schema standardization efforts ## Best practices[​](#best-practices "Direct link to Best practices") ### Schema design[​](#schema-design "Direct link to Schema design") **Start with Standards:** * Use library schemas as foundations * Follow established naming conventions * Design for reusability and extension * Document schema purposes and constraints **Plan Dependencies:** * Load base schemas first * Minimize circular dependencies * Use inheritance appropriately * Design clear relationship hierarchies **Version Management:** * Use Infrahub branches for schema changes * Test schema modifications thoroughly * Plan migration strategies for breaking changes * Maintain schema documentation ### Performance considerations[​](#performance-considerations "Direct link to Performance considerations") **Large Schema Sets:** * Load schemas in batches * Use dependency ordering * Monitor Infrahub performance during loads * Consider schema consolidation **Complex Relationships:** * Avoid overly complex relationship networks * Use visualization tools to understand complexity * Simplify where possible * Document complex relationships clearly ### Team collaboration[​](#team-collaboration "Direct link to Team collaboration") **Schema Governance:** * Establish schema review processes * Define naming and design standards * Use version control for schema files * Document schema decisions and rationale **Knowledge Sharing:** * Use schema visualization for training * Maintain schema documentation * Share library schemas across teams * Regular schema review meetings ## Advanced features[​](#advanced-features "Direct link to Advanced features") ### Schema diff and comparison[​](#schema-diff-and-comparison "Direct link to Schema diff and comparison") Compare schemas across branches or versions: * Side-by-side schema comparisons * Highlight attribute and relationship changes * Impact analysis for modifications * Migration planning assistance ### Bulk operations[​](#bulk-operations "Direct link to Bulk operations") Manage multiple schemas efficiently: * Bulk schema loading and updates * Batch validation and testing * Mass schema exports * Automated dependency resolution ### Integration APIs[​](#integration-apis "Direct link to Integration APIs") Programmatic schema management: * REST APIs for schema operations * GraphQL queries for schema metadata * Webhook notifications for schema changes * CI/CD integration capabilities ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Common issues[​](#common-issues "Direct link to Common issues") **Schema Load Failures:** * Check dependency requirements * Verify YAML syntax * Ensure proper permissions * Review Infrahub compatibility **Visualization Problems:** * Clear browser cache * Check for large schema sets * Verify network connectivity * Update browser if needed **Performance Issues:** * Reduce loaded schema count * Use filtering in visualizations * Load schemas in smaller batches * Monitor system resources For detailed troubleshooting, see the [Troubleshooting Guide](/emma/reference/troubleshooting.md). ## Next steps[​](#next-steps "Direct link to Next steps") * Explore the [Schema Library](https://docs.infrahub.app/schema-library) for available schemas * Try [Building Your First Schema](/emma/guides/building-your-first-schema.md) * Learn about [Data Import & Export](/emma/features/data-import-export.md) for populating your schemas * Check out the [Schema Builder](/emma/features/schema-builder.md) for creating custom schemas --- # Configuration Emma connects to Infrahub using standard environment variables and configuration options. This guide covers all the ways to configure Emma for your environment. ## Environment variables[​](#environment-variables "Direct link to Environment variables") Emma uses the same environment variables as other Infrahub tools for consistency: ### Required configuration[​](#required-configuration "Direct link to Required configuration") | Variable | Description | Example | | -------------------- | ----------------------------- | -------------------------------------- | | `INFRAHUB_ADDRESS` | URL of your Infrahub instance | `http://localhost:8000` | | `INFRAHUB_API_TOKEN` | API token for authentication | `06438eb2-8019-4776-878c-0941b1f1d1ec` | ### Optional configuration[​](#optional-configuration "Direct link to Optional configuration") | Variable | Description | Default | Example | | -------------------- | --------------------------------------------- | ------- | -------------------------------- | | `EMMA_FEATURE_FLAGS` | Comma-separated list of experimental features | `""` | `query_builder,template_builder` | | `INFRAHUB_TIMEOUT` | API request timeout in seconds | `30` | `60` | ## Setting environment variables[​](#setting-environment-variables "Direct link to Setting environment variables") * Local Development * Docker * Production For local development, create a `.env` file in the Emma root directory: ``` # .env file INFRAHUB_ADDRESS=http://localhost:8000 INFRAHUB_API_TOKEN=your-api-token-here EMMA_FEATURE_FLAGS=query_builder ``` Or export them in your shell: ``` export INFRAHUB_ADDRESS="http://localhost:8000" export INFRAHUB_API_TOKEN="06438eb2-8019-4776-878c-0941b1f1d1ec" export EMMA_FEATURE_FLAGS="query_builder,template_builder" ``` When using Docker, set environment variables in your `docker-compose.yml`: ``` version: '3.8' services: emma: build: . ports: - "8501:8501" environment: - INFRAHUB_ADDRESS=http://infrahub:8000 - INFRAHUB_API_TOKEN=your-api-token-here - EMMA_FEATURE_FLAGS=query_builder ``` Or use an environment file: ``` services: emma: build: . env_file: - .env ``` For production deployments, use secure methods to inject environment variables: **Kubernetes:** ``` apiVersion: apps/v1 kind: Deployment metadata: name: emma spec: template: spec: containers: - name: emma env: - name: INFRAHUB_ADDRESS value: "https://infrahub.example.com" - name: INFRAHUB_API_TOKEN valueFrom: secretKeyRef: name: emma-secrets key: api-token ``` **Docker Swarm:** ``` version: '3.8' services: emma: environment: - INFRAHUB_ADDRESS=https://infrahub.example.com - INFRAHUB_API_TOKEN_FILE=/run/secrets/api_token secrets: - api_token ``` ## Getting an API token[​](#getting-an-api-token "Direct link to Getting an API token") To get an API token from Infrahub: 1. **Log into your Infrahub instance** at your `INFRAHUB_ADDRESS` 2. **Navigate to Settings** → **API Tokens** 3. **Create a new token** with appropriate permissions 4. **Copy the token** and use it as your `INFRAHUB_API_TOKEN` warning API tokens provide full access to your Infrahub instance. Store them securely and never commit them to version control. ## In-app configuration[​](#in-app-configuration "Direct link to In-app configuration") Emma also provides configuration options within the web interface: ### Infrahub connection settings[​](#infrahub-connection-settings "Direct link to Infrahub connection settings") * **Address**: Override the environment variable for the current session * **Branch**: Select which Infrahub branch to work with * **Timeout**: Adjust request timeout for slow connections ### AI assistant settings[​](#ai-assistant-settings "Direct link to AI assistant settings") * **OpenAI API Key**: Configure AI features for schema generation * **Model Selection**: Choose between different AI models * **Temperature**: Adjust AI creativity level Access these settings through the sidebar in any Emma page. ## Feature flags[​](#feature-flags "Direct link to Feature flags") Emma uses feature flags to enable experimental functionality: | Flag | Description | Status | | ------------------ | ------------------------------- | ------ | | `query_builder` | GraphQL query builder interface | Alpha | | `template_builder` | Template creation tools | Alpha | Enable feature flags by setting `EMMA_FEATURE_FLAGS`: ``` export EMMA_FEATURE_FLAGS="query_builder,template_builder" ``` ## Next steps[​](#next-steps "Direct link to Next steps") With Emma configured, you're ready to take your [First Steps](/emma/getting-started/first-steps.md) with the application. ## Configuration validation[​](#configuration-validation "Direct link to Configuration validation") Emma validates your configuration on startup. Common configuration issues: * **Invalid Infrahub address**: Check the URL format and network connectivity * **Authentication errors**: Verify your API token is valid and has necessary permissions * **Connection timeouts**: Increase timeout values for slow networks For detailed troubleshooting, see the [Troubleshooting Guide](/emma/reference/troubleshooting.md). --- # First Steps Welcome to Emma! This guide will walk you through your first experience using Emma to interact with Infrahub. ## Opening Emma[​](#opening-emma "Direct link to Opening Emma") 1. **Navigate to Emma** at `http://localhost:8501` (or your configured port) 2. **Verify connection** - Emma should show a connection status to your Infrahub instance 3. **Select a branch** - Choose which Infrahub branch to work with (usually `main`) ![Emma Homepage](/assets/images/home_page-688ffc9365ffba7db2bec7b3ee5c6c4f.png) ## Understanding Emma's interface[​](#understanding-emmas-interface "Direct link to Understanding Emma's interface") Emma is organized into several key areas accessible from the sidebar: ### Core features[​](#core-features "Direct link to Core features") * **🏗️ Schema Builder** - AI-powered schema creation and editing * **📁 Schema Library** - Browse and use pre-built schema templates * **🔄 Schema Loader** - Load schemas into your Infrahub instance * **👁️ Schema Visualizer** - Visual representation of your schemas * **📥 Data Importer** - Import CSV data into Infrahub * **📤 Data Exporter** - Export Infrahub data to CSV ### Experimental features[​](#experimental-features "Direct link to Experimental features") * **🔍 Query Builder** - Build GraphQL queries (feature flag required) * **📋 Template Builder** - Create templates (feature flag required) ## Your first task: explore the schema library[​](#your-first-task-explore-the-schema-library "Direct link to Your first task: explore the schema library") Let's start by exploring Emma's schema library: 1. **Click "Schema Library"** in the sidebar 2. **Browse available schemas** - You'll see categories like Base, Extensions, and Experimental 3. **Select a schema** to view its details, such as the "Device" schema from Base/DCIM 4. **Review the schema structure** - Notice how it defines attributes, relationships, and inheritance This gives you an understanding of how Infrahub schemas are structured. ## Load your first schema[​](#load-your-first-schema "Direct link to Load your first schema") Next, let's load a schema into Infrahub: 1. **Navigate to "Schema Loader"** 2. **Select schemas** from the library (start with Base schemas like Organization and Location) 3. **Preview the schema** to understand what will be loaded 4. **Click "Load Schema"** to add it to your Infrahub instance tip Start with foundational schemas like Organization and Location before adding more complex schemas that depend on them. ## Visualize your schema[​](#visualize-your-schema "Direct link to Visualize your schema") After loading schemas: 1. **Go to "Schema Visualizer"** 2. **View the graphical representation** of your schema relationships 3. **Explore connections** between different schema objects 4. **Use filters** to focus on specific schema types or relationships ## Import sample data[​](#import-sample-data "Direct link to Import sample data") To see Emma in action with real data: 1. **Prepare a CSV file** with data matching your schema structure 2. **Navigate to "Data Importer"** 3. **Upload your CSV** and map columns to schema attributes 4. **Preview the import** to verify mappings are correct 5. **Execute the import** to add data to Infrahub ## Key concepts to understand[​](#key-concepts-to-understand "Direct link to Key concepts to understand") ### Schemas vs data[​](#schemas-vs-data "Direct link to Schemas vs data") * **Schemas** define the structure and relationships of your infrastructure models * **Data** is the actual instances that follow those schemas (specific devices, locations, etc.) ### Branches[​](#branches "Direct link to Branches") * Emma works with Infrahub branches, similar to Git branches * Changes can be made in development branches before merging to main * Always be aware of which branch you're working in ### AI integration[​](#ai-integration "Direct link to AI integration") * Emma's Schema Builder uses AI to help create schemas from natural language * The AI understands infrastructure concepts and can suggest appropriate attributes and relationships * Review AI-generated schemas carefully before using them in production ## Next steps[​](#next-steps "Direct link to Next steps") Now that you've completed your first steps: 1. **Explore Features** - Learn about each feature in detail in the [Features](/emma/features/schema-builder.md) section 2. **Follow Guides** - Try the step-by-step [guides](/emma/guides/building-your-first-schema.md) for common tasks 3. **Build Your Own** - Start creating schemas and importing data for your infrastructure ## Getting help[​](#getting-help "Direct link to Getting help") If you encounter issues: * Check the [Troubleshooting Guide](/emma/reference/troubleshooting.md) * Review feature-specific documentation in the Features section * Remember that Emma is experimental - some features may not work as expected Emma is designed to make working with Infrahub more accessible and efficient. Take your time to explore each feature and don't hesitate to experiment! --- # Installation Emma is an experimental AI-powered assistant for Infrahub that helps you manage infrastructure schemas and data through an intuitive web interface. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before installing Emma, ensure you have: * **Infrahub instance**: Emma requires a running Infrahub instance to connect to * **Docker** (for containerized deployment) or **Python 3.10+** and **uv** (for local development) ## Installation methods[​](#installation-methods "Direct link to Installation methods") * Quick Start (Recommended) * Docker Compose * Local Development The easiest way to get started is using the Infrahub quickstart with Emma included: ``` # Download and start Infrahub + Emma with Docker Compose curl https://infrahub.opsmill.io/latest-emma | docker compose -f - up -d ``` Or if you want to add Emma on to a specific release of Infrahub: ``` # Download and start Infrahub + Emma with Docker Compose curl https://infrahub.opsmill.io/1.7.6-emma | docker compose -f - up -d ``` This command will: * Download the latest Infrahub and Emma configuration * Start both services with proper networking * Make Emma available at `http://localhost:8501` * Make Infrahub available at `http://localhost:8000` tip Update the version number in the URL to the [latest Infrahub release](https://github.com/opsmill/infrahub/tags) for the most recent features. To run Emma alongside your existing Infrahub setup: 1. **Clone the Emma repository:** ``` git clone https://github.com/opsmill/emma.git cd emma ``` 2. **Start Emma with Docker Compose:** ``` docker compose up --build -d ``` 3. **Connect to Infrahub network** (if running Infrahub in containers): ``` # Find your Infrahub network name docker network ls # Connect Emma to the Infrahub network docker network connect emma-emma-1 ``` Emma will be available at `http://localhost:8501` For development or custom setups: 1. **Clone the repository:** ``` git clone https://github.com/opsmill/emma.git cd emma ``` 2. **Install dependencies with uv:** ``` uv sync ``` 3. **Run Emma:** ``` uv run streamlit run main.py ``` Emma will start on `http://localhost:8501` ## Next steps[​](#next-steps "Direct link to Next steps") After installation, proceed to [Configuration](/emma/getting-started/configuration.md) to set up your connection to Infrahub. ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") If you encounter issues during installation: * **Port conflicts**: Ensure ports 8501 (Emma) and 8000 (Infrahub) are available * **Docker issues**: Check Docker is running and you have sufficient resources allocated * **Network connectivity**: Verify Emma can reach your Infrahub instance For more detailed troubleshooting, see the [Troubleshooting Guide](/emma/reference/troubleshooting.md). --- # Building Your First Schema This guide walks you through creating your first Infrahub schema using Emma's AI Schema Builder. We'll create a practical schema for modeling network devices. ## What we'll build[​](#what-well-build "Direct link to What we'll build") By the end of this guide, you'll have created a "NetworkDevice" schema that includes: * Basic device information (name, model, serial number) * Network configuration (management IP, location) * Operational status and metadata * Relationships to location and vendor schemas ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before starting, ensure you have: * Emma running and connected to your Infrahub instance * Basic understanding of what schemas are (see [First Steps](/emma/getting-started/first-steps.md)) * An OpenAI API key configured for AI features ## Step 1: Plan your schema[​](#step-1-plan-your-schema "Direct link to Step 1: Plan your schema") Before jumping into the Schema Builder, think about what you want to model: **Core Attributes:** * `name` - Unique device identifier * `model` - Device model/part number * `serial_number` - Hardware serial number * `management_ip` - IP address for device management * `status` - Operational status (active, maintenance, decommissioned) **Relationships:** * `location` - Reference to a Location object * `vendor` - Reference to a Vendor/Organization object **Metadata:** * `description` - Human-readable description * `tags` - Flexible tagging system ## Step 2: Open the schema builder[​](#step-2-open-the-schema-builder "Direct link to Step 2: Open the schema builder") 1. **Navigate to Schema Builder** in Emma's sidebar 2. **Verify AI configuration** - Ensure your OpenAI API key is set 3. **Check Infrahub connection** - Confirm you're connected to the right instance and branch ## Step 3: Describe your schema[​](#step-3-describe-your-schema "Direct link to Step 3: Describe your schema") In the Schema Builder interface, enter this description: ``` Create a NetworkDevice schema for modeling network infrastructure devices with the following requirements: Core Information: - name: unique identifier for the device - model: device model or part number - serial_number: hardware serial number - description: human-readable description Network Configuration: - management_ip: IPv4 address for device management - management_interface: name of the management interface Location and Organization: - location: relationship to a Location object - vendor: relationship to an Organization object representing the device vendor Operational Status: - status: enumeration with values: active, maintenance, decommissioned, planned - commissioned_date: when the device was put into service - last_seen: timestamp of last successful connection The schema should inherit from the base Device schema if available, and include proper validation for IP addresses and required fields. ``` ## Step 4: Review the generated schema[​](#step-4-review-the-generated-schema "Direct link to Step 4: Review the generated schema") The AI will generate a complete YAML schema. Review it carefully: ### Check the schema structure[​](#check-the-schema-structure "Direct link to Check the schema structure") ``` # Example generated schema (yours may vary) --- version: '1.0' schemas: - name: NetworkDevice namespace: Infrastructure description: Network infrastructure device inherit_from: - CoreDevice attributes: - name: name kind: Text unique: true optional: false - name: model kind: Text optional: true # ... more attributes relationships: - name: location peer: LocationGeneric optional: true cardinality: one # ... more relationships ``` ### Verify key elements[​](#verify-key-elements "Direct link to Verify key elements") ✅ **Naming**: Schema name follows your conventions ✅ **Namespace**: Appropriate namespace for organization ✅ **Inheritance**: Inherits from appropriate base schema ✅ **Attributes**: All required attributes are present with correct types ✅ **Relationships**: References to Location and Organization schemas ✅ **Validation**: IP address validation and required field constraints ## Step 5: Refine if needed[​](#step-5-refine-if-needed "Direct link to Step 5: Refine if needed") If the generated schema needs adjustments, refine your description: **Add missing details:** ``` Also include: - hostname: DNS hostname for the device - firmware_version: current firmware version - power_consumption: power usage in watts ``` **Adjust relationships:** ``` The location relationship should be required (not optional) and the vendor relationship should reference Organization objects with a role filter for vendors only. ``` **Modify validation:** ``` The management_ip should validate as a proper IPv4 address, and the serial_number should be required and unique. ``` ## Step 6: Load the schema[​](#step-6-load-the-schema "Direct link to Step 6: Load the schema") Once you're satisfied with the generated schema: 1. **Copy the YAML** from the Schema Builder output 2. **Navigate to Schema Loader** in Emma's sidebar 3. **Paste the schema** into the YAML editor 4. **Preview the schema** to verify it looks correct 5. **Load into Infrahub** by clicking "Load Schema" ## Step 7: Verify the schema[​](#step-7-verify-the-schema "Direct link to Step 7: Verify the schema") After loading, verify your schema worked correctly: ### Using the schema visualizer[​](#using-the-schema-visualizer "Direct link to Using the schema visualizer") 1. **Go to Schema Visualizer** 2. **Find your NetworkDevice schema** in the schema list 3. **Examine the relationships** to Location and Organization 4. **Verify the attributes** appear correctly ### Using Infrahub directly[​](#using-infrahub-directly "Direct link to Using Infrahub directly") 1. **Open your Infrahub instance** in a web browser 2. **Navigate to Schema** section 3. **Find NetworkDevice** in the schema list 4. **Review the generated schema** definition ## Step 8: Test with sample data[​](#step-8-test-with-sample-data "Direct link to Step 8: Test with sample data") Now test your schema by adding some sample data: ### Using the data importer[​](#using-the-data-importer "Direct link to Using the data importer") Create a CSV file: ``` name,model,serial_number,management_ip,status,description switch-01,Cisco 3850,FCW2140L0EF,192.168.1.10,active,Core distribution switch router-01,Cisco ISR4431,FJC2140L0AG,192.168.1.1,active,Main internet gateway switch-02,Cisco 3850,FCW2140L0EH,192.168.1.11,maintenance,Access layer switch ``` Then: 1. **Go to Data Importer** in Emma 2. **Upload your CSV file** 3. **Map columns** to your NetworkDevice schema attributes 4. **Preview the import** to verify mappings 5. **Execute the import** to create your first devices ### Verify the data[​](#verify-the-data "Direct link to Verify the data") 1. **Check Infrahub** to see your imported devices 2. **Use the Data Exporter** to export and verify the data 3. **Navigate between relationships** to see how location and vendor references work ## Common issues and solutions[​](#common-issues-and-solutions "Direct link to Common issues and solutions") ### Schema generation issues[​](#schema-generation-issues "Direct link to Schema generation issues") **Problem**: AI generates incorrect attribute types **Solution**: Be more specific about data types in your description, for example: "IPv4 address" instead of "IP address" **Problem**: Missing relationships **Solution**: Explicitly mention all required relationships and their cardinality **Problem**: Schema doesn't inherit correctly **Solution**: Check if base schemas exist in your Infrahub instance first ### Schema loading issues[​](#schema-loading-issues "Direct link to Schema loading issues") **Problem**: Validation errors when loading **Solution**: Check that referenced schemas (Location, Organization) exist in Infrahub **Problem**: Namespace conflicts **Solution**: Use unique namespaces or check existing schema names ### Data import issues[​](#data-import-issues "Direct link to Data import issues") **Problem**: Relationship resolution fails **Solution**: Ensure referenced objects (locations, vendors) exist before importing devices **Problem**: Validation errors on IP addresses **Solution**: Verify IP address format in your CSV data ## Next steps[​](#next-steps "Direct link to Next steps") Congratulations! You've created your first schema with Emma. Here's what to explore next: ### Extend your schema[​](#extend-your-schema "Direct link to Extend your schema") * **Add more attributes** like rack position, power requirements, or warranty information * **Create related schemas** for interfaces, VLANs, or IP addresses * **Model hierarchical relationships** like chassis and line cards ### Build a complete model[​](#build-a-complete-model "Direct link to Build a complete model") * **Create supporting schemas** for racks, cabinets, and rooms * **Add network connectivity** with cable and interface schemas * **Include service modeling** for applications and services ### Advanced features[​](#advanced-features "Direct link to Advanced features") * **Try the Schema Library** to find pre-built schemas you can extend * **Use the Schema Visualizer** to understand complex relationships * **Explore experimental features** like the Query Builder ### Best practices[​](#best-practices "Direct link to Best practices") * **Start with basics** and add complexity gradually * **Plan relationships** before creating dependent schemas * **Test with sample data** before importing production data * **Document your schemas** for team collaboration ## Resources[​](#resources "Direct link to Resources") * [Schema Builder Reference](/emma/features/schema-builder.md) - Detailed Schema Builder documentation * [Schema Library](https://docs.infrahub.app/schema-library) - Pre-built schemas and examples * [Data Import & Export](/emma/features/data-import-export.md) - Advanced data import techniques * [Infrahub Schema Documentation](https://docs.infrahub.app/reference/schema/) - Official schema reference You're now ready to build more complex schemas and models with Emma! --- # Configuration Reference This reference covers all configuration options available in Emma, including environment variables, feature flags, and runtime settings. ## Environment variables[​](#environment-variables "Direct link to Environment variables") ### Required variables[​](#required-variables "Direct link to Required variables") | Variable | Description | Example | Notes | | -------------------- | ------------------------ | -------------------------------------- | ---------------------------- | | `INFRAHUB_ADDRESS` | URL of Infrahub instance | `http://localhost:8000` | Must be accessible from Emma | | `INFRAHUB_API_TOKEN` | API authentication token | `06438eb2-8019-4776-878c-0941b1f1d1ec` | Generate in Infrahub admin | ### Optional variables[​](#optional-variables "Direct link to Optional variables") | Variable | Description | Default | Example | | -------------------------- | ------------------------------------- | --------- | -------------------------------- | | `INFRAHUB_TIMEOUT` | API request timeout (seconds) | `30` | `60` | | `INFRAHUB_RETRIES` | Number of API retry attempts | `3` | `5` | | `EMMA_FEATURE_FLAGS` | Comma-separated experimental features | `""` | `query_builder,template_builder` | | `STREAMLIT_SERVER_PORT` | Port for Emma web interface | `8501` | `8080` | | `STREAMLIT_SERVER_ADDRESS` | Interface to bind to | `0.0.0.0` | `127.0.0.1` | ### AI configuration[​](#ai-configuration "Direct link to AI configuration") | Variable | Description | Default | Example | | -------------------- | ------------------------------ | --------------- | -------- | | `OPENAI_API_KEY` | OpenAI API key for AI features | `""` | `sk-...` | | `OPENAI_MODEL` | AI model to use | `gpt-3.5-turbo` | `gpt-4` | | `OPENAI_TEMPERATURE` | AI creativity level (0.0-2.0) | `0.7` | `0.3` | | `OPENAI_MAX_TOKENS` | Maximum tokens per AI response | `2000` | `4000` | ### Logging configuration[​](#logging-configuration "Direct link to Logging configuration") | Variable | Description | Default | Example | | ------------------------ | -------------------------- | ------- | ------- | | `STREAMLIT_LOGGER_LEVEL` | Streamlit logging level | `INFO` | `DEBUG` | | `PYTHONPATH` | Python path for modules | `""` | `/app` | | `LOG_LEVEL` | Emma application log level | `INFO` | `DEBUG` | ## Feature flags[​](#feature-flags "Direct link to Feature flags") Feature flags enable experimental functionality in Emma. Set via `EMMA_FEATURE_FLAGS` environment variable. ### Available flags[​](#available-flags "Direct link to Available flags") | Flag | Description | Status | Dependencies | | ------------------ | -------------------------------- | ------------ | -------------- | | `query_builder` | GraphQL query builder interface | Alpha | None | | `template_builder` | Template creation and management | Alpha | None | | `advanced_import` | Enhanced data import features | Beta | None | | `bulk_operations` | Bulk schema and data operations | Alpha | None | | `ai_suggestions` | AI-powered suggestions in UI | Experimental | OpenAI API key | ### Setting feature flags[​](#setting-feature-flags "Direct link to Setting feature flags") **Single flag:** ``` export EMMA_FEATURE_FLAGS="query_builder" ``` **Multiple flags:** ``` export EMMA_FEATURE_FLAGS="query_builder,template_builder,ai_suggestions" ``` **Docker Compose:** ``` environment: - EMMA_FEATURE_FLAGS=query_builder,template_builder ``` **Kubernetes:** ``` env: - name: EMMA_FEATURE_FLAGS value: "query_builder,template_builder" ``` ## Runtime configuration[​](#runtime-configuration "Direct link to Runtime configuration") ### In-app settings[​](#in-app-settings "Direct link to In-app settings") Emma provides runtime configuration through the web interface: #### Connection settings[​](#connection-settings "Direct link to Connection settings") * **Infrahub Address**: Override environment variable for current session * **API Token**: Override environment variable for current session * **Branch**: Select Infrahub branch to work with * **Timeout**: Adjust request timeout for slow connections #### AI settings[​](#ai-settings "Direct link to AI settings") * **OpenAI API Key**: Configure AI features * **Model Selection**: Choose AI model (gpt-3.5-turbo, gpt-4) * **Temperature**: Adjust AI creativity (0.0 = deterministic, 2.0 = very creative) * **Max Tokens**: Limit AI response length #### Import/export settings[​](#importexport-settings "Direct link to Import/export settings") * **Batch Size**: Records to process per batch (default: 100) * **Validation Level**: Strict, moderate, or lenient validation * **Error Handling**: Stop on error or continue with warnings * **Relationship Resolution**: Automatic or manual relationship handling #### Display settings[​](#display-settings "Direct link to Display settings") * **Theme**: Light or dark mode * **Page Layout**: Wide or narrow layout * **Auto-refresh**: Automatic data refresh interval * **Pagination**: Number of items per page ### Configuration persistence[​](#configuration-persistence "Direct link to Configuration persistence") Settings configured in the Emma interface are stored in: **Local Development:** * Browser session storage (temporary) * Browser local storage (persistent across sessions) **Docker Deployment:** * Volume-mounted configuration file * Environment variables take precedence **Kubernetes:** * ConfigMaps for application settings * Secrets for sensitive data (API keys, tokens) ## Deployment configurations[​](#deployment-configurations "Direct link to Deployment configurations") ### Docker Compose[​](#docker-compose "Direct link to Docker Compose") Complete docker-compose.yml configuration: ``` version: '3.8' services: emma: build: . ports: - "8501:8501" environment: # Required - INFRAHUB_ADDRESS=http://infrahub:8000 - INFRAHUB_API_TOKEN=${INFRAHUB_API_TOKEN} # Optional - INFRAHUB_TIMEOUT=60 - EMMA_FEATURE_FLAGS=query_builder,template_builder - OPENAI_API_KEY=${OPENAI_API_KEY} # Streamlit Configuration - STREAMLIT_SERVER_PORT=8501 - STREAMLIT_SERVER_ADDRESS=0.0.0.0 - STREAMLIT_LOGGER_LEVEL=INFO volumes: - emma-config:/app/config networks: - infrahub-network depends_on: - infrahub restart: unless-stopped volumes: emma-config: networks: infrahub-network: external: true ``` ### Kubernetes[​](#kubernetes "Direct link to Kubernetes") Emma deployment configuration: ``` apiVersion: apps/v1 kind: Deployment metadata: name: emma labels: app: emma spec: replicas: 1 selector: matchLabels: app: emma template: metadata: labels: app: emma spec: containers: - name: emma image: emma:latest ports: - containerPort: 8501 env: - name: INFRAHUB_ADDRESS value: "http://infrahub:8000" - name: INFRAHUB_API_TOKEN valueFrom: secretKeyRef: name: emma-secrets key: api-token - name: OPENAI_API_KEY valueFrom: secretKeyRef: name: emma-secrets key: openai-key - name: EMMA_FEATURE_FLAGS valueFrom: configMapKeyRef: name: emma-config key: feature-flags resources: requests: memory: "256Mi" cpu: "100m" limits: memory: "512Mi" cpu: "500m" volumeMounts: - name: config mountPath: /app/config volumes: - name: config configMap: name: emma-config --- apiVersion: v1 kind: Service metadata: name: emma spec: selector: app: emma ports: - port: 8501 targetPort: 8501 type: ClusterIP --- apiVersion: v1 kind: ConfigMap metadata: name: emma-config data: feature-flags: "query_builder,template_builder" --- apiVersion: v1 kind: Secret metadata: name: emma-secrets type: Opaque data: api-token: openai-key: ``` ## Security configuration[​](#security-configuration "Direct link to Security configuration") ### API token security[​](#api-token-security "Direct link to API token security") **Best Practices:** * Use dedicated service accounts for Emma * Rotate tokens regularly * Limit token permissions to required operations * Never log or expose tokens in plaintext **Token Permissions:** Emma requires the following Infrahub permissions: * Schema read/write access * Data read/write access * Branch access (if using feature branches) * API access ### Network security[​](#network-security "Direct link to Network security") **Firewall Rules:** * Emma port (default 8501) should be restricted to authorized users * Infrahub connection should use HTTPS in production * Consider VPN or private networks for sensitive deployments **TLS/SSL:** For production deployments, configure TLS: ``` # Using reverse proxy (nginx, traefik, etc.) # Emma itself runs HTTP, proxy handles HTTPS ``` ### Data security[​](#data-security "Direct link to Data security") **Sensitive Data Handling:** * API tokens stored securely (secrets management) * Temporary files cleaned up automatically * No sensitive data logged by default * CSV uploads processed in memory when possible ## Performance configuration[​](#performance-configuration "Direct link to Performance configuration") ### Resource limits[​](#resource-limits "Direct link to Resource limits") **Memory:** * Minimum: 256MB * Recommended: 512MB * Large datasets: 1GB+ **CPU:** * Minimum: 0.1 cores * Recommended: 0.5 cores * Heavy AI usage: 1+ cores **Storage:** * Application: \~100MB * Temporary files: Variable based on data volume * Configuration: <1MB ### Optimization settings[​](#optimization-settings "Direct link to Optimization settings") **For Large Datasets:** ``` # Increase timeouts export INFRAHUB_TIMEOUT=120 # Optimize batch sizes # Set in Emma UI: Import/Export Settings → Batch Size: 50 ``` **For Slow Networks:** ``` # Increase retries and timeout export INFRAHUB_TIMEOUT=60 export INFRAHUB_RETRIES=5 ``` **For High-Volume Usage:** ``` # Enable performance features export EMMA_FEATURE_FLAGS="bulk_operations,advanced_import" ``` ## Configuration validation[​](#configuration-validation "Direct link to Configuration validation") Emma validates configuration on startup and provides helpful error messages: **Common Validation Errors:** ``` ERROR: INFRAHUB_ADDRESS is required ERROR: Cannot connect to Infrahub at http://localhost:8000 WARNING: OPENAI_API_KEY not set - AI features disabled WARNING: Unknown feature flag 'invalid_flag' ignored ``` **Configuration Check:** ``` # Test configuration without starting full application uv run python -c "from emma.utils import validate_config; validate_config()" ``` ## Migration and upgrades[​](#migration-and-upgrades "Direct link to Migration and upgrades") ### Configuration migration[​](#configuration-migration "Direct link to Configuration migration") When upgrading Emma, configuration may need updates: **Version 1.x to 2.x:** * New required environment variables * Changed feature flag names * Updated AI model configurations **Check Release Notes:** Always review release notes for configuration changes and migration steps. ### Backup configuration[​](#backup-configuration "Direct link to Backup configuration") **Docker:** ``` # Backup configuration volume docker run --rm -v emma-config:/data -v $(pwd):/backup busybox tar czf /backup/emma-config.tar.gz -C /data . ``` **Kubernetes:** ``` # Export ConfigMaps and Secrets kubectl get configmap emma-config -o yaml > emma-config-backup.yaml kubectl get secret emma-secrets -o yaml > emma-secrets-backup.yaml ``` This configuration reference provides comprehensive guidance for setting up Emma in various environments. For troubleshooting configuration issues, see the [Troubleshooting Guide](/emma/reference/troubleshooting.md). --- # Feature Flags Emma uses feature flags to enable experimental and beta functionality. This allows you to try new features while maintaining stability in production environments. ## Overview[​](#overview "Direct link to Overview") Feature flags in Emma control access to: * **Experimental features** - Early implementations that may change significantly * **Beta features** - Stable implementations undergoing final testing * **Advanced features** - Complex functionality for power users ## Configuration[​](#configuration "Direct link to Configuration") Set feature flags using the `EMMA_FEATURE_FLAGS` environment variable: ``` # Single feature export EMMA_FEATURE_FLAGS="query_builder" # Multiple features (comma-separated) export EMMA_FEATURE_FLAGS="query_builder,template_builder" ``` ## Available feature flags[​](#available-feature-flags "Direct link to Available feature flags") ### `query_builder` (Alpha)[​](#query_builder-alpha "Direct link to query_builder-alpha") **Description:** Interactive GraphQL query builder for Infrahub **Status:** Alpha - Basic functionality implemented, interface may change **What it enables:** * Visual query builder interface * GraphQL query generation and execution * Result formatting and export * Query history and favorites **Use cases:** * Custom data extraction from Infrahub * Ad-hoc reporting and analysis * Learning GraphQL query syntax * Debugging data relationships **Requirements:** * None (works with basic Infrahub connection) **Limitations:** * Limited to read-only queries * Basic error handling * No query optimization suggestions * Interface is subject to change **Example usage:** 1. Enable the feature flag 2. Navigate to "Query Builder" in Emma's sidebar 3. Use the visual interface to build queries 4. Execute queries and view results ### `template_builder` (Alpha)[​](#template_builder-alpha "Direct link to template_builder-alpha") **Description:** Template creation and management system **Status:** Alpha - Core functionality working, needs refinement **What it enables:** * Template creation interface * Template library management * Variable substitution * Template sharing and export **Use cases:** * Creating reusable configuration templates * Standardizing infrastructure deployments * Generating documentation templates * Configuration as code workflows **Requirements:** * Write access to Infrahub * Understanding of template syntax **Limitations:** * Basic template syntax support * Limited validation * No template versioning * Experimental UI **Example usage:** 1. Enable the feature flag 2. Access "Template Builder" from the sidebar 3. Create templates with variables 4. Test template rendering with sample data ### `advanced_import` (Beta)[​](#advanced_import-beta "Direct link to advanced_import-beta") **Description:** Enhanced data import capabilities **Status:** Beta - Feature complete, undergoing testing **What it enables:** * Advanced CSV parsing options * Custom data transformation rules * Relationship auto-resolution * Import validation previews * Batch processing optimizations **Use cases:** * Complex data migrations * Custom data format handling * Large dataset imports * Data quality validation **Requirements:** * Standard Emma data import permissions * Understanding of data transformation concepts **Limitations:** * Increased memory usage * Longer processing times for complex transformations **Example usage:** 1. Enable the feature flag 2. Use Data Importer with enhanced options 3. Configure advanced parsing and transformation rules 4. Preview and validate before importing ### `bulk_operations` (Alpha)[​](#bulk_operations-alpha "Direct link to bulk_operations-alpha") **Description:** Bulk operations for schemas and data **Status:** Alpha - Basic implementation, needs performance optimization **What it enables:** * Bulk schema loading * Mass data operations * Batch delete functionality * Progress tracking for large operations **Use cases:** * Initial system setup with many schemas * Large-scale data cleanup * Mass updates across multiple objects * System migrations **Requirements:** * Administrator-level permissions * Understanding of bulk operation impacts **Limitations:** * Performance limitations with very large datasets * Limited rollback capabilities * Potential impact on Infrahub performance **Example usage:** 1. Enable the feature flag 2. Access bulk operation interfaces 3. Select multiple items for batch operations 4. Monitor progress and handle errors ### `ai_suggestions` (experimental)[​](#ai_suggestions-experimental "Direct link to ai_suggestions-experimental") **Description:** AI-powered suggestions throughout Emma's interface **Status:** Experimental - Research implementation, may be removed **What it enables:** * Schema attribute suggestions * Data validation suggestions * Relationship recommendations * Import mapping assistance **Use cases:** * First-time users learning schema design * Data quality improvement * Relationship discovery * Import optimization **Requirements:** * OpenAI API key configured * Sufficient OpenAI API credits * Network access to OpenAI services **Limitations:** * Requires external API calls * May slow down interface * Suggestions quality varies * Additional API costs **Example usage:** 1. Configure OpenAI API key 2. Enable the feature flag 3. Look for AI suggestion indicators in the UI 4. Review and apply suggestions as appropriate ## Feature flag management[​](#feature-flag-management "Direct link to Feature flag management") ### Checking active flags[​](#checking-active-flags "Direct link to Checking active flags") Emma displays active feature flags in several places: **Settings Panel:** * Lists all enabled flags * Shows feature status (Alpha, Beta, Experimental) * Provides links to feature documentation **Feature Indicators:** * Icons next to experimental features * Tooltips explaining feature status * Warnings about stability ### Dynamic configuration[​](#dynamic-configuration "Direct link to Dynamic configuration") Some feature flags can be toggled at runtime: **Session-level flags** (temporary): * Set via URL parameters: `?features=query_builder,template_builder` * Override environment variables for testing * Reset when browser session ends **User preferences** (persistent): * Stored in browser local storage * Survive browser restarts * Can be cleared via settings panel ### Validation and error handling[​](#validation-and-error-handling "Direct link to Validation and error handling") Emma validates feature flags on startup: **Valid flag formats:** ``` # Correct export EMMA_FEATURE_FLAGS="query_builder,template_builder" # Also correct (spaces are stripped) export EMMA_FEATURE_FLAGS="query_builder, template_builder" ``` **Invalid configurations:** ``` # Unknown flags are ignored with warnings export EMMA_FEATURE_FLAGS="invalid_flag,query_builder" # Warning: Unknown feature flag 'invalid_flag' ignored # Empty values are acceptable export EMMA_FEATURE_FLAGS="" ``` ## Best practices[​](#best-practices "Direct link to Best practices") ### Development environments[​](#development-environments "Direct link to Development environments") **Enable experimental features** for testing: ``` export EMMA_FEATURE_FLAGS="query_builder,template_builder,ai_suggestions" ``` **Benefits:** * Access to latest functionality * Opportunity to provide feedback * Early adoption of useful features **Considerations:** * Features may break or change * Performance may be sub-optimal * Some features require additional setup ### Production environments[​](#production-environments "Direct link to Production environments") **Use only stable features** in production: ``` # Only enable well-tested features export EMMA_FEATURE_FLAGS="advanced_import" ``` **Recommended approach:** 1. Test features in development first 2. Enable beta features in staging 3. Only use stable features in production 4. Monitor for issues after enabling new features ### Team deployments[​](#team-deployments "Direct link to Team deployments") **Coordinate feature usage** across teams: * Document which features are enabled in each environment * Communicate feature changes to team members * Provide training for new experimental features * Establish rollback procedures ## Feature lifecycle[​](#feature-lifecycle "Direct link to Feature lifecycle") Emma features follow a development lifecycle: ### Experimental[​](#experimental "Direct link to Experimental") * **Purpose:** Research and initial implementation * **Stability:** May be removed or significantly changed * **Support:** Limited documentation and support * **Recommendation:** Development environments only ### Alpha[​](#alpha "Direct link to Alpha") * **Purpose:** Core functionality complete, needs refinement * **Stability:** Basic functionality works, interfaces may change * **Support:** Basic documentation, community support * **Recommendation:** Development and staging environments ### Beta[​](#beta "Direct link to Beta") * **Purpose:** Feature complete, undergoing final testing * **Stability:** Stable functionality, minor interface changes possible * **Support:** Full documentation, supported by team * **Recommendation:** Safe for production with monitoring ### Stable[​](#stable "Direct link to Stable") * **Purpose:** Production-ready feature * **Stability:** Fully stable, changes follow deprecation policy * **Support:** Full support and documentation * **Recommendation:** Safe for all environments ### Deprecated[​](#deprecated "Direct link to Deprecated") * **Purpose:** Feature being phased out * **Stability:** Still works but may be removed in future versions * **Support:** Limited support, migration guidance provided * **Recommendation:** Plan migration to replacement features ## Migration and compatibility[​](#migration-and-compatibility "Direct link to Migration and compatibility") ### Version compatibility[​](#version-compatibility "Direct link to Version compatibility") Feature flags may change between Emma versions: **Major versions**, for example: 1.x to 2.x: * Feature flags may be renamed or removed * New features may be added * Check migration guide for changes **Minor versions**, for example: 1.1 to 1.2: * New features may be added * Existing features may change status * Backward compatibility maintained **Patch versions**, for example: 1.1.1 to 1.1.2: * Feature flags unchanged * Bug fixes only ### Feature graduation[​](#feature-graduation "Direct link to Feature graduation") When experimental features become stable: 1. **Feature flag removed** - Feature becomes always available 2. **Configuration maintained** - No changes to your setup required 3. **Documentation updated** - Feature moves to main documentation 4. **Migration period** - Old flag maintained temporarily for compatibility ### Deprecation process[​](#deprecation-process "Direct link to Deprecation process") When features are deprecated: 1. **Deprecation notice** - Warning in documentation and UI 2. **Migration guide** - Instructions for moving to replacement 3. **Grace period** - Feature continues to work for several versions 4. **Removal** - Feature flag and functionality removed ## Troubleshooting feature flags[​](#troubleshooting-feature-flags "Direct link to Troubleshooting feature flags") ### Common issues[​](#common-issues "Direct link to Common issues") **Feature not appearing:** * Check feature flag spelling * Verify environment variable is set correctly * Restart Emma after changing environment variables * Check Emma logs for feature flag warnings **Feature causing issues:** * Disable the feature flag temporarily * Check feature-specific documentation * Report issues with feature flag and Emma version information **Performance problems:** * Some experimental features may impact performance * Disable CPU-intensive features if needed * Monitor system resources when testing new features ### Getting help[​](#getting-help "Direct link to Getting help") When reporting feature flag issues: 1. **Include Emma version** and deployment method 2. **List active feature flags** from your configuration 3. **Describe the specific feature** causing issues 4. **Provide steps to reproduce** the problem 5. **Include relevant logs** with feature flag context ### Feedback and contributions[​](#feedback-and-contributions "Direct link to Feedback and contributions") Emma's experimental features benefit from user feedback: * **Test new features** and report issues * **Suggest improvements** for experimental interfaces * **Share use cases** for features you find valuable * **Contribute documentation** for features you use Feature flags allow Emma to evolve rapidly while maintaining stability. Use them to access cutting-edge functionality and help shape Emma's future development. --- # Troubleshooting This guide covers common issues you might encounter while using Emma and how to resolve them. ## Connection issues[​](#connection-issues "Direct link to Connection issues") ### Cannot connect to Infrahub[​](#cannot-connect-to-infrahub "Direct link to Cannot connect to Infrahub") **Symptoms:** * Emma shows "Connection Failed" status * API calls timeout or fail * Cannot load schemas or data **Solutions:** 1. **Check Infrahub Address** ``` # Verify the address is correct and accessible curl http://localhost:8000/api/schema/ ``` 2. **Verify API Token** * Ensure the token is valid and not expired * Check token permissions in Infrahub * Try generating a new token 3. **Network Connectivity** * Check if Infrahub is running: `docker ps` or service status * Verify port accessibility: `telnet localhost 8000` * Check firewall rules and network configuration 4. **Docker Network Issues** (when using containers) ``` # Check if containers can communicate docker network ls docker network inspect # Connect Emma to Infrahub network docker network connect emma-emma-1 ``` ### Authentication errors[​](#authentication-errors "Direct link to Authentication errors") **Error:** "Unauthorized" or "403 Forbidden" **Solutions:** * Verify `INFRAHUB_API_TOKEN` is set correctly * Check token permissions in Infrahub admin interface * Ensure token hasn't expired * Try accessing Infrahub directly with the token: ``` curl -H "X-INFRAHUB-KEY: your-token" http://localhost:8000/api/schema/ ``` ## Schema issues[​](#schema-issues "Direct link to Schema issues") ### Schema loading failures[​](#schema-loading-failures "Direct link to Schema loading failures") **Error:** "Failed to load schema" or validation errors **Common Causes and Solutions:** 1. **Missing Dependencies** * Load base schemas before dependent schemas * Check inheritance chain requirements * Verify referenced schemas exist 2. **Invalid YAML Syntax** * Use a YAML validator to check syntax * Check indentation (use spaces, not tabs) * Verify quotes and special characters 3. **Schema Conflicts** * Check for naming conflicts with existing schemas * Verify namespace uniqueness * Review attribute naming conventions 4. **Permission Issues** * Ensure API token has schema modification permissions * Check branch permissions if using feature branches ### AI schema builder issues[​](#ai-schema-builder-issues "Direct link to AI schema builder issues") **Problem:** AI generates incorrect or incomplete schemas **Solutions:** 1. **Improve Descriptions** * Be more specific about requirements * Include examples of desired attributes * Mention relationships explicitly * Reference existing schemas 2. **Check OpenAI Configuration** * Verify API key is valid and has credits * Check rate limits and quotas * Try a simpler description first 3. **Validate Generated Schemas** * Always review AI output carefully * Test schemas with sample data * Compare with similar schemas in the library ## Data import/export issues[​](#data-importexport-issues "Direct link to Data import/export issues") ### Import failures[​](#import-failures "Direct link to Import failures") **Common Problems:** 1. **File Format Issues** ``` Error: "Unable to parse CSV file" ``` **Solutions:** * Ensure file is UTF-8 encoded * Check for proper CSV format with headers * Remove or escape special characters * Verify file size is under 100MB 2. **Mapping Errors** ``` Error: "Column 'xyz' cannot be mapped to attribute 'abc'" ``` **Solutions:** * Check attribute names match schema definition * Verify data types are compatible * Review required vs optional fields * Check relationship target schemas exist 3. **Validation Failures** ``` Error: "Validation failed for record 123" ``` **Solutions:** * Check data format (dates, IPs, emails) * Verify enum values are valid * Ensure required fields are populated * Check uniqueness constraints 4. **Relationship Resolution** ``` Error: "Cannot resolve relationship to 'Location'" ``` **Solutions:** * Ensure referenced objects exist in Infrahub * Check relationship field naming * Verify object identifiers are correct * Create referenced objects first ### Export failures[​](#export-failures "Direct link to Export failures") **Common Problems:** 1. **Permission Errors** * Verify read permissions for target schemas * Check branch access permissions * Ensure API token has export rights 2. **Performance Issues** * Reduce export size with filters * Export in smaller batches * Check Infrahub resource usage * Increase timeout settings 3. **Format Issues** * Verify encoding settings * Check delimiter configuration * Review special character handling ## Performance issues[​](#performance-issues "Direct link to Performance issues") ### Slow response times[​](#slow-response-times "Direct link to Slow response times") **Symptoms:** * Long loading times for schemas or data * Timeouts during operations * Unresponsive interface **Solutions:** 1. **Check System Resources** ``` # Monitor system resources htop # or top docker stats # for containerized deployments ``` 2. **Optimize Operations** * Reduce batch sizes for imports * Use filters to limit data exports * Close unused browser tabs * Clear browser cache 3. **Network Optimization** * Check network latency to Infrahub * Use local deployments for development * Increase timeout settings 4. **Infrahub Performance** * Check Infrahub logs for errors * Monitor database performance * Verify adequate system resources ### Memory issues[​](#memory-issues "Direct link to Memory issues") **Error:** "Out of memory" or browser crashes **Solutions:** * Reduce file sizes for imports * Process data in smaller batches * Close other applications * Increase system memory if possible * Use command-line tools for very large datasets ## Browser and interface issues[​](#browser-and-interface-issues "Direct link to Browser and interface issues") ### Display problems[​](#display-problems "Direct link to Display problems") **Issues:** * Missing UI elements * Formatting problems * Broken layouts **Solutions:** 1. **Clear Browser Cache** * Hard refresh (Ctrl+F5 or Cmd+Shift+R) * Clear browser cache and cookies * Try incognito/private mode 2. **Check Browser Compatibility** * Use supported browsers (Chrome, Firefox, Safari, Edge) * Update to latest browser version * Disable problematic browser extensions 3. **Check Console Errors** * Open browser developer tools (F12) * Look for JavaScript errors in console * Check network tab for failed requests ### Streamlit-specific issues[​](#streamlit-specific-issues "Direct link to Streamlit-specific issues") **Error:** "Please run this app with streamlit run" **Solution:** ``` # Ensure you're using the correct command uv run streamlit run main.py # Or for direct Python execution python -m streamlit run main.py ``` **Error:** Session state errors **Solutions:** * Refresh the page * Clear browser session storage * Restart Emma application ## Configuration issues[​](#configuration-issues "Direct link to Configuration issues") ### Environment variables[​](#environment-variables "Direct link to Environment variables") **Problem:** Configuration not being applied **Check List:** 1. **Verify Variables Are Set** ``` echo $INFRAHUB_ADDRESS echo $INFRAHUB_API_TOKEN env | grep INFRAHUB ``` 2. **Docker Environment Issues** ``` # In docker-compose.yml environment: - INFRAHUB_ADDRESS=http://infrahub:8000 - INFRAHUB_API_TOKEN=${INFRAHUB_API_TOKEN} ``` 3. **Environment File Issues** ``` # Check .env file exists and format cat .env # Ensure no spaces around = signs # CORRECT: INFRAHUB_ADDRESS=http://localhost:8000 # WRONG: INFRAHUB_ADDRESS = http://localhost:8000 ``` ### Feature flags[​](#feature-flags "Direct link to Feature flags") **Problem:** Experimental features not working **Solutions:** ``` # Check feature flags are set echo $EMMA_FEATURE_FLAGS # Set feature flags correctly export EMMA_FEATURE_FLAGS="query_builder,template_builder" # For Docker docker run -e EMMA_FEATURE_FLAGS="query_builder" emma ``` ## Logging and debugging[​](#logging-and-debugging "Direct link to Logging and debugging") ### Enable debug logging[​](#enable-debug-logging "Direct link to Enable debug logging") **For Local Development:** ``` # Set Streamlit logging level export STREAMLIT_LOGGER_LEVEL=debug # Set Python logging level export PYTHONPATH=. uv run python -c "import logging; logging.basicConfig(level=logging.DEBUG)" ``` **For Docker:** ``` # In docker-compose.yml environment: - STREAMLIT_LOGGER_LEVEL=debug - PYTHONPATH=/app ``` ### Check application logs[​](#check-application-logs "Direct link to Check application logs") **Local Development:** ``` # Run with verbose output uv run streamlit run main.py --logger.level=debug # Check Emma logs tail -f ~/.streamlit/logs/ ``` **Docker Deployment:** ``` # Check container logs docker logs emma-emma-1 # Follow logs in real-time docker logs -f emma-emma-1 ``` ### Common log patterns[​](#common-log-patterns "Direct link to Common log patterns") **Connection Issues:** ``` ERROR: Failed to connect to Infrahub at http://localhost:8000 ERROR: Authentication failed - invalid token ``` **Schema Issues:** ``` ERROR: Schema validation failed WARNING: Referenced schema 'Location' not found ``` **Import Issues:** ``` ERROR: Failed to parse CSV at line 123 WARNING: Relationship resolution failed for 'device-01' ``` ## Getting help[​](#getting-help "Direct link to Getting help") ### Before asking for help[​](#before-asking-for-help "Direct link to Before asking for help") 1. **Check this troubleshooting guide** 2. **Review Emma documentation** 3. **Check Infrahub documentation** at [docs.infrahub.app](https://docs.infrahub.app) 4. **Search existing issues** on GitHub ### Gathering information[​](#gathering-information "Direct link to Gathering information") When reporting issues, include: 1. **Emma version** and deployment method 2. **Infrahub version** and configuration 3. **Error messages** (full text, not screenshots) 4. **Steps to reproduce** the issue 5. **Environment details** (OS, browser, Docker version) 6. **Relevant logs** with debug logging enabled ### Where to get help[​](#where-to-get-help "Direct link to Where to get help") * **GitHub Issues**: [GitHub](https://github.com/opsmill/emma/issues) * **Infrahub Community**: [GitHub Discussions](https://github.com/opsmill/infrahub/discussions) * **Documentation**: [docs.infrahub.app](https://docs.infrahub.app) ### Emergency procedures[​](#emergency-procedures "Direct link to Emergency procedures") **If Emma is completely broken:** 1. **Check Infrahub directly** to ensure it's working 2. **Restart Emma** application or containers 3. **Clear browser cache** and try again 4. **Check recent changes** and revert if necessary 5. **Use Infrahub directly** as a workaround **If data import corrupted data:** 1. **Stop the import** immediately 2. **Use Infrahub branches** to isolate changes 3. **Review import logs** for affected records 4. **Use Infrahub's version control** to revert if needed 5. **Export and verify** data integrity Remember: Emma is experimental software. Always backup your Infrahub data before major operations and test changes in non-production environments first. --- # Infrahub Exporter Infrahub Exporter is a service that exports metrics and service discovery information from Infrahub to monitoring systems like Prometheus and OpenTelemetry. ## Overview[​](#overview "Direct link to Overview") Infrahub Exporter acts as a bridge between your Infrahub instance and monitoring tools, providing: 1. **Metrics Export**: Collects and exposes metrics from Infrahub nodes for monitoring 2. **Service Discovery**: Provides dynamic service discovery for Prometheus based on Infrahub data 3. **OpenTelemetry Integration**: Supports sending metrics to OpenTelemetry collectors ## Features[​](#features "Direct link to Features") * **Prometheus Integration**: Exposes metrics in Prometheus format * **OpenTelemetry Support**: Sends metrics to OTLP-compatible collectors * **Dynamic Service Discovery**: Generates Prometheus service discovery files based on GraphQL queries * **Flexible Configuration**: Configurable via YAML with environment variable overrides * **Caching**: Efficient data retrieval with caching to reduce load on Infrahub * **Resilience**: Automatic retries and error handling for API calls ## Guides[​](#guides "Direct link to Guides") * [Install and run Infrahub Exporter](/exporter/guides/installation.md) * [Configure Infrahub Exporter](/exporter/guides/configuration.md) --- # Configure Infrahub Exporter ## Configuration[​](#configuration "Direct link to Configuration") Create a `config.yml` file with the following structure: ``` # Infrahub connection settings infrahub: address: "http://localhost:8000" # Infrahub server address token: "your-api-token" # Infrahub SDK API key branch: main # Which branch to fetch # Poll interval in seconds (default is XXX) poll_interval_seconds: 30 # HTTP server configuration listen_address: "0.0.0.0" listen_port: 8001 # Log level (default level is INFO) log_level: "INFO" ``` ## Exporter configuration[​](#exporter-configuration "Direct link to Exporter configuration") Infrahub Exporter separates what metrics to collect from how to send them. You define metrics (resources and fields) once, and then enable one or more exporters to deliver those metrics: ### Defining metrics[​](#defining-metrics "Direct link to Defining metrics") ``` metrics: # Define which resource types and fields to include kind: - kind: InfraDevice include: - name - description - platform - role - status # You can apply filters to this `kind` filters: - site__name__value: "dc1" - role__value: "edge" ``` These metrics will be available to all configured exporters. You can choose one or more exporters: * Pull-based (Prometheus): Exposes a `/metrics` HTTP endpoint for Prometheus or other scrapers. * Push-based (OTLP): Sends metrics to an OpenTelemetry collector. ### Prometheus integration[​](#prometheus-integration "Direct link to Prometheus integration") Configure the Prometheus exporter in your `config.yml` : ``` exporters: prometheus: enabled: true # Pull-based endpoint at /metrics ``` And in your Prometheus : ``` scrape_configs: - job_name: 'infrahub' scrape_interval: 30s static_configs: - targets: ['infrahub-exporter:8001'] ``` ### Opentelemetry integration[​](#opentelemetry-integration "Direct link to Opentelemetry integration") Configure the OTLP exporter in your `config.yml` : ``` exporters: otlp: enabled: true endpoint: "http://otel-collector:4317" timeout_seconds: 10 ``` ## Service discovery configuration[​](#service-discovery-configuration "Direct link to Service discovery configuration") The exporter can provide HTTP-based service-discovery for Prometheus. Define your queries and how to map fields into target labels: ``` service_discovery: enabled: true queries: - name: "devices" file_path: "./queries/devices.gql" endpoint_path: "devices" refresh_interval_seconds: 60 target_field: "primary_address.node.address.ip" label_mappings: "device_name": "name.value" "location": "site.node.name.value" "role": "role.value" "platform": "platform.node.name.value" ``` This section ensures Prometheus can automatically discover and scrape dynamic targets managed by Infrahub. To integrate it with your monitoring stack, add this section in your Prometheus configuration: ``` scrape_configs: - job_name: 'infrahub-sd' http_sd_configs: - url: http://infrahub-exporter:8001/sd/devices refresh_interval: 60s ``` --- # Install and run Infrahub Exporter This guide walks you through installing and running the Infrahub Exporter on your system. ## Installation[​](#installation "Direct link to Installation") Choose one of the following methods to install Infrahub Exporter. ### Using poetry[​](#using-poetry "Direct link to Using poetry") 1. **Clone the repository** ``` git clone https://github.com/opsmill/infrahub-exporter.git cd infrahub-exporter ``` 1. **Install dependencies** ``` poetry install ``` 1. **Configure your exporter** See the [Configuration guide](/exporter/guides/configuration.md) 1. **Run the exporter** ``` poetry run python -m infrahub_exporter --config path/to/config.yml ``` ### Using docker (recommended)[​](#using-docker-recommended "Direct link to Using docker (recommended)") 1. **Clone the repository** ``` git clone https://github.com/opsmill/infrahub-exporter.git cd infrahub-exporter ``` 1. **Build the Docker image** ``` docker build -t infrahub-exporter:latest -f development/Dockerfile . ``` 1. **Configure your exporter** See the [Configuration guide](/exporter/guides/configuration.md) 1. **Run the container** ``` docker run \ -v "path/to/config.yml:/app/config.yml" \ -p 8001:8001 \ infrahub-exporter:latest ``` 1. \*\*Docker compose example ``` infrahub-exporter: image: infrahub-exporter:latest restart: unless-stopped ports: - "8001:8001" volumes: - path/to/config.yml:/app/config.yml:ro - path/to/queries:/app/queries:ro environment: - INFRAHUB_SIDECAR_LOG_LEVEL=INFO depends_on: - infrahub-server # adjust to your Infrahub service name ``` --- # Infrahub demo-sp example Welcome to the **Infrahub demo-sp** example — a service-provider bundle that shows how Infrahub manages a multi-vendor MPLS backbone and provisions customer-facing services end to end. It demonstrates Infrahub's core capabilities through a realistic SP workload: * Schema-driven data modeling for backbone and customer services * Generator-driven provisioning (VRF, RT, PE-CE interfaces, IP allocation, BGP) * Per-vendor configuration artifacts from a shared schema * Version-controlled changes with proposed-change diff review * A Streamlit Service Catalog for self-service L3VPN and SD-WAN provisioning Whether you're a network engineer evaluating automation, a developer building on Infrahub, or an architect comparing infrastructure-data platforms, this demo gives you a hands-on view of how an SP team would model and operate a real backbone. ## Documentation guide[​](#documentation-guide "Direct link to Documentation guide") This documentation follows the [Diataxis framework](https://diataxis.fr/) so you can find the right kind of page for what you need to do. ### Getting started[​](#getting-started "Direct link to Getting started") | Page | Purpose | | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | **[Quick start](/infrahub-demo-sp/quickstart.md)** | Step-by-step instructions to clone the repository, configure `.env`, start Infrahub, and load the bootstrap data. Start here on your first run. | ### Tutorials[​](#tutorials "Direct link to Tutorials") | Page | Purpose | | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **[L3VPN service walkthrough](/infrahub-demo-sp/services/l3vpn.md)** | End-to-end tour of the L3VPN service: how the catalog form drives `ServiceL3Vpn` creation, what the generator materialises (VRF, RTs, PE-CE links, eBGP), what each per-vendor template emits, and how the validation checks fit together. | | **[SD-WAN service walkthrough](/infrahub-demo-sp/services/sdwan.md)** | Companion tour of the SD-WAN service: vendor choice (Cisco Viptela vs Versa Networks), hub-spoke vs full-mesh topology, generator-created edge devices, and per-vendor edge configurations. | | **[Deploy with containerlab](/infrahub-demo-sp/lab/containerlab.md)** | Take the rendered MPLS backbone artifacts and bring them up in a virtual lab with containerlab. Cover Arista cEOS, Nokia SR Linux substitution, and how to reach the lab CLIs. | ### Topics[​](#topics "Direct link to Topics") | Page | Purpose | | ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **[Architecture](/infrahub-demo-sp/architecture.md)** | The "why" behind the demo — backbone design choices, service modelling, generator vs catalog responsibilities, and the lifecycle of a customer service. | | **[Schema reference](/infrahub-demo-sp/schema-reference.md)** | Field-level documentation of every node (`ServiceL3Vpn`, `ServiceSdwan`, `TopologyMplsBackbone`, …), the resource pools, and the user-vs-generator split for each attribute. | ### Operations[​](#operations "Direct link to Operations") | Page | Purpose | | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | **[Troubleshooting](/infrahub-demo-sp/troubleshooting.md)** | Symptoms and fixes for common issues — stuck repository sync, generator races, broken artifacts, and how to inspect live state when something looks off. | ## Quick start[​](#quick-start "Direct link to Quick start") If you're ready to dive in: 1. Follow the **[quick start](/infrahub-demo-sp/quickstart.md)** to clone the repository, configure `.env`, and run `uv run invoke init`. 2. Open the **Infrahub UI** at `http://localhost:8000` and explore the seeded data — Devices, MPLS Backbones, Service Catalog → L3 VPNs and SD-WAN. 3. Provision a new service from the **[Streamlit Service Catalog](/infrahub-demo-sp/services/l3vpn.md)** at `http://localhost:8501`. 4. (Optional) Bring up the MPLS backbone in **[containerlab](/infrahub-demo-sp/lab/containerlab.md)**. 5. Read the **[architecture](/infrahub-demo-sp/architecture.md)** and **[schema reference](/infrahub-demo-sp/schema-reference.md)** to understand the moving parts before you extend the demo. ## What you'll learn[​](#what-youll-learn "Direct link to What you'll learn") Through this demo, you'll gain practical experience with: * **Modelling SP services in Infrahub** — `ServiceL3Vpn` / `ServiceSdwan` with uniqueness constraints, parent/child relationships, and lifecycle status. * **Resource pools** — `vpn_id_pool`, `sdwan_id_pool`, `pe_loopback_pool`, `backbone_p2p_pool`, `pe_ce_pool` for deterministic allocation. * **Generators** — Materialising VRFs, route targets, PE-CE /30 allocations, eBGP sessions, and SD-WAN edge devices from a single user-facing object. * **Per-vendor transforms** — Rendering the same intent into Arista EOS, Cisco IOS-XR, Juniper Junos, Nokia SR OS, Cisco Viptela, and Versa VOS configurations. * **Validation checks** — Catching duplicate RDs, overlapping customer prefixes, exhausted interface pools, and missing iBGP sessions before merge. * **Branch-based workflows** — Catalog form → feature branch → generator → artifact regeneration → proposed change → review → merge. * **Containerlab** — Booting the rendered backbone in a virtual lab for hands-on testing. ## Architecture at a glance[​](#architecture-at-a-glance "Direct link to Architecture at a glance") Out of the box the **financial** dataset gives you the Sterling Financial MPLS backbone with two seeded customer services — one L3VPN, one SD-WAN — wired up end to end: **Backbone** — Four PE routers, one per vendor, full-mesh iBGP/VPNv4, ISIS for underlay reachability, LDP for label distribution. **L3VPN service (`trading-floor-vpn`)** — Tenant `markets-trading`, four sites (one per PE) with eBGP PE-CE sessions and one /24 customer subnet per site in the `10.200.0.0/16` supernet. **SD-WAN service (`treasury-branch-sdwan`)** — Tenant `treasury-ops`, hub-spoke on Cisco Viptela cEdge-1000 edges at London (hub), Frankfurt and Amsterdam (spokes); LAN subnets in the `10.250.0.0/16` supernet. An alternate **isp** dataset (Lumina Networks pan-European ISP, eight customer tenants, a different default L3VPN and SD-WAN) is available via the `INFRAHUB_DATASET` env var. ## Key features demonstrated[​](#key-features-demonstrated "Direct link to Key features demonstrated") ### Schema-driven service modelling[​](#schema-driven-service-modelling "Direct link to Schema-driven service modelling") `ServiceL3Vpn`, `ServiceL3VpnSite`, `ServiceSdwan`, `ServiceSdwanSite` define not just object shape but uniqueness constraints, parent/child relationships, dropdown enums for vendor and topology, and lifecycle status. See the **[schema reference](/infrahub-demo-sp/schema-reference.md)** for the full field tables. ### Generator-driven provisioning[​](#generator-driven-provisioning "Direct link to Generator-driven provisioning") When a catalog form creates a `ServiceL3Vpn` or `ServiceSdwan` and the row joins the `l3vpns` or `sdwans` group, Infrahub auto-fires the matching generator. The L3VPN generator allocates a VRF, route targets, a /30 from `pe_ce_pool`, sets up the PE interface, and writes the eBGP session for each site. The SD-WAN generator materialises one CPE per site, allocates the LAN address from the customer subnet, and adds the edge to the vendor-specific group so the artifact pipeline targets it. ### Per-vendor configuration artifacts[​](#per-vendor-configuration-artifacts "Direct link to Per-vendor configuration artifacts") A single schema drives six Jinja2 templates: Arista EOS, Cisco IOS-XR, Juniper Junos, Nokia SR OS / SR Linux, Cisco Viptela cEdge (IOS-XE SD-WAN), and Versa VOS (FlexVNF). Each PE or edge ends up with a `text/plain` configuration artifact keyed by `name__value`, ready to feed into a CI/CD push. ### Branch-based workflows[​](#branch-based-workflows "Direct link to Branch-based workflows") Every catalog submission opens a feature branch (`l3vpn/` or `sdwan/`), creates objects on the branch, polls the generator to completion, regenerates artifacts so the diff is meaningful, and opens a `CoreProposedChange` against `main`. Validation checks run on the PC before merge. ### Containerlab integration[​](#containerlab-integration "Direct link to Containerlab integration") The `clab-mpls-topology` artifact emits a containerlab YAML wiring the four PEs (with Nokia SR OS substituted to SR Linux for the lab image). See the **[containerlab guide](/infrahub-demo-sp/lab/containerlab.md)**. ## Community and support[​](#community-and-support "Direct link to Community and support") * **Source code**: [GitHub repository](https://github.com/opsmill/infrahub-demo-sp) * **Infrahub documentation**: [docs.infrahub.app](https://docs.infrahub.app) * **Discord community**: [Discord](https://discord.gg/opsmill) * **OpsMill website**: [opsmill.com](https://opsmill.com) ## Next steps[​](#next-steps "Direct link to Next steps") Ready to get started? Head to the **[quick start](/infrahub-demo-sp/quickstart.md)** to set up your environment. --- # Architecture This page summarises the data flow and the role of each directory. ## Data flow[​](#data-flow "Direct link to Data flow") ``` Schema definition → Bootstrap data → Generators → Transforms → Artifacts ↓ Checks ``` ## Directory map[​](#directory-map "Direct link to Directory map") | Path | Purpose | | --------------------- | --------------------------------------------------------------- | | `schemas/base/` | Core node definitions copied from schema-library | | `schemas/extensions/` | VRF, routing, BGP, topology extensions from schema-library | | `schemas/sp/` | SP-demo-specific schemas (MPLS, L3VPN service, PE role) | | `objects/` | Pre-loaded bootstrap data (PEs, backbone, pools, tenants) | | `generators/` | `L3VpnGenerator` materialises VRF + interfaces + IPs | | `transforms/` | One Python+Jinja transform per vendor + the clab transform | | `templates/` | Jinja2 templates (one per vendor + clab + macros) | | `checks/` | Five checks gating the proposed-change pipeline (incl. Batfish) | | `service_catalog/` | Streamlit sidecar (Dashboard + Create L3VPN) | | `queries/` | GraphQL queries used by generators, transforms, checks | | `menus/` | Sidebar menu configuration | | `lab/` | Runtime-only; not committed | ## Backbone[​](#backbone "Direct link to Backbone") The MPLS backbone is **static demo data**: 4 PEs (one per vendor) with a pre-built full iBGP mesh, ISIS L2, and LDP. This keeps the demo focused on the L3VPN-as-a-service story. To add or move PEs, edit `objects/60_backbone.yml` by hand and rerun `invoke bootstrap`. ## L3VPN service flow[​](#l3vpn-service-flow "Direct link to L3VPN service flow") When an operator creates an L3VPN through the Streamlit catalog: 1. The catalog allocates a `vpn_id` from the `vpn_id_pool` number pool. 2. It opens a feature branch and creates `ServiceL3Vpn` + one or more `ServiceL3VpnSite` objects. 3. It opens a `CoreProposedChange` targeting `main`. 4. Infrahub runs the `L3VpnGenerator`, which materialises the VRF, route targets, PE-CE interfaces, IP addresses, and an eBGP session if the routing protocol is set to `ebgp`. 5. The proposed-change checks run in the pipeline — any failure blocks the merge. 6. Per-vendor configuration artifacts are rendered by the transform layer. 7. The operator reviews the diff in the Infrahub UI and merges. See [services/l3vpn](/infrahub-demo-sp/services/l3vpn.md) for the full service reference. ## Transform layer[​](#transform-layer "Direct link to Transform layer") Each vendor has a dedicated Python transform module (in `transforms/`) backed by a Jinja2 template (in `templates/`). The transform fetches the full PE state via GraphQL and renders a single, complete device configuration fragment. The mapping is: | Vendor | Transform | Template | | ------------- | -------------------------------- | ------------------------------- | | Arista EOS | `transforms/pe_arista_eos.py` | `templates/pe_arista_eos.j2` | | Cisco IOS-XR | `transforms/pe_cisco_iosxr.py` | `templates/pe_cisco_iosxr.j2` | | Juniper Junos | `transforms/pe_juniper_junos.py` | `templates/pe_juniper_junos.j2` | | Nokia SR OS | `transforms/pe_nokia_sros.py` | `templates/pe_nokia_sros.j2` | | Containerlab | `transforms/clab_topology.py` | `templates/clab_topology.j2` | ## Checks[​](#checks "Direct link to Checks") Five checks gate the proposed-change pipeline: | Check | What it enforces | | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `l3vpn_overlap` | No duplicate VPN IDs across active L3VPNs | | `l3vpn_site_subnet` | Customer subnet is reachable / not already allocated in the same VRF | | `pe_interface_alloc` | The nominated PE interface is free (status = `free`) | | `backbone_session_count` | Every PE has at least 3 iBGP sessions (full mesh invariant) | | `batfish_backbone` | Batfish-driven static validation of rendered backbone configs (parse status, undefined references, BGP session compatibility, IS-IS adjacency mesh). See [Batfish validation](/infrahub-demo-sp/validation/batfish.md). | ## Per-vendor interface name macros[​](#per-vendor-interface-name-macros "Direct link to Per-vendor interface name macros") The schema uses an abstract interface name (`Ethernet1`, `Ethernet2`, …); each vendor template translates that to the platform-native form via a macro in `templates/_macros.j2`: | Vendor | Macro | `Ethernet1` becomes | | ------------------------- | ---------------- | ------------------------ | | Arista EOS | (no translation) | `Ethernet1` | | Cisco IOS-XR | `iosxr_iface` | `GigabitEthernet0/0/0/0` | | Juniper Junos | `junos_iface` | `ge-0/0/0` | | SR Linux (lab substitute) | `srl_iface` | `ethernet-1/1` | Without these macros the rendered IOS-XR / Junos configs would carry Arista-style `Ethernet` interface names — invalid on the target platform and reported as parse errors by `BatfishBackboneCheck`. ## Schema layering[​](#schema-layering "Direct link to Schema layering") Schemas are loaded in three passes so each layer can reference the previous: ``` invoke bootstrap └── infrahubctl schema load schemas/base/ └── infrahubctl schema load schemas/extensions/ └── infrahubctl schema load schemas/sp/ ``` The SP layer adds `ServiceL3Vpn`, `ServiceL3VpnSite`, `TopologyMplsBackbone`, `MplsIsisProcess`, `MplsLdpProcess`, and `MplsBgpProcess`. See [schema-reference](/infrahub-demo-sp/schema-reference.md) for field-level details. --- # Containerlab This page covers the optional containerlab integration: what runs, which PEs are substituted, how to deploy, and known limitations. *** ## Overview[​](#overview "Direct link to Overview") The containerlab artifact (`clab-mpls-topology`) is generated from Infrahub data by the `clab_topology` transform and renders a valid containerlab YAML topology. It wires up four PE nodes and two CE nodes for end-to-end L3VPN testing. The topology is written to `lab/mpls-topology.clab.yml` by `invoke lab.deploy` before containerlab brings it up. *** ## Node map[​](#node-map "Direct link to Node map") | Bootstrap PE | Vendor | Containerlab image | Notes | | ---------------- | ------------- | ---------------------------------- | ---------------------------------------------------- | | `pe-lon-arista` | Arista EOS | `ceos:latest` | Full configuration push via `invoke lab.push-arista` | | `pe-fra-cisco` | Cisco IOS-XR | — | Not in lab v1 (out of scope) | | `pe-ams-juniper` | Juniper Junos | — | Not in lab v1 (out of scope) | | `pe-par-nokia` | Nokia SR OS | SR Linux (`ghcr.io/nokia/srlinux`) | SR Linux used in place of SR OS (see below) | CE nodes run a generic Linux image (`ghcr.io/hellt/network-multitool`) and simulate customer CPE with a loopback address. *** ## SR OS → SR Linux substitution[​](#sr-os--sr-linux-substitution "Direct link to SR OS → SR Linux substitution") Nokia's containerlab-friendly image is **SR Linux**, not SR OS. SR OS is available only with a paid Nokia licence and is not suitable for a public demo. The demo uses SR Linux as a stand-in for the Nokia PE. The Infrahub data model remains `nokia_sros` for schema consistency; the containerlab artifact swaps in the SR Linux image automatically. The SR Linux node boots with the generated Nokia SR OS configuration loaded as a startup configuration, but **SR Linux does not parse SR OS CLI syntax**. Configuration push to the Nokia PE is therefore not available in v1 (see [Known gaps](#known-gaps)). *** ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * containerlab >= 0.50 * Docker with access to pull: * `ceos:latest` (requires an Arista account and local image import — see ) * `ghcr.io/nokia/srlinux` (public) * `ghcr.io/hellt/network-multitool` (public) * A running Infrahub instance with bootstrap data loaded (`invoke init`) *** ## Deploy[​](#deploy "Direct link to Deploy") ``` # Fetch the clab artifact from Infrahub and write lab/mpls-topology.clab.yml, # then call containerlab deploy. uv run invoke lab.deploy ``` Expected output: ``` [INFO] Fetching clab-mpls-topology artifact from Infrahub... [INFO] Writing lab/mpls-topology.clab.yml [INFO] Running: containerlab deploy -t lab/mpls-topology.clab.yml ... +---+------------------+-----------+------------------------------+ | # | Name | Kind | Image | +---+------------------+-----------+------------------------------+ | 1 | pe-lon-arista | ceos | ceos:latest | | 2 | pe-par-nokia-lab | srl | ghcr.io/nokia/srlinux | | 3 | ce-lon | linux | ghcr.io/hellt/network-multitool | | 4 | ce-par | linux | ghcr.io/hellt/network-multitool | +---+------------------+-----------+------------------------------+ ``` *** ## Push Arista configuration[​](#push-arista-configuration "Direct link to Push Arista configuration") After the lab is up, push the Arista EOS configuration artifact from Infrahub: ``` uv run invoke lab.push-arista ``` This fetches the `pe-lon-arista` artifact from Infrahub via the SDK and applies it over the containerlab management network using `pyeapi` / SSH. The command shows a diff of what changed. Verify with: ``` containerlab exec -t lab/mpls-topology.clab.yml --label clab-node-name=pe-lon-arista \ --cmd "show bgp vpn-ipv4 summary" ``` *** ## Destroy[​](#destroy "Direct link to Destroy") ``` uv run invoke lab.destroy ``` This calls `containerlab destroy` and removes the generated topology file. *** ## Lab status[​](#lab-status "Direct link to Lab status") ``` uv run invoke lab.status ``` Shows container health without modifying anything. *** ## Login credentials[​](#login-credentials "Direct link to Login credentials") containerlab registers each node in its embedded DNS as `clab--`. For the bundled MPLS backbone the lab name is `mpls-backbone-1`, so the Arista PE is at `clab-mpls-backbone-1-pe-lon-arista` and the Nokia PE is at `clab-mpls-backbone-1-pe-par-nokia`. | Node | Image | User | Password | Notes | | ---------------------- | ----------------- | ------------ | ------------ | ------------------------------------------- | | `pe-lon-arista` | Arista cEOS | `admin` | `admin` | Set by our startup-configuration, demo-only | | `pe-par-nokia` (CLI) | Nokia SR Linux | `admin` | `NokiaSrl1!` | SR Linux 22.11+ vendor default | | `pe-par-nokia` (shell) | Nokia SR Linux | `linuxadmin` | `NokiaSrl1!` | Shell access, same default | | `ce-*` (netshoot) | nicolaka/netshoot | `root` | (none) | Plain Linux containers | To reach the CLIs: ``` # Arista — via SSH or docker exec ssh admin@clab-mpls-backbone-1-pe-lon-arista docker exec -it clab-mpls-backbone-1-pe-lon-arista Cli # Nokia SR Linux ssh admin@clab-mpls-backbone-1-pe-par-nokia docker exec -it clab-mpls-backbone-1-pe-par-nokia sr_cli # CE Linux node docker exec -it clab-mpls-backbone-1-ce-trading-floor-vpn-pe-lon-arista bash ``` The cEOS password (`admin`) is set by the startup-configuration we render — specifically the `username admin privilege 15 role network-admin secret 0 admin` line in `templates/pe_arista_eos.j2`. It's **demo-only**; if you ever publish from this template to real Arista hardware, swap the secret or gate the line behind a flag. The Nokia password is the SR Linux 22.11+ default. Older SR Linux releases used `admin` as the password — adjust if you've pinned a different image tag in `templates/clab_topology.j2`. *** ## Known gaps[​](#known-gaps "Direct link to Known gaps") | Gap | Details | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Nokia SR Linux configuration push | SR Linux uses a different CLI/API from SR OS, and the public SR Linux 23.10 image lacks several constructs SR OS supports (LDP, `l3vpn-ipv4-unicast` afi-safi, `bgp-vpn` under `network-instance`). The dedicated `pe-nokia-srlinux` artifact intentionally renders a **lab-minimum** configuration — hostname + interfaces + ISIS underlay only. BGP and per-VPN ip-vrf signalling are skipped on the SRL lab node. The production-quality configuration lives in the `pe-nokia-sros` artifact (real SR OS syntax) and is meant for a real Nokia box. | | Cisco / Juniper PEs | IOS-XR and Junos container images require vendor licences; these PEs are absent from the v1 lab topology. | | CE routing | CE nodes have loopbacks but no BGP daemon configured. Confirming end-to-end L3VPN reachability requires manual CE configuration. | *** ## Image pull failures[​](#image-pull-failures "Direct link to Image pull failures") If containerlab fails with `Error response from daemon: pull access denied`: 1. **SR Linux** (`ghcr.io/nokia/srlinux`) — requires a GitHub login: `docker login ghcr.io -u -p ` 2. **cEOS** — the image is not on any public registry. Download the `.tar.xz` from and import it: ``` docker import cEOS-lab-4.30.0F.tar.xz ceos:latest ``` 3. **network-multitool** (`ghcr.io/hellt/network-multitool`) — public image; if it fails, check Docker Hub rate limits or mirror via a local registry. --- # Quick start This page gets you from clone to a running demo in about 10 minutes. For an overview of what `invoke init` creates, see the [landing page](/infrahub-demo-sp.md#architecture-at-a-glance). ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Docker / Docker Compose * Python 3.10+ * `uv` () * (Optional) containerlab >= 0.50 * (Optional) `pnpm` () — only needed for `uv run invoke docs`, which builds the Docusaurus site locally ## 1. Clone and install[​](#1-clone-and-install "Direct link to 1. Clone and install") ``` git clone https://github.com/opsmill/infrahub-demo-sp.git cd infrahub-demo-sp cp .env.example .env uv sync ``` Open `.env` and review the values — see [Configuration](#configuration) below for what each variable does. The defaults are fine for a local demo; you typically only edit `INFRAHUB_SERVICE_CATALOG` (to enable the Streamlit sidecar) and `INFRAHUB_DATASET` (to switch tenant theme). ## 2. Start Infrahub and bootstrap data[​](#2-start-infrahub-and-bootstrap-data "Direct link to 2. Start Infrahub and bootstrap data") ``` uv run invoke init ``` This destroys any prior state, starts the containers, loads the schemas, the menu, all bootstrap objects (4 PEs, full backbone, resource pools, plus the selected customer-facing dataset), and generates schema protocol bindings. Wait \~30 s after the containers come up before bootstrap runs. ## 3. Open the Infrahub UI[​](#3-open-the-infrahub-ui "Direct link to 3. Open the Infrahub UI") Visit — log in with `admin` / `infrahub`. The sidebar menu shows **Service Catalog → L3 VPNs**, **Topology → MPLS Backbones**, and **MPLS** (ISIS / LDP / MP-BGP processes). ## 4. Start the Streamlit Service Catalog[​](#4-start-the-streamlit-service-catalog "Direct link to 4. Start the Streamlit Service Catalog") Set `INFRAHUB_SERVICE_CATALOG="true"` in `.env`, then run: ``` uv run invoke start ``` Every `invoke start` / `invoke init` will now also build and start the Streamlit sidecar. Visit . Create your first L3VPN. The Service Catalog also exposes a second wizard — **Create SD-WAN service** — for provisioning Cisco Viptela or Versa Networks SD-WAN overlays. The flow mirrors Create L3VPN: pick vendor and topology, list the sites, submit. See [services/sdwan](/infrahub-demo-sp/services/sdwan.md) for the lifecycle, vendor differences, and known gaps. ## 5. (Optional) Run Batfish validation[​](#5-optional-run-batfish-validation "Direct link to 5. (Optional) Run Batfish validation") Under the sidebar **Validation → Batfish Check** the catalog runs `BatfishBackboneCheck` against the rendered MPLS backbone configs and shows findings bucketed by severity. Each query (parse status, undefined references, BGP session compatibility, IS-IS adjacency mesh) progresses with its own status indicator so a slow Batfish init isn't a blank screen. The Batfish sidecar is enabled by default in `docker-compose.override.yml` and started by `invoke start`. You can also run the same check from the command line: ``` uv run invoke batfish ``` See [validation/batfish](/infrahub-demo-sp/validation/batfish.md) for what each finding means and how severity is tiered. ## 6. (Optional) Bring up the containerlab[​](#6-optional-bring-up-the-containerlab "Direct link to 6. (Optional) Bring up the containerlab") See [`lab/containerlab`](/infrahub-demo-sp/lab/containerlab.md). ## Configuration[​](#configuration "Direct link to Configuration") All configurable behaviour is driven by environment variables in `.env` (loaded with `set -a; source .env; set +a` before any `uv run invoke` command). `uv run invoke info` prints the resolved values. | Variable | Default | Effect | | -------------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `INFRAHUB_ADDRESS` | `http://localhost:8000` | Where the SDK / Streamlit catalog reach the server | | `INFRAHUB_API_TOKEN` | demo token | Authentication. Rotate before any non-local use | | `INFRAHUB_UI_URL` | `http://localhost:8000` | Used by the Streamlit catalog for "open in Infrahub" links | | `INFRAHUB_GIT_LOCAL` | `false` | `true` registers a `CoreRepository` pointed at the bind-mounted `/upstream` (no GitHub clone needed). `false` registers a `CoreReadOnlyRepository` against the public GitHub repository | | `INFRAHUB_SERVICE_CATALOG` | `false` | `true` builds + starts the Streamlit sidecar on every `invoke start` / `invoke init` | | `INFRAHUB_DATASET` | `financial` | Customer-facing tenant overlay loaded on top of the shared backbone. One subdirectory of `objects/datasets/`. Ships with `financial` (internal bank, 8 division tenants, `trading-floor-vpn`) and `isp` (Lumina Networks pan-European ISP, 8 customer tenants, `kestrel-bank-mpls`) | | `INFRAHUB_ENTERPRISE` | `false` | `true` streams the Enterprise edition compose file from `https://infrahub.opsmill.io/enterprise/` instead of the Community one. Requires an Enterprise license / pull credentials | | `INFRAHUB_VERSION` | `stable` | Compose file version tag pulled from `infrahub.opsmill.io` (use `latest`, `stable`, or a specific release like `1.4.0`) | | `INFRAHUB_PORT` | `8000` | Override the Infrahub server port | | `PREFECT_PORT` | `4200` | Override the Prefect task-manager port | | `STREAMLIT_PORT` | `8501` | Override the Streamlit catalog port | To switch dataset: ``` # in .env INFRAHUB_DATASET="isp" ``` …then `uv run invoke init` to wipe and reload from the new dataset. ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") See [`troubleshooting`](/infrahub-demo-sp/troubleshooting.md). --- # Schema reference This page covers the SP-demo-specific schema nodes defined in `schemas/sp/`. Base and extension schemas (devices, interfaces, IPAM, routing) come from the Infrahub schema-library and are not repeated here. For the overall schema loading order and layering, see [architecture](/infrahub-demo-sp/architecture.md). *** ## ServiceL3Vpn[​](#servicel3vpn "Direct link to ServiceL3Vpn") **Namespace:** `Service` · **Kind:** `ServiceL3Vpn` **File:** `schemas/sp/service_l3vpn.yml` Represents a single L3VPN service offered to a tenant. One `ServiceL3Vpn` has one or more `ServiceL3VpnSite` attachments (component relationship). ### Attributes[​](#attributes "Direct link to Attributes") | Field | Kind | Required | Description | Who sets it | | ---------------- | -------- | -------- | ------------------------------------------------------------------------------------- | ------------------------------------------------- | | `name` | Text | Required | Unique human name for the VPN (for example, `acme-prod`) | User | | `description` | Text | Optional | Free-text description | User | | `vpn_id` | Number | Required | Unique integer allocated from `vpn_id_pool`. Used to derive RD/RT as `:` | User (catalog allocates from pool) | | `address_family` | Dropdown | Required | `ipv4` or `ipv4_ipv6`. Defaults to `ipv4` | User | | `status` | Dropdown | Required | `draft` → `active` → `decommissioned`. Defaults to `draft` | User sets `draft`; generator promotes to `active` | ### Relationships[​](#relationships "Direct link to Relationships") | Field | Peer | Cardinality | Required | Description | Who sets it | | -------- | --------------------- | ----------- | -------- | --------------------------------- | -------------- | | `tenant` | `OrganizationGeneric` | one | Required | Owning organisation | User | | `vrf` | `IpamVRF` | one | Optional | VRF materialised by the generator | Generator | | `sites` | `ServiceL3VpnSite` | many | Optional | PE attachment points (component) | User / catalog | *** ## ServiceL3VpnSite[​](#servicel3vpnsite "Direct link to ServiceL3VpnSite") **Namespace:** `Service` · **Kind:** `ServiceL3VpnSite` **File:** `schemas/sp/service_l3vpn.yml` Represents a single PE attachment of an L3VPN to a customer subnet. One site = one PE + one customer-facing interface + a /30 PE-CE link. ### Attributes[​](#attributes-1 "Direct link to Attributes") | Field | Kind | Required | Description | Who sets it | | ------------------ | -------- | -------- | ------------------------------------------------------------------------ | ------------------------------ | | `name` | Text | Required | Unique within a given `l3vpn` (uniqueness constraint) | User | | `routing_protocol` | Dropdown | Required | `ebgp`, `static`, or `connected` | User | | `bgp_peer_asn` | Number | Optional | Customer BGP ASN (required when `routing_protocol = ebgp`) | User | | `static_routes` | JSON | Optional | List of static routes to inject into the VRF | User | | `status` | Dropdown | Required | `provisioning` → `active` → `decommissioned`. Defaults to `provisioning` | Generator promotes to `active` | ### Relationships[​](#relationships-1 "Direct link to Relationships") | Field | Peer | Cardinality | Required | Description | Who sets it | | ----------------- | ------------------- | ----------- | -------- | ----------------------------------------- | ---------------------------------------- | | `l3vpn` | `ServiceL3Vpn` | one | Required | Parent VPN (parent relationship) | User / catalog | | `pe_device` | `DcimDevice` | one | Required | PE router for this attachment | User | | `pe_interface` | `InterfacePhysical` | one | Optional | PE-facing physical interface | Generator allocates from free interfaces | | `customer_subnet` | `IpamPrefix` | one | Required | Customer prefix to be placed into the VRF | User | | `pe_address` | `IpamIPAddress` | one | Optional | PE IP on the PE-CE /30 link | Generator allocates from `pe_ce_pool` | | `ce_address` | `IpamIPAddress` | one | Optional | CE IP on the PE-CE /30 link | Generator allocates from `pe_ce_pool` | *** ## ServiceSdwan[​](#servicesdwan "Direct link to ServiceSdwan") **Namespace:** `Service` · **Kind:** `ServiceSdwan` **File:** `schemas/sp/service_sdwan.yml` Represents a single SD-WAN service offered to a tenant. One `ServiceSdwan` has one or more `ServiceSdwanSite` attachments (component relationship). ### Attributes[​](#attributes-2 "Direct link to Attributes") | Field | Kind | Required | Description | Who sets it | | ------------- | -------- | -------- | --------------------------------------------------------------------------------- | ------------------------------------------------- | | `name` | Text | Required | Unique human name for the SD-WAN service (for example, `treasury-branch-sdwan`) | User | | `description` | Text | Optional | Free-text description | User | | `service_id` | Number | Required | Unique integer allocated from `sdwan_id_pool`. Used for SD-WAN site-id derivation | User (catalog allocates from pool) | | `vendor` | Dropdown | Required | `viptela` (Cisco SD-WAN) or `versa` (Versa Networks). Defaults to `viptela` | User | | `topology` | Dropdown | Required | `full-mesh` or `hub-spoke`. Defaults to `full-mesh` | User | | `status` | Dropdown | Required | `draft` → `active` → `decommissioned`. Defaults to `draft` | User sets `draft`; generator promotes to `active` | ### Relationships[​](#relationships-2 "Direct link to Relationships") | Field | Peer | Cardinality | Required | Description | Who sets it | | -------- | --------------------- | ----------- | -------- | ----------------------------- | -------------- | | `tenant` | `OrganizationGeneric` | one | Required | Owning organisation | User | | `sites` | `ServiceSdwanSite` | many | Optional | SD-WAN edge sites (component) | User / catalog | *** ## ServiceSdwanSite[​](#servicesdwansite "Direct link to ServiceSdwanSite") **Namespace:** `Service` · **Kind:** `ServiceSdwanSite` **File:** `schemas/sp/service_sdwan.yml` Represents a single SD-WAN edge site: one location, one edge device, one LAN subnet, one LAN IP. The edge `DcimDevice` is created automatically by the generator (it isn't pre-seeded in any dataset). ### Attributes[​](#attributes-3 "Direct link to Attributes") | Field | Kind | Required | Description | Who sets it | | -------- | -------- | -------- | ------------------------------------------------------------------------ | ------------------------------ | | `name` | Text | Required | Unique within a given `sdwan` (uniqueness constraint) | User | | `role` | Dropdown | Required | `hub`, `spoke`, or `branch`. Defaults to `spoke` | User | | `status` | Dropdown | Required | `provisioning` → `active` → `decommissioned`. Defaults to `provisioning` | Generator promotes to `active` | ### Relationships[​](#relationships-3 "Direct link to Relationships") | Field | Peer | Cardinality | Required | Description | Who sets it | | ------------- | --------------- | ----------- | -------- | ------------------------------------------- | ------------------------------------- | | `sdwan` | `ServiceSdwan` | one | Required | Parent SD-WAN service (parent relationship) | User / catalog | | `location` | `LocationSite` | one | Required | Customer site / PoP this edge attaches to | User | | `lan_subnet` | `IpamPrefix` | one | Required | Customer LAN subnet behind this edge | User | | `lan_address` | `IpamIPAddress` | one | Optional | Edge's LAN IP (network address + 1) | Generator allocates from `lan_subnet` | | `sdwan_edge` | `DcimDevice` | one | Optional | Edge router materialised at this site | Generator creates per-site | *** ## TopologyMplsBackbone[​](#topologymplsbackbone "Direct link to TopologyMplsBackbone") **Namespace:** `Topology` · **Kind:** `TopologyMplsBackbone` **File:** `schemas/sp/topology_mpls.yml` Inherits from `TopologyGeneric`. Represents the MPLS backbone as a whole — one row per backbone domain. The demo has exactly one row: `mpls-backbone-1`. ### Attributes[​](#attributes-4 "Direct link to Attributes") | Field | Kind | Required | Description | Who sets it | | ------------ | -------- | -------- | -------------------------------------------------------- | ---------------- | | `name` | Text | Required | Unique backbone name | User (bootstrap) | | `isis_area` | Text | Required | ISIS area ID (default `49.0001`) | User (bootstrap) | | `isis_level` | Dropdown | Required | `level-1`, `level-2`, or `level-1-2` (default `level-2`) | User (bootstrap) | ### Relationships[​](#relationships-4 "Direct link to Relationships") | Field | Peer | Cardinality | Required | Description | Who sets it | | ----- | ------------------------- | ----------- | -------- | -------------------------------------- | ---------------- | | `asn` | `RoutingAutonomousSystem` | one | Required | Provider ASN used for RD/RT derivation | User (bootstrap) | | `pes` | `DcimDevice` | many | Optional | All PE routers in this backbone | User (bootstrap) | *** ## MplsIsisProcess[​](#mplsisisprocess "Direct link to MplsIsisProcess") **Namespace:** `Mpls` · **Kind:** `MplsIsisProcess` **File:** `schemas/sp/mpls.yml` Inherits from `RoutingProtocol`. One row per PE representing the IS-IS routing process. The `device` relationship is inherited from `RoutingProtocol`. ### Attributes[​](#attributes-5 "Direct link to Attributes") | Field | Kind | Required | Description | Who sets it | | --------- | -------- | -------- | ------------------------------------------------------------- | ---------------- | | `area_id` | Text | Required | IS-IS area (default `49.0001`) | User (bootstrap) | | `level` | Dropdown | Required | `level-1`, `level-2`, or `level-1-2` (default `level-2`) | User (bootstrap) | | `net_id` | Text | Required | ISO NET identifier (for example, `49.0001.0100.0000.0001.00`) | User (bootstrap) | ### Relationships[​](#relationships-5 "Direct link to Relationships") | Field | Peer | Cardinality | Required | Description | | ------------ | ------------------- | ----------- | -------- | --------------------------------- | | `interfaces` | `InterfacePhysical` | many | Optional | Backbone interfaces running IS-IS | ### Inherited from RoutingProtocol[​](#inherited-from-routingprotocol "Direct link to Inherited from RoutingProtocol") | Field | Description | | ------------- | ------------------------------------- | | `device` | The PE device this process belongs to | | `description` | Free-text description | | `status` | `active` / `inactive` | *** ## MplsLdpProcess[​](#mplsldpprocess "Direct link to MplsLdpProcess") **Namespace:** `Mpls` · **Kind:** `MplsLdpProcess` **File:** `schemas/sp/mpls.yml` Inherits from `RoutingProtocol`. One row per PE representing the LDP label distribution process. ### Attributes[​](#attributes-6 "Direct link to Attributes") | Field | Kind | Required | Description | Who sets it | | ----------- | ---- | -------- | ------------------------------------------ | ---------------- | | `router_id` | Text | Required | LDP router-ID (typically the Loopback0 IP) | User (bootstrap) | ### Relationships[​](#relationships-6 "Direct link to Relationships") | Field | Peer | Cardinality | Required | Description | | ------------------- | ------------------- | ----------- | -------- | ------------------------------- | | `transport_address` | `IpamIPAddress` | one | Optional | LDP transport address override | | `interfaces` | `InterfacePhysical` | many | Optional | Backbone interfaces running LDP | *** ## MplsBgpProcess[​](#mplsbgpprocess "Direct link to MplsBgpProcess") **Namespace:** `Mpls` · **Kind:** `MplsBgpProcess` **File:** `schemas/sp/mpls.yml` Inherits from `RoutingProtocol`. One row per PE representing the MP-BGP overlay process carrying VPNv4 / VPNv6 address families. ### Attributes[​](#attributes-7 "Direct link to Attributes") | Field | Kind | Required | Description | Who sets it | | ------------------ | ---- | -------- | ------------------------------------------------------------------- | ---------------- | | `router_id` | Text | Required | BGP router-ID (typically the Loopback0 IP) | User (bootstrap) | | `address_families` | List | Required | MP-BGP address families enabled (for example, `["vpnv4", "vpnv6"]`) | User (bootstrap) | ### Relationships[​](#relationships-7 "Direct link to Relationships") | Field | Peer | Cardinality | Required | Description | | ---------- | ------------------- | ----------- | -------- | ------------------------------ | | `sessions` | `RoutingBGPSession` | many | Optional | iBGP sessions for this process | *** ## LocationRegion / LocationSite[​](#locationregion--locationsite "Direct link to LocationRegion / LocationSite") **Namespace:** `Location` · **File:** `schemas/sp/location_concrete.yml` Concrete location kinds for this demo. `LocationRegion` inherits from `LocationGeneric` (the schema-library generic), and `LocationSite` adds hosting capability via `LocationHosting`. Both render under `LocationGeneric` in the sidebar menu. The bootstrap data uses `LocationRegion` for `emea` and `LocationSite` for the four PoPs (`lon`, `fra`, `ams`, `par`). | Kind | Inherits | Purpose | | ---------------- | ------------------------------------ | --------------------------------------- | | `LocationRegion` | `LocationGeneric` | Geographic region (for example, `emea`) | | `LocationSite` | `LocationGeneric`, `LocationHosting` | PoP / site that hosts PEs | *** ## DcimDevice role extension[​](#dcimdevice-role-extension "Direct link to DcimDevice role extension") **File:** `schemas/sp/dcim_role_pe.yml` This file is a schema *extension* (not a new node) that adds SP-specific choices to the existing `DcimDevice.role` dropdown: | Role | Label | Use | | ------ | -------------------------- | ----------------------------------------- | | `pe` | Provider Edge | PE router terminating customer VPNs | | `p` | Provider Core | P router (transit only, no customer VRFs) | | `rr` | Route Reflector | BGP route reflector | | `core` | Core Router | Central part of the network | | `edge` | Edge Router | Network boundary with external networks | | `cpe` | Customer Premise Equipment | Devices at the customer's premises | The demo bootstrap sets `role = pe` on all four PEs. *** ## Resource pools[​](#resource-pools "Direct link to Resource pools") The following pools are bootstrapped in `objects/50_pools.yml` and consumed by the generator and the Streamlit catalog: | Pool name | Kind | Purpose | Allocation unit | | ------------------- | ------------------ | ---------------------------------- | ---------------------------------------------------------------------------------- | | `vpn_id_pool` | `CoreNumberPool` | Globally unique VPN IDs | Integer (range 200–9999; values 100–199 are reserved for bootstrap-seeded L3VPNs) | | `sdwan_id_pool` | `CoreNumberPool` | Globally unique SD-WAN service IDs | Integer (range 200–9999; values 100–199 are reserved for bootstrap-seeded SD-WANs) | | `pe_loopback_pool` | `CoreIPPrefixPool` | Loopback /32 addresses | /32 from `10.0.0.0/24` | | `backbone_p2p_pool` | `CoreIPPrefixPool` | Backbone p2p /31 links | /31 from `10.1.0.0/16` | | `pe_ce_pool` | `CoreIPPrefixPool` | PE-CE /30 links per L3VPN site | /30 from `10.100.0.0/16` | *** ## User-provided vs generator-filled summary[​](#user-provided-vs-generator-filled-summary "Direct link to User-provided vs generator-filled summary") The following table consolidates which fields an operator must supply and which the `L3VpnGenerator` fills in automatically: | Object | Field | Source | | --------------------------- | ----------------------------------------------------------------------------------- | -------------- | | `ServiceL3Vpn` | `name`, `vpn_id`, `address_family`, `tenant` | User / catalog | | `ServiceL3Vpn` | `vrf`, `status = active` | Generator | | `ServiceL3VpnSite` | `name`, `pe_device`, `routing_protocol`, `bgp_peer_asn`, `customer_subnet`, `l3vpn` | User / catalog | | `ServiceL3VpnSite` | `pe_interface`, `pe_address`, `ce_address`, `status = active` | Generator | | `IpamVRF` | all | Generator | | `IpamIPAddress` (PE-CE) | all | Generator | | `RoutingBGPSession` (PE-CE) | all (only when `routing_protocol = ebgp`) | Generator | --- # L3VPN service This page documents the L3VPN service end-to-end: how an operator creates one through the Streamlit catalog, what Infrahub does automatically, and how each vendor's configuration differs. For field-level schema details see [schema-reference](/infrahub-demo-sp/schema-reference.md). *** ## User flow[​](#user-flow "Direct link to User flow") ### Step-by-step[​](#step-by-step "Direct link to Step-by-step") 1. **Catalog form** — The operator fills in the VPN name, selects a tenant, picks a PE and a customer subnet for each site, and chooses a routing protocol (eBGP / static / connected). 2. **Branch + objects** — The catalog allocates a `vpn_id` from `vpn_id_pool`, opens a feature branch, and writes `ServiceL3Vpn` + `ServiceL3VpnSite` objects to Infrahub. All objects start with `status = draft / provisioning`. 3. **Proposed Change** — The catalog opens a `CoreProposedChange` from the feature branch into `main`. This triggers the generator and checks automatically. 4. **Generator run** — `L3VpnGenerator` does all heavy lifting: * Creates an `IpamVRF` with `vrf_rd = :`. * Creates matching import and export route targets. * Selects the lowest-numbered free interface on the PE. * Allocates a /30 from `pe_ce_pool` and creates the PE and CE IP addresses. * Places the customer subnet prefix into the VRF. * Creates a `RoutingBGPSession` (eBGP, `session_type = EXTERNAL`) when `routing_protocol = ebgp`. * Promotes `ServiceL3Vpn.status` to `active` and `ServiceL3VpnSite.status` to `active`. 5. **Checks** — Four checks must pass before merge is allowed (see [Checks](#checks) below). 6. **Transforms** — Configuration artifacts are rendered for each PE that has at least one L3VPN site. 7. **Merge** — The operator reviews the diff in the Infrahub UI and merges. On the target platform the artifact is pushed via the relevant `invoke` task (for example, `invoke lab.push-arista`). *** ## Checks[​](#checks "Direct link to Checks") | Check | File | What it enforces | | ------------------------ | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `l3vpn_overlap` | `checks/l3vpn_overlap.py` | No two active `ServiceL3Vpn` objects share the same `vpn_id` | | `l3vpn_site_subnet` | `checks/l3vpn_site_subnet.py` | The customer subnet is not already claimed by another site in the same VRF | | `pe_interface_alloc` | `checks/pe_interface_alloc.py` | The nominated PE interface exists and has `status = free` | | `backbone_session_count` | `checks/backbone_session_count.py` | Every PE in the backbone has at least 3 iBGP sessions (full-mesh invariant) | | `batfish_backbone` | `checks/batfish_backbone.py` | Static validation of the rendered backbone configs via Batfish — see [Batfish validation](/infrahub-demo-sp/validation/batfish.md) for the full query battery | A failing check blocks merge of the proposed change. The operator can see the check result in the Infrahub UI under the proposed change's **Checks** tab. *** ## Generator: what it creates[​](#generator-what-it-creates "Direct link to Generator: what it creates") | Object | Created by | Notes | | ----------------------------- | ------------------------------ | -------------------------------------------------------------- | | `IpamVRF` | `_ensure_vrf` | Name = VPN name; `vrf_rd` = `:` | | `IpamRouteTarget` | `find_or_create_route_target` | Same value used for import and export RT | | `InterfacePhysical` (updated) | `next_free_physical_interface` | Lowest free-status interface on the PE; role set to `cust` | | `IpamPrefix` (/30) | `allocate_prefix_from_pool` | Allocated from `pe_ce_pool`; placed in VRF | | `IpamIPAddress` × 2 | direct create | `.1` = PE, `.2` = CE within the /30 | | `RoutingBGPSession` | `_ensure_ebgp_session` | Only when `routing_protocol = ebgp`; `session_type = EXTERNAL` | The generator is idempotent: re-running it on an L3VPN that already has a VRF and allocated addresses will skip re-creation and only update missing fields. *** ## Per-vendor configuration[​](#per-vendor-configuration "Direct link to Per-vendor configuration") The transform layer renders a full configuration fragment for each PE when that PE has at least one active L3VPN site. Configuration sections differ by vendor: ### Arista EOS[​](#arista-eos "Direct link to Arista EOS") ``` vrf instance rd : ! router bgp vrf rd : route-target import route-target export neighbor remote-as network ``` ### Cisco IOS-XR[​](#cisco-ios-xr "Direct link to Cisco IOS-XR") ``` vrf address-family ipv4 unicast import route-target export route-target ! router bgp vrf rd : address-family ipv4 unicast neighbor remote-as ``` ### Juniper Junos[​](#juniper-junos "Direct link to Juniper Junos") ``` routing-instances { { instance-type vrf; interface ; vrf-target target:; protocols { bgp { group PE-CE { neighbor { peer-as ; } } } } } } ``` ### Nokia SR OS[​](#nokia-sr-os "Direct link to Nokia SR OS") ``` service { vprn "" customer "1" create route-distinguisher vrf-target target: interface "" create address / sap :0 create exit exit bgp-vpn-backup no shutdown exit } ``` --- # SD-WAN service The SD-WAN service models a customer's SD-WAN overlay across one or more sites. Each site has a dedicated edge device (a per-site `DcimDevice` created automatically by the generator). The default vendor is Cisco Viptela (cEdge / IOS-XE SD-WAN); Versa Networks VOS is available as an alternate. For field-level schema details see [schema-reference](/infrahub-demo-sp/schema-reference.md). *** ## User flow[​](#user-flow "Direct link to User flow") ### Step-by-step[​](#step-by-step "Direct link to Step-by-step") 1. **Catalog form** — Operator picks vendor (Viptela / Versa), topology (hub-spoke / full-mesh), and lists one site per location with a LAN subnet. 2. **Branch + objects** — Catalog allocates a `service_id` from `sdwan_id_pool`, opens a feature branch, writes `ServiceSdwan` and `ServiceSdwanSite` rows, and adds the service to the `sdwans` group. 3. **Generator** — `SdwanGenerator` fires automatically on group membership. For each site it creates one edge `DcimDevice` (`--edge`) with the vendor's platform / device type / manufacturer, adds the device to the vendor-specific edge group (`sdwan_viptela_edges` or `sdwan_versa_edges`), allocates a LAN address from the site's subnet, and flips both site and service status to `active`. 4. **Artifacts** — Infrahub renders one configuration per edge via the matching transform (`sdwan_viptela` or `sdwan_versa`). 5. **Proposed Change** — Reviewable diff in the UI; merging promotes the service to `main`. *** ## Schema shape[​](#schema-shape "Direct link to Schema shape") The shape parallels `ServiceL3Vpn`: * `ServiceSdwan` — name, `service_id`, `vendor`, `topology`, tenant, sites. * `ServiceSdwanSite` — name, `role` (hub / spoke / branch), `location`, `lan_subnet`, `lan_address`, `sdwan_edge`. *** ## Vendor differences[​](#vendor-differences "Direct link to Vendor differences") | Aspect | Viptela (cEdge) | Versa (FlexVNF) | | -------------------- | ------------------------------------------------ | ----------------------------------------- | | Platform | `cisco_viptela` | `versa_flexvnf` | | Device type | `cEdge-1000` | `FlexVNF-200` | | Edge group | `sdwan_viptela_edges` | `sdwan_versa_edges` | | Artifact definition | `sdwan-viptela-config` | `sdwan-versa-config` | | Configuration flavor | IOS-XE SD-WAN CLI (`system` / `sdwan` / `vpn N`) | Versa VOS CLI (`set orgs org-services …`) | *** ## Checks[​](#checks "Direct link to Checks") * `sdwan_id_overlap` — no two services share `service_id`. Safety net behind the pool. * `sdwan_site_subnet` — no two sites within the same service have overlapping LAN subnets. *** ## Known gaps[​](#known-gaps "Direct link to Known gaps") * **No SD-WAN controllers** modelled — vManage / vSmart / vBond and Versa Director / Analytics are out of scope for v1. * **No transport circuits** — edges have only a LAN-side address; no MPLS-vs-Internet-vs-LTE distinction. * **No overlay tunnels or BGP** in rendered configs — templates emit the intent (system identity, `vpn 1` LAN block, organization name) but peer-site lists are comments only. * **No containerlab support** — the `clab-mpls-topology` artifact stays MPLS-only. SD-WAN edges would require a `data.ServiceSdwanSite.edges` loop in the clab template and a new `linux` CE per SD-WAN site. --- # Troubleshooting Common failure modes and their fixes. If your problem is not here, check the [Infrahub documentation](https://docs.infrahub.app) or open an issue. *** ## Bootstrap timeout[​](#bootstrap-timeout "Direct link to Bootstrap timeout") **Symptom:** ``` [ERROR] ConnectionRefusedError: [Errno 111] Connection refused ``` or the bootstrap script exits immediately with "Infrahub not reachable". **Cause:** The bootstrap script runs too soon after `docker compose up`. Infrahub takes 20–40 seconds to initialise its database and API server. **Fix:** ``` # Wait for Infrahub to be ready, then bootstrap manually: uv run invoke start sleep 40 uv run invoke bootstrap ``` Alternatively, run `invoke init` which includes a built-in wait, but if your machine is slow, increase the sleep in `tasks.py`: ``` # tasks.py — find the sleep call in init_demo and increase it time.sleep(40) # change to 60 or more on slow machines ``` *** ## Generator fails: "No free physical interface"[​](#generator-fails-no-free-physical-interface "Direct link to Generator fails: \"No free physical interface\"") **Symptom:** ``` RuntimeError: No free physical interface on pe-lon-arista ``` **Cause:** The `L3VpnGenerator` allocates PE-CE interfaces by picking the first `InterfacePhysical` with `status = free` on the target PE. All interfaces on that PE are already in use (either `active` or `cust`). **Fix:** Create a free interface on the PE via the Infrahub UI or GraphQL: ``` curl -s -X POST http://localhost:8000/graphql \ -H "X-INFRAHUB-KEY: 06438eb2-8019-4776-878c-0941b1f1d1ec" \ -H "Content-Type: application/json" \ -d '{ "query": "mutation { InterfacePhysicalCreate(data: { name: {value: \"Ethernet10\"}, status: {value: \"free\"}, mtu: {value: 9000}, device: {hfid: [\"pe-lon-arista\"]} }) { ok object { id } } }" }' ``` Repeat for whichever PE is out of free interfaces. *** ## Port conflict: Infrahub already running[​](#port-conflict-infrahub-already-running "Direct link to Port conflict: Infrahub already running") **Symptom:** ``` Error response from daemon: driver failed programming external connectivity ... Bind for 0.0.0.0:8000 failed: port is already allocated ``` **Cause:** Another process (or a leftover container) is using port 8000, 4200 (Prefect), or 8501 (Streamlit). **Fix:** ``` # Find what is using the port: lsof -i :8000 # Stop leftover containers from this project: docker compose -p sp-demo down # If another unrelated project is running on the same port, # override the port in .env: echo "INFRAHUB_PORT=8001" >> .env # Then update INFRAHUB_ADDRESS accordingly: echo 'INFRAHUB_ADDRESS="http://localhost:8001"' >> .env uv run invoke start ``` *** ## Containerlab: image pull failure[​](#containerlab-image-pull-failure "Direct link to Containerlab: image pull failure") ### SR Linux (`ghcr.io/nokia/srlinux`)[​](#sr-linux-ghcrionokiasrlinux "Direct link to sr-linux-ghcrionokiasrlinux") ``` Error response from daemon: Head "https://ghcr.io/...": unauthorized ``` SR Linux is on GitHub Container Registry and requires a GitHub token: ``` docker login ghcr.io -u -p ``` Create a PAT at with `read:packages` scope. ### Arista cEOS (`ceos:latest`)[​](#arista-ceos-ceoslatest "Direct link to arista-ceos-ceoslatest") cEOS is not on any public registry. Download the `.tar.xz` from the Arista Software Downloads portal (requires an Arista account) and import it: ``` docker import cEOS-lab-4.30.0F.tar.xz ceos:latest docker image ls ceos ``` ### network-multitool (`ghcr.io/hellt/network-multitool`)[​](#network-multitool-ghcriohelltnetwork-multitool "Direct link to network-multitool-ghcriohelltnetwork-multitool") This is a public image. If it fails, you may be hitting Docker Hub rate limits. Authenticate with Docker Hub: ``` docker login docker.io ``` *** ## Streamlit catalog: "Infrahub not reachable"[​](#streamlit-catalog-infrahub-not-reachable "Direct link to Streamlit catalog: \"Infrahub not reachable\"") **Symptom:** The Streamlit app loads but shows "Cannot connect to Infrahub". **Cause:** `INFRAHUB_ADDRESS` is not set or points to the wrong host/port. **Fix:** ``` # Verify the env var is set: grep INFRAHUB_ADDRESS .env # If running Streamlit outside Docker Compose, ensure the address # points to the correct host: echo 'INFRAHUB_ADDRESS="http://localhost:8000"' >> .env uv run streamlit run service_catalog/app.py ``` If you are running the Streamlit app inside Docker Compose, use the internal service name: ``` INFRAHUB_ADDRESS=http://infrahub-server:8000 ``` *** ## Schema load order error[​](#schema-load-order-error "Direct link to Schema load order error") **Symptom:** ``` SchemaNotFound: Node 'RoutingProtocol' not found ``` **Cause:** The SP schemas (`schemas/sp/`) reference base and extension nodes that were not loaded first. **Fix:** Always load schemas in order: ``` infrahubctl schema load schemas/base/ infrahubctl schema load schemas/extensions/ infrahubctl schema load schemas/sp/ ``` `invoke bootstrap` does this automatically. If you loaded schemas manually in the wrong order, run `invoke destroy && invoke init` to start clean. *** ## Proposed Change: check fails after generator runs[​](#proposed-change-check-fails-after-generator-runs "Direct link to Proposed Change: check fails after generator runs") **Symptom:** The `pe_interface_alloc` check fails even though the generator ran and set the interface. **Cause:** The check reads the current branch state. If the generator ran on a previous branch and the change was already merged, the interface status may already be `cust` on `main`, making it appear unavailable for a new site. **Fix:** Use a different (free) interface for the new site, or set an existing `cust`-status interface back to `free` if it was decommissioned. *** ## yamllint: line too long[​](#yamllint-line-too-long "Direct link to yamllint: line too long") **Symptom:** ``` [error] line too long (105 > 100 characters) ``` **Cause:** Bootstrap YAML files sometimes have long inline object specs. **Fix:** Break the long line or raise the limit in `.yamllint.yml`: ``` rules: line-length: max: 120 level: warning # demote from error to warning if needed ``` *** ## uv sync fails[​](#uv-sync-fails "Direct link to uv sync fails") **Symptom:** `uv sync` fails with a resolver error or `requires-python` mismatch. **Fix:** ``` # Verify your Python version: python --version # must be 3.10, 3.11, or 3.12 # If using pyenv: pyenv install 3.12 pyenv local 3.12 uv sync ``` --- # Batfish validation `BatfishBackboneCheck` runs the [Batfish](https://www.batfish.org/) query battery against the rendered MPLS backbone configs and reports findings as Infrahub log entries. It runs automatically on every proposed change touching the backbone, and can also be invoked ad-hoc from the CLI or the Streamlit service catalog. ## What it checks[​](#what-it-checks "Direct link to What it checks") For each MPLS backbone, the check: 1. Fetches the rendered configuration artifact for every PE whose platform is in `SUPPORTED_PLATFORMS` (`arista_eos`, `cisco_iosxr`, `juniper_junos`). Nokia SR OS and SR Linux are not parsed by Batfish today — they're reported as INFO so the gap is explicit, not invisible. 2. Loads the configs into a temporary Batfish snapshot. 3. Runs five Batfish questions and maps each answer row to a `Finding`: | Query | What it flags | Severity | | ------------------------- | ---------------------------------------------------------------------------------------------------- | -------- | | `fileParseStatus` | Files Batfish couldn't model at all (`PARSE_FAIL`, `ORPHANED`, `WILL_NOT_COMMIT`) | ERROR | | `fileParseStatus` | Files partially parsed (`PARTIALLY_UNRECOGNIZED` — Batfish grammar gaps) | WARNING | | `parseWarning` | Per-line "Batfish doesn't model this construct" notes | WARNING | | `undefinedReferences` | Configuration references a structure (route-map, ACL, peer-group) that isn't defined anywhere | ERROR | | `bgpSessionCompatibility` | Configured BGP session doesn't pair cleanly with the other end (mismatched AS, missing remote, etc.) | WARNING | | `isisEdges` | Expected IS-IS adjacency between two PEs isn't present | WARNING | Only ERROR findings block the merge. WARNING and INFO surface in the proposed-change log so reviewers see them without preventing the merge. ## Run from the CLI[​](#run-from-the-cli "Direct link to Run from the CLI") ``` uv run invoke batfish # or against a specific backbone: uv run invoke batfish --backbone mpls-backbone-1 ``` Requires: * Infrahub running (`uv run invoke start`) * The `batfish` sidecar running (started automatically by `invoke start`) * At least one rendered `pe-*` artifact (`invoke bootstrap` if you've never bootstrapped, or `invoke init` for a clean rebuild) The CLI exit code is 0 when the check passes — i.e. zero ERROR findings. WARNING and INFO findings still print but don't fail the run. ## Run from the Service Catalog[​](#run-from-the-service-catalog "Direct link to Run from the Service Catalog") The Streamlit catalog (port 8501) has a **Validation → Batfish Check** page. It runs the same query battery as the CLI, but with stage-by-stage progress: 1. *Fetching backbone topology …* 2. *Fetching N rendered configuration(s) …* (one tick per PE) 3. *Waiting for Batfish coordinator at batfish:9996 …* 4. *Initializing snapshot …* 5. *Running query: Parse status / Parse warnings / Undefined references / BGP / IS-IS edges* Findings then render in three tabs (🚫 Errors / ⚠️ Warnings / ℹ️ Info) with per-query expanders inside each tab. The Branch + Backbone selectors are populated from Infrahub so typos can't silently no-op the check. ## Run as a proposed-change check[​](#run-as-a-proposed-change-check "Direct link to Run as a proposed-change check") The check is registered in `.infrahub.yml` against the `topologies_mpls` target. Every proposed change touching an MPLS backbone runs it automatically; the result appears in the proposed change's **Checks** tab in the Infrahub UI. ## The Batfish sidecar[​](#the-batfish-sidecar "Direct link to The Batfish sidecar") `docker-compose.override.yml` adds a `batfish/allinone:latest` container. It exposes the coordinator on port 9996 (`/v2/networks`) and is reachable from any other container on the `sp-demo` network as `batfish:9996`. No host port mapping is set up by default — the sidecar talks only inside the compose network. Set `BATFISH_DISABLED=1` to skip the check entirely (for example, for CI environments that don't want to start the sidecar): ``` BATFISH_DISABLED=1 uv run pytest ``` ## Known limitations[​](#known-limitations "Direct link to Known limitations") * **No SR Linux / Nokia SR OS support.** Batfish doesn't parse either grammar, so those PEs are filtered out and surfaced as INFO findings. * **Partial parses are common.** Batfish's per-vendor grammars lag real vendor syntax — features like `mpls ip`, `vrf-import` policies, and `route-target import vpn-ipv4` show up as parseWarnings even though the configs are valid on the target platform. That's why parseWarning is mapped to WARNING (informational) rather than ERROR. * **IS-IS adjacencies are inferred from configuration, not observed.** Batfish compares advertised IS-IS interface tuples to predict who *should* peer; it doesn't see live protocol state. A `isisEdges` finding may mean "configuration drift between PEs" or "this PE isn't fully meshed with the others yet." --- # Demo guide This guide walks through the AI/DC solution from start to finish. You will load a data center design into Infrahub, trigger Generators that build the infrastructure from that design, and inspect the results. No code changes are required. Prerequisites Python 3.11+, Docker, and [uv](https://docs.astral.sh/uv/) are required. Refer to [Installation & Setup](/infrahub-solution-ai-dc/solution-ai-dc/installation-setup.md) for detailed environment setup instructions. ## What you are about to see[​](#what-you-are-about-to-see "Direct link to What you are about to see") ### The problem[​](#the-problem "Direct link to The problem") AI data center fabrics involve hundreds of standardized switches, thousands of interfaces, and hierarchical IP addressing schemes that must be consistent across every pod and rack. Traditional scripting can automate the initial build, but it discards design intent after execution — the script knows what to create, but that knowledge lives in code, not in the data layer. Day-two changes (adding a rack, expanding a pod) require re-running or patching scripts with no guarantee that existing infrastructure remains untouched. ### Design-driven automation[​](#design-driven-automation "Direct link to Design-driven automation") Infrahub inverts this approach. Operators define *what* the infrastructure should look like — topology, device counts, IP ranges — as structured design objects stored in Infrahub. Generators read those design objects and produce the implementation: devices, IP allocations, cabling plans, and configuration data. Because the design stays in Infrahub as the source of truth, day-two changes are surgical — a Generator rebuilds only its target scope, leaving everything else intact. For more on how Generators work, see the [Infrahub documentation on Generators](https://docs.infrahub.app/topics/generator). ### The fabric topology[​](#the-fabric-topology "Direct link to The fabric topology") Fabric-A is a 5-stage Clos network with the following hierarchy: * **Fabric-A**: 6 super spine switches * **Pod-A1** (role: `fabric`): represents the super spine tier — the FabricGenerator places super spine devices here * **Pod-A2** and **Pod-A3**: each with 4 spine switches * **4 racks under Pod-A2**: Rack-A2-1 through Rack-A2-4, each containing 1–2 leaf switches * **4 racks under Pod-A3**: Rack-A3-1 through Rack-A3-4, each containing 1–2 leaf switches ![Fabric-A topology diagram](data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDYwIDUyMCIgZm9udC1mYW1pbHk9IkludGVyLCAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsICdTZWdvZSBVSScsIFJvYm90bywgc2Fucy1zZXJpZiI+CiAgPGRlZnM+CiAgICA8ZmlsdGVyIGlkPSJzaGFkb3ciIHg9Ii0yJSIgeT0iLTIlIiB3aWR0aD0iMTA0JSIgaGVpZ2h0PSIxMDQlIj4KICAgICAgPGZlRHJvcFNoYWRvdyBkeD0iMCIgZHk9IjEiIHN0ZERldmlhdGlvbj0iMiIgZmxvb2Qtb3BhY2l0eT0iMC4xIi8+CiAgICA8L2ZpbHRlcj4KICA8L2RlZnM+CgogIDwhLS0gRmFicmljLUEgY29udGFpbmVyIC0tPgogIDxyZWN0IHg9IjEwIiB5PSIxMCIgd2lkdGg9IjEwNDAiIGhlaWdodD0iNTAwIiByeD0iMTIiIGZpbGw9IiNmNWY1ZjUiIHN0cm9rZT0iI2QwZDBkMCIgc3Ryb2tlLXdpZHRoPSIxLjUiLz4KICA8dGV4dCB4PSIzMCIgeT0iMzgiIGZvbnQtc2l6ZT0iMTYiIGZvbnQtd2VpZ2h0PSI3MDAiIGZpbGw9IiMzMzMiPkZhYnJpYy1BPC90ZXh0PgoKICA8IS0tIFN1cGVyIFNwaW5lcyAtLT4KICA8cmVjdCB4PSIzNTAiIHk9IjU1IiB3aWR0aD0iMzYwIiBoZWlnaHQ9IjU1IiByeD0iOCIgZmlsbD0iIzRhOTBkOSIgZmlsdGVyPSJ1cmwoI3NoYWRvdykiLz4KICA8dGV4dCB4PSI1MzAiIHk9Ijc4IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LXNpemU9IjE1IiBmb250LXdlaWdodD0iNzAwIiBmaWxsPSIjMzMzIj42IFN1cGVyIFNwaW5lczwvdGV4dD4KICA8dGV4dCB4PSI1MzAiIHk9Ijk3IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LXNpemU9IjEyIiBmaWxsPSIjMzMzIj5zcy1mYWJyaWMtYS0xIOKApiBzcy1mYWJyaWMtYS02PC90ZXh0PgoKICA8IS0tIEZ1bGwgbWVzaCBsaW5lcyAtLT4KICA8bGluZSB4MT0iNDMwIiB5MT0iMTEwIiB4Mj0iMjcwIiB5Mj0iMTc1IiBzdHJva2U9IiMzMzMiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtZGFzaGFycmF5PSI1LDMiLz4KICA8dGV4dCB4PSIzMzAiIHk9IjE0MiIgZm9udC1zaXplPSIxMSIgZmlsbD0iIzMzMyIgdHJhbnNmb3JtPSJyb3RhdGUoLTIwIDMzMCAxNDIpIj5mdWxsIG1lc2g8L3RleHQ+CiAgPGxpbmUgeDE9IjYzMCIgeTE9IjExMCIgeDI9Ijc5MCIgeTI9IjE3NSIgc3Ryb2tlPSIjMzMzIiBzdHJva2Utd2lkdGg9IjEuNSIgc3Ryb2tlLWRhc2hhcnJheT0iNSwzIi8+CiAgPHRleHQgeD0iNzE1IiB5PSIxNDIiIGZvbnQtc2l6ZT0iMTEiIGZpbGw9IiMzMzMiIHRyYW5zZm9ybT0icm90YXRlKDIwIDcxNSAxNDIpIj5mdWxsIG1lc2g8L3RleHQ+CgogIDwhLS0gUG9kLUEyIGNvbnRhaW5lcjogeD0yMCwgd2lkdGg9NTA1IChoYWxmIG9mIDEwNDAtMjAtMTAgbWludXMgNXB4IGdhcCBlYWNoIHNpZGUpIC0tPgogIDxyZWN0IHg9IjIwIiB5PSIxNjUiIHdpZHRoPSI1MDUiIGhlaWdodD0iMjMwIiByeD0iMTAiIGZpbGw9IiNmZmYiIHN0cm9rZT0iI2RkZCIgc3Ryb2tlLXdpZHRoPSIxLjUiLz4KICA8dGV4dCB4PSI0MCIgeT0iMTkwIiBmb250LXNpemU9IjE0IiBmb250LXdlaWdodD0iNzAwIiBmaWxsPSIjMzMzIj5Qb2QtQTI8L3RleHQ+CiAgPHRleHQgeD0iOTgiIHk9IjE5MCIgZm9udC1zaXplPSIxMyIgZmlsbD0iIzY2NiI+wrcgcm9sZTogY3B1PC90ZXh0PgoKICA8IS0tIFBvZC1BMiBTcGluZXMgLSBjZW50ZXJlZCBhdCB4PTI3MiAtLT4KICA8cmVjdCB4PSIxNDIiIHk9IjIwNSIgd2lkdGg9IjI2MCIgaGVpZ2h0PSI1MCIgcng9IjgiIGZpbGw9IiNmNWE2MjMiIGZpbHRlcj0idXJsKCNzaGFkb3cpIi8+CiAgPHRleHQgeD0iMjcyIiB5PSIyMjYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZvbnQtc2l6ZT0iMTQiIGZvbnQtd2VpZ2h0PSI3MDAiIGZpbGw9IiMzMzMiPjQgU3BpbmVzPC90ZXh0PgogIDx0ZXh0IHg9IjI3MiIgeT0iMjQ0IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LXNpemU9IjExIiBmaWxsPSIjMzMzIj5zcGluZS1wb2QtYTItMSDigKYgNDwvdGV4dD4KCiAgPCEtLSBQb2QtQTIgUmFja3MgLSA0IGluIGEgcm93LCAxMTBweCBlYWNoLCAxMHB4IGdhcHMsIGNlbnRlcmVkIGluIDUwNXB4IHBvZAogICAgICAgdG90YWwgcmFjayB3aWR0aCA9IDQqMTEwICsgMyoxMCA9IDQ3MCwgbGVmdCBtYXJnaW4gPSAoNTA1LTQ3MCkvMiA9IDE3LjUsIHN0YXJ0IGF0IDIwKzE3LjU9MzcgLS0+CiAgPHJlY3QgeD0iMzciIHk9IjI5MCIgd2lkdGg9IjExMCIgaGVpZ2h0PSI1MCIgcng9IjYiIGZpbGw9IiM3ZWQzMjEiIGZpbHRlcj0idXJsKCNzaGFkb3cpIi8+CiAgPHRleHQgeD0iOTIiIHk9IjMxMCIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC1zaXplPSIxMiIgZm9udC13ZWlnaHQ9IjYwMCIgZmlsbD0iIzMzMyI+UmFjay1BMi0xPC90ZXh0PgogIDx0ZXh0IHg9IjkyIiB5PSIzMjYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZvbnQtc2l6ZT0iMTAiIGZpbGw9IiMzMzMiPmNvbXB1dGUgwrcgMiBsZWFmczwvdGV4dD4KCiAgPHJlY3QgeD0iMTU3IiB5PSIyOTAiIHdpZHRoPSIxMTAiIGhlaWdodD0iNTAiIHJ4PSI2IiBmaWxsPSIjN2VkMzIxIiBmaWx0ZXI9InVybCgjc2hhZG93KSIvPgogIDx0ZXh0IHg9IjIxMiIgeT0iMzEwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LXNpemU9IjEyIiBmb250LXdlaWdodD0iNjAwIiBmaWxsPSIjMzMzIj5SYWNrLUEyLTI8L3RleHQ+CiAgPHRleHQgeD0iMjEyIiB5PSIzMjYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZvbnQtc2l6ZT0iMTAiIGZpbGw9IiMzMzMiPmNvbXB1dGUgwrcgMSBsZWFmPC90ZXh0PgoKICA8cmVjdCB4PSIyNzciIHk9IjI5MCIgd2lkdGg9IjExMCIgaGVpZ2h0PSI1MCIgcng9IjYiIGZpbGw9IiM3ZWQzMjEiIGZpbHRlcj0idXJsKCNzaGFkb3cpIi8+CiAgPHRleHQgeD0iMzMyIiB5PSIzMTAiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZvbnQtc2l6ZT0iMTIiIGZvbnQtd2VpZ2h0PSI2MDAiIGZpbGw9IiMzMzMiPlJhY2stQTItMzwvdGV4dD4KICA8dGV4dCB4PSIzMzIiIHk9IjMyNiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC1zaXplPSIxMCIgZmlsbD0iIzMzMyI+Y29tcHV0ZSDCtyAyIGxlYWZzPC90ZXh0PgoKICA8cmVjdCB4PSIzOTciIHk9IjI5MCIgd2lkdGg9IjExMCIgaGVpZ2h0PSI1MCIgcng9IjYiIGZpbGw9IiM3ZWQzMjEiIGZpbHRlcj0idXJsKCNzaGFkb3cpIi8+CiAgPHRleHQgeD0iNDUyIiB5PSIzMTAiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZvbnQtc2l6ZT0iMTIiIGZvbnQtd2VpZ2h0PSI2MDAiIGZpbGw9IiMzMzMiPlJhY2stQTItNDwvdGV4dD4KICA8dGV4dCB4PSI0NTIiIHk9IjMyNiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC1zaXplPSIxMCIgZmlsbD0iIzMzMyI+c3RvcmFnZSDCtyAxIGxlYWY8L3RleHQ+CgogIDwhLS0gUG9kLUEyIHNwaW5lLXRvLXJhY2sgbGluZXMgLS0+CiAgPGxpbmUgeDE9IjIwMiIgeTE9IjI1NSIgeDI9IjkyIiB5Mj0iMjkwIiBzdHJva2U9IiMzMzMiIHN0cm9rZS13aWR0aD0iMS4yIi8+CiAgPGxpbmUgeDE9IjI0MiIgeTE9IjI1NSIgeDI9IjIxMiIgeTI9IjI5MCIgc3Ryb2tlPSIjMzMzIiBzdHJva2Utd2lkdGg9IjEuMiIvPgogIDxsaW5lIHgxPSIzMDIiIHkxPSIyNTUiIHgyPSIzMzIiIHkyPSIyOTAiIHN0cm9rZT0iIzMzMyIgc3Ryb2tlLXdpZHRoPSIxLjIiLz4KICA8bGluZSB4MT0iMzQyIiB5MT0iMjU1IiB4Mj0iNDUyIiB5Mj0iMjkwIiBzdHJva2U9IiMzMzMiIHN0cm9rZS13aWR0aD0iMS4yIi8+CgogIDwhLS0gUG9kLUEzIGNvbnRhaW5lcjogeD01MzUsIHdpZHRoPTUwNSAtLT4KICA8cmVjdCB4PSI1MzUiIHk9IjE2NSIgd2lkdGg9IjUwNSIgaGVpZ2h0PSIyMzAiIHJ4PSIxMCIgZmlsbD0iI2ZmZiIgc3Ryb2tlPSIjZGRkIiBzdHJva2Utd2lkdGg9IjEuNSIvPgogIDx0ZXh0IHg9IjU1NSIgeT0iMTkwIiBmb250LXNpemU9IjE0IiBmb250LXdlaWdodD0iNzAwIiBmaWxsPSIjMzMzIj5Qb2QtQTM8L3RleHQ+CiAgPHRleHQgeD0iNjEzIiB5PSIxOTAiIGZvbnQtc2l6ZT0iMTMiIGZpbGw9IiM2NjYiPsK3IHJvbGU6IGNwdTwvdGV4dD4KCiAgPCEtLSBQb2QtQTMgU3BpbmVzIC0gY2VudGVyZWQgYXQgeD03ODcgLS0+CiAgPHJlY3QgeD0iNjU3IiB5PSIyMDUiIHdpZHRoPSIyNjAiIGhlaWdodD0iNTAiIHJ4PSI4IiBmaWxsPSIjZjVhNjIzIiBmaWx0ZXI9InVybCgjc2hhZG93KSIvPgogIDx0ZXh0IHg9Ijc4NyIgeT0iMjI2IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LXNpemU9IjE0IiBmb250LXdlaWdodD0iNzAwIiBmaWxsPSIjMzMzIj40IFNwaW5lczwvdGV4dD4KICA8dGV4dCB4PSI3ODciIHk9IjI0NCIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC1zaXplPSIxMSIgZmlsbD0iIzMzMyI+c3BpbmUtcG9kLWEzLTEg4oCmIDQ8L3RleHQ+CgogIDwhLS0gUG9kLUEzIFJhY2tzIC0gNCBpbiBhIHJvdywgY2VudGVyZWQgaW4gNTA1cHggcG9kCiAgICAgICBzdGFydCBhdCA1MzUrMTcuNT01NTIgLS0+CiAgPHJlY3QgeD0iNTUyIiB5PSIyOTAiIHdpZHRoPSIxMTAiIGhlaWdodD0iNTAiIHJ4PSI2IiBmaWxsPSIjN2VkMzIxIiBmaWx0ZXI9InVybCgjc2hhZG93KSIvPgogIDx0ZXh0IHg9IjYwNyIgeT0iMzEwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LXNpemU9IjEyIiBmb250LXdlaWdodD0iNjAwIiBmaWxsPSIjMzMzIj5SYWNrLUEzLTE8L3RleHQ+CiAgPHRleHQgeD0iNjA3IiB5PSIzMjYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZvbnQtc2l6ZT0iMTAiIGZpbGw9IiMzMzMiPmNvbXB1dGUgwrcgMiBsZWFmczwvdGV4dD4KCiAgPHJlY3QgeD0iNjcyIiB5PSIyOTAiIHdpZHRoPSIxMTAiIGhlaWdodD0iNTAiIHJ4PSI2IiBmaWxsPSIjN2VkMzIxIiBmaWx0ZXI9InVybCgjc2hhZG93KSIvPgogIDx0ZXh0IHg9IjcyNyIgeT0iMzEwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LXNpemU9IjEyIiBmb250LXdlaWdodD0iNjAwIiBmaWxsPSIjMzMzIj5SYWNrLUEzLTI8L3RleHQ+CiAgPHRleHQgeD0iNzI3IiB5PSIzMjYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZvbnQtc2l6ZT0iMTAiIGZpbGw9IiMzMzMiPmNvbXB1dGUgwrcgMSBsZWFmPC90ZXh0PgoKICA8cmVjdCB4PSI3OTIiIHk9IjI5MCIgd2lkdGg9IjExMCIgaGVpZ2h0PSI1MCIgcng9IjYiIGZpbGw9IiM3ZWQzMjEiIGZpbHRlcj0idXJsKCNzaGFkb3cpIi8+CiAgPHRleHQgeD0iODQ3IiB5PSIzMTAiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZvbnQtc2l6ZT0iMTIiIGZvbnQtd2VpZ2h0PSI2MDAiIGZpbGw9IiMzMzMiPlJhY2stQTMtMzwvdGV4dD4KICA8dGV4dCB4PSI4NDciIHk9IjMyNiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC1zaXplPSIxMCIgZmlsbD0iIzMzMyI+c3RvcmFnZSDCtyAxIGxlYWY8L3RleHQ+CgogIDxyZWN0IHg9IjkxMiIgeT0iMjkwIiB3aWR0aD0iMTEwIiBoZWlnaHQ9IjUwIiByeD0iNiIgZmlsbD0iIzdlZDMyMSIgZmlsdGVyPSJ1cmwoI3NoYWRvdykiLz4KICA8dGV4dCB4PSI5NjciIHk9IjMxMCIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC1zaXplPSIxMiIgZm9udC13ZWlnaHQ9IjYwMCIgZmlsbD0iIzMzMyI+UmFjay1BMy00PC90ZXh0PgogIDx0ZXh0IHg9Ijk2NyIgeT0iMzI2IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LXNpemU9IjEwIiBmaWxsPSIjMzMzIj5jb21wdXRlIMK3IDEgbGVhZjwvdGV4dD4KCiAgPCEtLSBQb2QtQTMgc3BpbmUtdG8tcmFjayBsaW5lcyAtLT4KICA8bGluZSB4MT0iNzE3IiB5MT0iMjU1IiB4Mj0iNjA3IiB5Mj0iMjkwIiBzdHJva2U9IiMzMzMiIHN0cm9rZS13aWR0aD0iMS4yIi8+CiAgPGxpbmUgeDE9Ijc1NyIgeTE9IjI1NSIgeDI9IjcyNyIgeTI9IjI5MCIgc3Ryb2tlPSIjMzMzIiBzdHJva2Utd2lkdGg9IjEuMiIvPgogIDxsaW5lIHgxPSI4MTciIHkxPSIyNTUiIHgyPSI4NDciIHkyPSIyOTAiIHN0cm9rZT0iIzMzMyIgc3Ryb2tlLXdpZHRoPSIxLjIiLz4KICA8bGluZSB4MT0iODU3IiB5MT0iMjU1IiB4Mj0iOTY3IiB5Mj0iMjkwIiBzdHJva2U9IiMzMzMiIHN0cm9rZS13aWR0aD0iMS4yIi8+CgogIDwhLS0gTGVnZW5kIC0tPgogIDxyZWN0IHg9IjUwIiB5PSI0MjAiIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgcng9IjMiIGZpbGw9IiM0YTkwZDkiLz4KICA8dGV4dCB4PSI3NCIgeT0iNDMzIiBmb250LXNpemU9IjEyIiBmaWxsPSIjMzMzIj5TdXBlciBTcGluZTwvdGV4dD4KICA8cmVjdCB4PSIxNzAiIHk9IjQyMCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiByeD0iMyIgZmlsbD0iI2Y1YTYyMyIvPgogIDx0ZXh0IHg9IjE5NCIgeT0iNDMzIiBmb250LXNpemU9IjEyIiBmaWxsPSIjMzMzIj5TcGluZTwvdGV4dD4KICA8cmVjdCB4PSIyNjAiIHk9IjQyMCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiByeD0iMyIgZmlsbD0iIzdlZDMyMSIvPgogIDx0ZXh0IHg9IjI4NCIgeT0iNDMzIiBmb250LXNpemU9IjEyIiBmaWxsPSIjMzMzIj5MZWFmIChwZXIgcmFjayk8L3RleHQ+CgogIDx0ZXh0IHg9IjUwIiB5PSI0NjAiIGZvbnQtc2l6ZT0iMTIiIGZpbGw9IiMzMzMiPkVhY2ggc3BpbmUgY29ubmVjdHMgdG8gYWxsIHN1cGVyIHNwaW5lcyDCtyBFYWNoIGxlYWYgY29ubmVjdHMgdG8gYWxsIHNwaW5lcyBpbiBpdHMgcG9kPC90ZXh0PgogIDx0ZXh0IHg9IjUwIiB5PSI0NzgiIGZvbnQtc2l6ZT0iMTIiIGZpbGw9IiMzMzMiPlRvdGFsOiA2IHN1cGVyIHNwaW5lcyArIDggc3BpbmVzICsgMTEgbGVhZnMgPSAyNSBkZXZpY2VzPC90ZXh0Pgo8L3N2Zz4K) ### Minimal inputs, complete infrastructure[​](#minimal-inputs-complete-infrastructure "Direct link to Minimal inputs, complete infrastructure") The contrast between what you define and what Generators produce illustrates the power of design-driven automation. **Design inputs** (loaded by `inv load`): * 3 single-vendor fabrics (Fabric-A Cisco, Fabric-B Arista, Fabric-C Dell), 9 pods, 24 racks * Per-vendor device templates (defining interface layouts and roles) * 1 IP supernet (`10.0.0.0/8`) with a `FabricSupernetPool`, plus overlay resource pools (ASN, VNI, VLAN) * A seed overlay tenant (`Blue` on Fabric-A) with a VRF and segments **Generated output** (for Fabric-A alone): * 25 devices: 6 super spines + 8 spines + 11 leafs * Hierarchical IP pools carved from the supernet through fabric, pod, and rack levels * Interface-level cabling with point-to-point /31 addressing between tiers * Computed interface descriptions on every connected interface ## Setting up the demo[​](#setting-up-the-demo "Direct link to Setting up the demo") ### Start the environment[​](#start-the-environment "Direct link to Start the environment") ``` uv sync --all-packages inv start ``` This installs Python dependencies and launches the Infrahub stack via Docker Compose — including the server, task workers, Neo4j, Redis, RabbitMQ, and PostgreSQL. ### Load the design data[​](#load-the-design-data "Direct link to Load the design data") ``` inv load ``` This runs four steps in sequence: loads schema definitions (`infrahubctl schema load`), loads UI menus (`infrahubctl menu load`), loads all design objects from `objects/`, and registers the Git repository from `repository.yml`. After this command completes, Infrahub contains the full design — fabrics, pods, racks, device templates, and IP pools — but no actual devices. What `inv load` does not create No devices exist yet. The fabrics, pods, and racks define design intent — they describe *what* the infrastructure should look like. Browse the NetworkDevice list in the Infrahub UI to confirm it is empty. ### Wait for repository sync[​](#wait-for-repository-sync "Direct link to Wait for repository sync") ``` infrahubctl repository list ``` Verify the repository status shows as synced. When Infrahub imports the repository, it reads `.infrahub.yml` to register Generator definitions, Transforms, queries, and artifact definitions. Generators cannot run until this import completes. ### Load trigger rules[​](#load-trigger-rules "Direct link to Load trigger rules") ``` infrahubctl object load triggers.yml ``` This creates `CoreGeneratorAction` and `CoreNodeTriggerRule` objects that enable automatic execution of the modular Generators — including the tenant/overlay trigger (`run-tenant-generator`). `triggers.yml` is a committed top-level file that is not part of `inv load`; it must be loaded after the repository has synced, because the rules reference Generator definitions that need to exist first. Branch scope Triggers fire only on non-main branches. All trigger rules are configured with `branch_scope: other_branches` by design. Running a Generator on `main` will not trigger downstream Generators. ## Running the Generators[​](#running-the-generators "Direct link to Running the Generators") With trigger rules loaded, the modular Generators run automatically. The FabricGenerator writes checksums to Pods — triggers fire the PodGenerator for each Pod — the PodGenerator writes checksums to Racks — triggers fire the RackGenerator for each Rack. One action at the fabric level builds the entire data center. ### How it works[​](#how-it-works "Direct link to How it works") 1. **FabricGenerator** completes and writes a checksum to each child Pod 2. The `CoreNodeTriggerRule` for `NetworkPod` detects the checksum update and fires the `run-pod-generator` action (`CoreGeneratorAction`) 3. **PodGenerator** runs for each Pod (skipping fabric-role pods), creates spines and links, then writes a checksum to each child Rack 4. The `CoreNodeTriggerRule` for `LocationRack` detects the checksum update and fires the `run-rack-generator` action 5. **RackGenerator** runs for each Rack, creating leafs and links The pattern is: **checksum write → trigger rule → Generator action → next tier**, repeated at each level. A fourth Generator — the **OverlayGenerator** (`generate-tenant`) — sits alongside this physical build. It is triggered by changes to a tenant's design (a `NetworkTenant` checksum update) rather than by the fabric cascade, and it layers EVPN/VXLAN services onto the leaf switches the fabric build produced. See [Overlay services (EVPN/VXLAN)](#overlay-services-evpnvxlan) below. ### Try it[​](#try-it "Direct link to Try it") Create a branch and run the FabricGenerator for Fabric-A: All Generator work happens on branches, not on `main`. This keeps the main branch clean and lets you review changes before merging. In the Infrahub UI, navigate to the Generator Definitions page (accessible from the Actions menu). Find `generate-fabric`, click **Run**, and select **Fabric-A** as the target. Watch the modular Generators in action: PodGenerators fire automatically for Pod-A2 and Pod-A3 (Pod-A1 is skipped — its `fabric` role marks it as the super spine tier), followed by RackGenerators for all 8 racks. After all Generators complete Verify in the UI: 25 devices in the NetworkDevice list (6 super spines, 8 spines, 11 leafs), hierarchical IP pools carved from the supernet, NetworkLink objects with point-to-point /31 addressing, and computed interface descriptions on every connected interface. Monitoring progress You can track progress through the Infrahub UI task execution list. Each Generator execution appears as a separate task, so you can follow the execution as it propagates through the tiers. ## Reviewing changes and generating artifacts[​](#reviewing-changes-and-generating-artifacts "Direct link to Reviewing changes and generating artifacts") With the Generators finished, create a proposed change to review what was built and trigger artifact generation. 1. In the Infrahub menu, navigate to **Proposed Change** and click **New proposed change**. 2. Select the branch that we previously created, give it a title (for example, *Build Fabric-A*) and click *Open*. The proposed change shows a diff of every object the Generators created — devices, IP allocations, links, and pools. This is the same review workflow used for any change in Infrahub. Creating the proposed change also triggers Infrahub's CI pipeline, which runs the Transforms and artifact definitions registered from the repository. Once the pipeline completes, the following artifacts are available: * **Startup configuration** — a text artifact per device, rendered from that device's vendor template. Fabric-A is Cisco, so its devices render from the Cisco template; Fabric-B (Arista) and Fabric-C (Dell) render from theirs. See [Multivendor configuration](/infrahub-solution-ai-dc/solution-ai-dc/multivendor-config.md). * **Cabling plan** — a CSV artifact per fabric, produced by a Python Transformation Browse the generated artifacts from the proposed change view or from individual device and fabric objects. Computed interface descriptions Computed interface descriptions are not generated by CI — they are computed attributes that update automatically whenever an interface's relationships change. They are already visible on interfaces after the Generators run. On the proposed change overview tab, click the **Merge** button to merge the changes into the main branch. We have now generated a full fabric from our design intend, and the startup configuration artifacts for each device can now be deployed, using a configuration deployment tool such as Ansible or Nornir. ## Day-two operations: adding a rack[​](#day-two-operations-adding-a-rack "Direct link to Day-two operations: adding a rack") Design-driven automation extends beyond the initial build. Adding a rack demonstrates how Generators handle incremental changes without rebuilding existing infrastructure. 1. Create a new branch 2. In the new branch, create a new **LocationRack** in the Infrahub UI: * **Name**: `Rack-A2-5` * **Index**: `5` * **Rack type**: `compute` * **Pod**: `Pod-A2` * **Parent**: `Hall-A1` * **Amount of leafs**: `2` * **Leaf switch template**: `Object template Device` >> `leaf-switch-compute` * **Member Of Groups**: `racks` 3. Run `generate-rack` manually for **Rack-A2-5** 4. Observe the results: 2 new leaf devices (`leaf-pod-a2-5-1` and `leaf-pod-a2-5-2`), new NetworkLink objects connecting them to the existing spines — and all existing devices, links, and IP addresses in other racks remain unchanged. Surgical updates The RackGenerator creates objects only for its target rack. Existing devices, links, and IPs in other racks are not modified. Triggers fire only on updates to existing objects, not on new object creation — which is why the RackGenerator is run manually here. 4. Open a new proposed change for the newly created branch The proposed change shows a diff of every object the Generator created — devices, IP allocations, links. Creating the proposed change also triggers Infrahub's CI pipeline, which runs the Transforms and artifact definitions registered from the repository. Once the pipeline completes, the following artifacts are available: * **Startup configuration** — new startup configurations for the 2 rack leaf switches, updated startup configurations for the pod spine switches * **Cabling plan** — an updated cabling plan for the whole fabric Browse the generated artifacts from the proposed change view or from individual device and fabric objects. On the proposed change overview tab, click the **Merge** button to merge the changes into the main branch. ## Overlay services (EVPN/VXLAN)[​](#overlay-services-evpnvxlan "Direct link to Overlay services (EVPN/VXLAN)") On top of the physical fabric, the solution layers multi-tenant EVPN/VXLAN overlay services. `inv load` already seeded a tenant — **Blue** on Fabric-A — with a VRF (`blue-prod`) and several segments (routed, rack-scoped, and L2-only examples). Nothing about the underlay or the fabric build changes; the overlay rides on top of it. 1. Create a new branch. 2. Run `generate-tenant` manually for the **Blue** tenant (Generator Definitions → `generate-tenant` → **Run** → target **Blue**). The OverlayGenerator allocates every overlay identifier (an L2VNI and VLAN per segment, an L3VNI and transit VLAN per VRF, route targets derived from the fabric ASN), allocates a subnet and `.1` anycast gateway for each routed segment, and materializes which leaf switches carry which segments. 3. Open a proposed change for the branch, let CI render the artifacts, then inspect a **leaf** device's startup configuration. Confirm it now contains the iBGP L2VPN-EVPN neighbors, the `nve1` VXLAN interface, per-segment VLAN/VNI mappings, the VRF context, and an anycast-gateway SVI for each routed segment. 4. Inspect a **spine** or **super-spine** artifact and confirm it carries the EVPN control plane and route-reflector role but **no** NVE, VLAN, VRF, or SVI state — only leaves are VTEPs. Scoped, queryable overlay Every allocated identifier and every iBGP session is stored as queryable data in Infrahub (browse **Network → Overlay** for Tenants, VRFs, Segments, and BGP Sessions). Adding, changing, or removing a tenant/VRF/segment regenerates configuration for only the affected leaves. To see a scoped day-two multi-tenant change, load the second tenant and run its Generator: ``` # data/ is not part of `inv load` — load the Red tenant manually infrahubctl object load data/tenant-red.yml ``` Then run `generate-tenant` for **Red**: only the leaves carrying Red's segments change; Blue's configuration and every unrelated device are untouched. For the full model, allocation rules, and rendered-configuration contract, see [EVPN/VXLAN overlay](/infrahub-solution-ai-dc/solution-ai-dc/evpn-vxlan-overlay.md). ## What to take away[​](#what-to-take-away "Direct link to What to take away") ### Key concepts demonstrated[​](#key-concepts-demonstrated "Direct link to Key concepts demonstrated") * **Design-driven automation** — you defined topology, device counts, and IP ranges; Generators produced 25 devices with full connectivity and addressing * **Modular Generators** — a single fabric-level action built the entire data center through checksum-triggered execution across layers * **Hierarchical IP delegation** — the supernet was carved automatically through fabric, pod, and rack levels via [Resource Manager](https://docs.infrahub.app/topics/resource-manager) * **Proposed change review** — a diff of every generated object, with CI-triggered artifact generation (startup configs, cabling plans) * **Day-two change support** — adding a rack produced only new objects with no rebuild of existing infrastructure * **Multi-tenant overlay** — declaring a tenant's intent produced a full EVPN/VXLAN control and data plane, with all identifiers auto-allocated and leaf-only state * **Multivendor rendering** — the same design produced Cisco, Arista, and Dell configuration, each device routed to its vendor's template * **Branch-based workflow** — all changes happened on a branch, reviewable and mergeable through Infrahub's standard workflow ### Next steps[​](#next-steps "Direct link to Next steps") * [EVPN/VXLAN overlay](/infrahub-solution-ai-dc/solution-ai-dc/evpn-vxlan-overlay.md) — the multi-tenant overlay model, allocation, and rendered configuration * [Multivendor configuration](/infrahub-solution-ai-dc/solution-ai-dc/multivendor-config.md) — how per-vendor configuration is resolved and rendered * [Design-driven automation](/infrahub-solution-ai-dc/solution-ai-dc/design-driven-automation.md) — deeper explanation of the architecture and concepts * [Modular Generator architecture](/infrahub-solution-ai-dc/solution-ai-dc/modular-generator-architecture.md) — how the modular Generators work internally * [Generator patterns](/infrahub-solution-ai-dc/solution-ai-dc/generator-patterns.md) — implementation patterns for building your own Generators * [Infrahub documentation: Generators](https://docs.infrahub.app/topics/generator) * [Infrahub documentation: Resource Manager](https://docs.infrahub.app/topics/resource-manager) Beyond AI data centers The modular Generator pattern demonstrated here — hierarchical Generators connected by checksum triggers — applies to any standardized, layered infrastructure: edge deployments, service provider networks, enterprise campuses. The Fabric → Pod → Rack hierarchy is illustrative, not prescriptive. --- # Design-driven automation Operators describe what infrastructure should look like; Generators produce it. This page explains how that separation works in the AI/DC solution, what the design objects look like, and why it matters for ongoing operations. See the [Demo Guide](/infrahub-solution-ai-dc/solution-ai-dc/demo-guide.md) for hands-on execution and [Modular Generator architecture](/infrahub-solution-ai-dc/solution-ai-dc/modular-generator-architecture.md) for how the modular Generators connect. ## What design-driven automation means[​](#what-design-driven-automation-means "Direct link to What design-driven automation means") ### Intent over procedure[​](#intent-over-procedure "Direct link to Intent over procedure") Traditional automation encodes *how*: create device X, assign IP Y, cable port Z. The design exists only inside the script — it is consumed during execution and discarded. To understand what was intended, you read code. Design-driven automation encodes *what*: "this fabric has 6 super spines." The *how* is the Generator's job. The design persists as structured data in Infrahub — queryable, versionable, and auditable — independent of whether Generators have run. An operator can inspect the intended state of a rack at any time without reading Generator code. ### Schema as design language[​](#schema-as-design-language "Direct link to Schema as design language") The solution's schema makes design intent machine-readable. Each schema node type represents a level in the data center hierarchy: * `NetworkFabric` — the top level, defining super spine counts and templates * `NetworkPod` — the middle level, defining spine counts, roles, and templates * `LocationRack` — the bottom level, defining rack type, leaf counts, and templates Relationships encode topology: a Fabric contains Pods, Pods reference Racks. Device templates (`CoreObjectTemplate`) capture interface layouts per device role — Generators use these to stamp out devices with the correct ports and interface profiles. Overlay services add a parallel intent hierarchy on top of the physical one — `NetworkTenant` → `NetworkVrf` → `NetworkSegment` — where operators declare *what* tenant services should exist and the OverlayGenerator produces the implementation. See [EVPN/VXLAN overlay](/infrahub-solution-ai-dc/solution-ai-dc/evpn-vxlan-overlay.md). Schema reference The physical design hierarchy is defined in `schemas/logical_design.yml` (Fabric and Pod) and `schemas/physical_location.yml` (Rack); the overlay intent model is in `schemas/overlay.yml`. See [Installation & Setup: Schemas](/infrahub-solution-ai-dc/solution-ai-dc/installation-setup.md#schemas) for the full schema file list. ### Generators read design, produce infrastructure[​](#generators-read-design-produce-infrastructure "Direct link to Generators read design, produce infrastructure") A Generator reads design objects via a GraphQL query and creates implementation objects — devices, IP allocations, links. Each Generator owns one hierarchy layer: * **FabricGenerator** reads `NetworkFabric` → produces super spine devices, fabric IP pools * **PodGenerator** reads `NetworkPod` → produces spine devices, spine-to-super-spine links * **RackGenerator** reads `LocationRack` → produces leaf devices, leaf-to-spine links * **OverlayGenerator** reads `NetworkTenant` → allocates overlay identifiers and places tenant segments on leaf switches The same principle extends beyond the physical fabric: the overlay describes tenant *intent* (a tenant, its VRFs, its segments) and the OverlayGenerator produces the EVPN/VXLAN implementation. See [EVPN/VXLAN overlay](/infrahub-solution-ai-dc/solution-ai-dc/evpn-vxlan-overlay.md). Generators are idempotent: running one again produces the same result, creating only what is missing. If the design changes, a Generator rebuilds only its scope — other layers remain untouched. Generators also connect through triggers: the output of one tier triggers the next, building a full data center from a single action. See [Modular Generator architecture](/infrahub-solution-ai-dc/solution-ai-dc/modular-generator-architecture.md) for how the modular Generators work and [Generator patterns](/infrahub-solution-ai-dc/solution-ai-dc/generator-patterns.md) for implementation details. ## The design in this solution[​](#the-design-in-this-solution "Direct link to The design in this solution") ### The Fabric-Pod-Rack hierarchy[​](#the-fabric-pod-rack-hierarchy "Direct link to The Fabric-Pod-Rack hierarchy") The AI/DC solution organizes design intent into three levels, each owning a layer of the data center topology: * **Fabric** — super spine count, super spine switch template * **Pod** — spine count, role, spine switch template * **Rack** — rack type, leaf count, leaf switch template Each level carries enough information for its Generator to produce a complete set of devices, IP allocations, and links — without knowledge of the layers above or below. ### What operators define[​](#what-operators-define "Direct link to What operators define") **Design hierarchy objects:** | Design object | Key attributes | Example from demo data | | --------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------- | | `NetworkFabric` | `amount_of_super_spines`, `super_spine_switch_template` | Fabric-A: 6 super spines, `cisco-9364d-gx2-super-spine-switch` template | | `NetworkPod` | `amount_of_spines`, `role`, `spine_switch_template` | Pod-A2: `cisco-9364d-gx2-spine-switch` template | | `LocationRack` | `rack_type`, `amount_of_leafs`, `leaf_switch_template` | Rack-A2-1: type `compute`, 2 leafs, `cisco-93400ld-h1-leaf-switch-compute` template | **Supporting design objects:** | Design object | Purpose | Example | | ---------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------ | | Device templates | Interface layouts and port-role assignments per device role | `spine-switch`: Loopback0 + Ethernet ranges with leaf and super-spine profiles | | IP supernet | Top-level address space for all allocations | `10.0.0.0/8` with role `supernet` | | Prefix pool | Allocates fabric subnets from the supernet | `FabricSupernetPool` → one /16 per fabric | Data files The design objects above are loaded from the `objects/` directory. Device templates are defined in `objects/06_device_template.yml`, IP resources in `objects/04_ipam.yml`, overlay resource pools in `objects/07_pools.yml`, the physical hierarchy in `objects/10_fabric.yml` and `objects/11_rack.yml`, and the seed overlay tenant in `objects/12_overlay.yml`. See [Installation & Setup: Demo data](/infrahub-solution-ai-dc/solution-ai-dc/installation-setup.md#demo-data-object-files) for the full file list. ### What Generators produce[​](#what-generators-produce "Direct link to What Generators produce") | Generator | Reads | Produces | | ---------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | FabricGenerator | `NetworkFabric` | Fabric supernet prefix, super spine loopback pool, super spine devices with loopback IPs, fabric overlay ASN | | PodGenerator | `NetworkPod` | Pod supernet prefix, pod loopback pool, spine devices with loopback IPs, spine-to-super-spine links with /31 IPs, iBGP EVPN sessions | | RackGenerator | `LocationRack` | Leaf devices with loopback IPs, leaf-to-spine links with /31 IPs, VTEP loopbacks | | OverlayGenerator | `NetworkTenant` | Allocated overlay identifiers (VNI/VLAN/ASN/route target), tenant subnets and anycast gateways, segment-to-leaf placement | The demo defines 36 hierarchy objects (3 fabrics, 9 pods, 24 racks), per-vendor device templates, 1 IP supernet with a prefix pool, and overlay resource pools. From these inputs, the Generators produce devices across all three fabrics, hierarchical IP pool trees, hundreds of links with point-to-point addressing, per-vendor configuration, and computed attributes on every interface. Hands-on walkthrough The [Demo Guide](/infrahub-solution-ai-dc/solution-ai-dc/demo-guide.md) walks through each Generator's output step by step. This section focuses on the mapping between design and implementation. ## Why this matters for day-two operations[​](#why-this-matters-for-day-two-operations "Direct link to Why this matters for day-two operations") Design-driven automation is not only about the initial build — the stronger differentiator is what happens afterward. 1. **Design persists after Generators run.** Fabric, Pod, and Rack objects remain in Infrahub as queryable data. An operator can inspect "what was this rack designed to look like" at any time, independent of the devices that were generated from it. 2. **Changes are scoped.** Adding a rack means creating one `LocationRack` object and running the RackGenerator for that rack. Existing racks, devices, links, and IP allocations remain untouched. 3. **Design is versionable.** Changes happen on branches with diffs, reviews, and merges — the same workflow used for configuration changes. There is a full audit trail from design intent through to generated infrastructure. Try it The [Demo Guide: Day-two operations](/infrahub-solution-ai-dc/solution-ai-dc/demo-guide.md#day-two-operations-adding-a-rack) walks through adding a rack and running the RackGenerator to see scoped generation in action. ## Learn more[​](#learn-more "Direct link to Learn more") For deeper coverage of the concepts and patterns introduced on this page: * [Infrahub documentation: Generators](https://docs.infrahub.app/topics/generator) * [Infrahub documentation: Resource Manager](https://docs.infrahub.app/topics/resource-manager) * [Modular Generator architecture](/infrahub-solution-ai-dc/solution-ai-dc/modular-generator-architecture.md) * [Generator patterns](/infrahub-solution-ai-dc/solution-ai-dc/generator-patterns.md) * [EVPN/VXLAN overlay](/infrahub-solution-ai-dc/solution-ai-dc/evpn-vxlan-overlay.md) * [Multivendor configuration](/infrahub-solution-ai-dc/solution-ai-dc/multivendor-config.md) --- # EVPN/VXLAN overlay The overlay adds **multi-tenant Layer-2 and Layer-3 network services** on top of the fabric the AI/DC solution already builds. Operators declare tenant intent — a tenant, its routing instances, and its segments — and the solution allocates every overlay identifier, decides which leaf switches carry which segments, and renders the complete per-device EVPN/VXLAN configuration. The existing OSPF underlay and the Fabric → Pod → Rack build are untouched: the overlay rides on top of them with zero disruption. This page covers the operator-facing model, how generation works, and the key behaviors. For the implementation patterns behind the Generators, see [Generator patterns](/infrahub-solution-ai-dc/solution-ai-dc/generator-patterns.md); for how Generators chain together, see [Modular Generator architecture](/infrahub-solution-ai-dc/solution-ai-dc/modular-generator-architecture.md). ## What it does[​](#what-it-does "Direct link to What it does") The fabric provides loopback-to-loopback reachability between switches over an OSPF-routed IP **underlay**. The overlay is the iBGP **L2VPN-EVPN** control plane and **VXLAN** data plane carried on top of that underlay. It lets multiple tenants share one physical fabric while staying isolated from one another: * **Bridged Layer-2 services** stretched across the fabric (a VLAN extended fabric-wide over VXLAN). * **Routed Layer-3 services** with a **distributed anycast gateway** — the same default gateway present on every leaf that carries the segment, so a host's gateway is always local. * **Inter-subnet routing within a tenant**, while keeping different tenants' routes fully separated. Each fabric is an **independent overlay domain** with its own routing number (ASN) and identifier space. Cross-fabric interconnect (DCI) is out of scope for this release. ## The operator-facing model[​](#the-operator-facing-model "Direct link to The operator-facing model") Operators work entirely in intent. The model is a three-level hierarchy, all scoped to a single fabric: | Object | What it represents | | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Tenant** | An owner of overlay services (a customer or workload owner), scoped to exactly one fabric. Owns one or more VRFs. | | **VRF** | A tenant's isolated Layer-3 routing instance (IP-VRF). Owns one or more segments and defines the boundary for inter-subnet routing and tenant isolation. | | **Segment** | A tenant Layer-2 service — one VLAN bridged fabric-wide. Belongs to one VRF. Optionally carries a subnet and an anycast gateway. | Two operator choices shape how a segment behaves: * **Routed (IRB) vs L2-only.** A segment **with** a subnet and gateway is routed: it gets a distributed anycast gateway and participates in inter-subnet routing within its tenant (symmetric IRB). A segment **without** a gateway is **L2-only** — bridged across the fabric with no gateway and no inter-subnet routing, for cases where routing happens outside the fabric. * **Rack placement (optional).** By default a segment is available on **every leaf in the fabric**. An operator may restrict it to **specific racks**, in which case only those racks' leaf switches receive it. Operators never assign VNIs, VLANs, route targets, or the fabric ASN by hand — those are allocated automatically (see below). The tenant/VRF/segment model is the stable contract: a future eBGP control-plane mode can be introduced without changing what operators declare. Domain language The terms used here — tenant, VRF, segment, anycast gateway, VTEP, leaf/spine/super-spine, route reflector — carry precise meanings in this solution. See [`CONTEXT.md`](https://github.com/opsmill/infrahub-solution-ai-dc/blob/main/CONTEXT.md) for the shared language. ## How it works[​](#how-it-works "Direct link to How it works") A new **OverlayGenerator** turns tenant intent into implementation objects, and the per-device configuration transform renders those objects into device configuration. Generator naming The overlay Generator is registered as `generate-tenant` in `.infrahub.yml` (targeting the `tenants` group) — that is the name you select in the Infrahub UI. Its Python class is `OverlayGenerator`, the name used throughout this page. ### Allocation and materialization (OverlayGenerator)[​](#allocation-and-materialization-overlaygenerator "Direct link to Allocation and materialization (OverlayGenerator)") Triggered by changes to a tenant's design, the OverlayGenerator: 1. **Allocates overlay identifiers** from Infrahub Resource Manager pools — an **L2VNI** and **VLAN** per segment, an **L3VNI** and transit VLAN per VRF, and the per-fabric **ASN** — with no collisions inside a fabric. **Route targets** are derived from the fabric ASN and the VNI. 2. **Allocates the addressing** for routed segments: a tenant subnet from the overlay address space, and the segment's **anycast gateway** as the first usable host in that subnet. 3. **Materializes placement** — it writes the relationship that records *which leaf switches carry which segments*. Empty placement means every leaf in the tenant's fabric; otherwise only the leaves in the listed racks. All of these allocations are retained as **queryable data** in Infrahub — the system of record — inspectable independently of the generated configuration. The supporting fabric/pod/rack Generators are extended to allocate the per-fabric ASN, stamp each device's ASN and route-reflector role, give every leaf a dedicated **VTEP loopback** address, and materialize the iBGP **BGP sessions** along the cabling — so the whole control plane is queryable data, not just rendered text. ### Rendering the configuration (per-device transform)[​](#rendering-the-configuration-per-device-transform "Direct link to Rendering the configuration (per-device transform)") The per-device `startup_configuration` transform is expanded to render the overlay. The control plane is **iBGP L2VPN-EVPN** with **hierarchical route reflection**: leaf switches are clients of their spines, spines reflect to leaves and are themselves clients of the super-spines, which reflect to the spines. **VTEPs are leaf-only** — only leaves encapsulate VXLAN, sourced from their VTEP loopback. Routed segments use **symmetric IRB** (L2VNI bridging plus L3VNI routing with the distributed anycast gateway). The sections rendered depend on the device's role: | Section | Leaf | Spine | Super-spine | | ------------------------------------------------------ | --------------------- | ------------ | ------------- | | OSPF underlay (now also advertising the VTEP loopback) | yes | yes | yes | | `router bgp ` + iBGP EVPN neighbors | yes | yes | yes | | `route-reflector-client` toward lower-tier neighbors | — | yes (→ leaf) | yes (→ spine) | | `interface nve1` (VXLAN tunnel endpoint) | yes | — | — | | L2VNI bridging (`vlan` / `vn-segment`, `member vni`) | yes | — | — | | L3VNI routing (`vrf context`, transit SVI) | yes | — | — | | Anycast gateway SVI | yes (routed segments) | — | — | Configuration is rendered **per vendor**: each device is routed to its vendor's template (Cisco NX-OS, Arista EOS, or Dell OS10), so the same overlay model produces the correct syntax for whichever fabric a device belongs to. In the demo data each fabric is single-vendor — Fabric-A Cisco, Fabric-B Arista, Fabric-C Dell. See [Multivendor configuration](/infrahub-solution-ai-dc/solution-ai-dc/multivendor-config.md) for how vendor is resolved and rendered. The leaf overlay block below is the **Cisco NX-OS** example (the Arista and Dell templates render the equivalent constructs in their own syntax). The shape (see the [configuration artifact contract](https://github.com/opsmill/infrahub-solution-ai-dc/blob/main/specs/001-evpn-overlay/contracts/config-artifact.md) for the full definition): ``` router bgp router-id neighbor remote-as ! one per cabled spine update-source loopback0 address-family l2vpn evpn send-community extended interface nve1 source-interface loopback1 ! the leaf VTEP loopback host-reachability protocol bgp member vni ! per carried segment member vni associate-vrf ! per carried VRF vrf context ! per carried VRF vni address-family ipv4 unicast route-target both evpn interface Vlan ! only if the segment has a gateway vrf member ip address fabric forwarding mode anycast-gateway ``` A spine or super-spine renders the EVPN control plane and acts as a route reflector toward its lower tier, but renders **no** NVE, VLAN, VRF, or SVI. ## Key behaviors[​](#key-behaviors "Direct link to Key behaviors") * **Zero-disruption coexistence.** Introducing the overlay causes no change to the previously generated underlay configuration or to the existing fabric/pod/rack build. A fabric with no tenants still establishes the EVPN control-plane baseline and keeps operating its underlay. * **Scoped day-two changes.** Adding, modifying, or removing a tenant, VRF, or segment regenerates configuration for **only the affected leaves** — every unrelated device's configuration is byte-identical. Removing a segment releases its allocated identifiers. Day-two overlay changes follow the same scoped, branch-based, reviewable workflow as adding a rack. * **Leaves carry tenant state; spines do not.** Only leaves are VTEPs and only leaves render per-tenant segment, VRF, and gateway state. Spines and super-spines participate in the control plane purely as route reflectors and never carry tenant state. * **No collisions.** Every overlay identifier is allocated from a managed pool, so identifiers are unique within a fabric by construction. ## Try it[​](#try-it "Direct link to Try it") The [overlay quickstart](https://github.com/opsmill/infrahub-solution-ai-dc/blob/main/specs/001-evpn-overlay/quickstart.md) walks through end-to-end validation: lint and test, load the schema and overlay seed data, run the full build, then inspect a leaf's rendered configuration and confirm the spine/super-spine carry no tenant state. It also covers verifying scoped day-two changes and the rack-placement and L2-only variations. ## Learn more[​](#learn-more "Direct link to Learn more") * [Multivendor configuration](/infrahub-solution-ai-dc/solution-ai-dc/multivendor-config.md) — how the overlay (and underlay) configuration is rendered per vendor (Cisco/Arista/Dell). * [`CONTEXT.md`](https://github.com/opsmill/infrahub-solution-ai-dc/blob/main/CONTEXT.md) — the domain language for the overlay (tenant, VRF, segment, VTEP, anycast gateway, route reflector). * Architecture Decision Records [`dev/adr/0001`–`0005`](https://github.com/opsmill/infrahub-solution-ai-dc/tree/main/dev/adr) — the rationale behind the design split, the OverlayGenerator's ownership, hierarchical route reflection, and the stored BGP-session and route-reflector model. * The feature [specification](https://github.com/opsmill/infrahub-solution-ai-dc/blob/main/specs/001-evpn-overlay/spec.md) and [implementation plan](https://github.com/opsmill/infrahub-solution-ai-dc/blob/main/specs/001-evpn-overlay/plan.md) for the full requirements and design. --- # Generator patterns This page covers the implementation patterns used in the AI/DC solution's Generators. It is aimed at developers who want to understand, adapt, or extend the code. Each section focuses on a specific pattern with annotated code from the solution. See [Modular Generator architecture](/infrahub-solution-ai-dc/solution-ai-dc/modular-generator-architecture.md) for the architectural concepts (signaling mechanism, validation gates, interface allocation design) and [Design-driven automation](/infrahub-solution-ai-dc/solution-ai-dc/design-driven-automation.md) for what the Generators read and produce. ## Generator definition wiring[​](#generator-definition-wiring "Direct link to Generator definition wiring") Each Generator is registered in `.infrahub.yml` with a definition that connects a Python class to a GraphQL query and a target group: ``` generator_definitions: - name: generate-fabric file_path: "./generators/generate_fabric.py" query: generate_fabric targets: fabrics parameters: fabric_name: name__value class_name: FabricGenerator convert_query_response: false execute_in_proposed_change: false execute_after_merge: false ``` Key fields: | Field | Purpose | | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `query` | References a named GraphQL query defined in the `queries` section of `.infrahub.yml` | | `targets` | The group whose members are valid targets for this Generator (e.g., `fabrics` is a `CoreStandardGroup`) | | `parameters` | Maps GraphQL query variables to target object attributes. `fabric_name: name__value` passes the target's `name` as the `$fabric_name` query variable | | `class_name` | The Python class inside `file_path` that Infrahub instantiates | | `convert_query_response` | Set to `false` to receive the raw GraphQL response as a dict. The Generators parse it with Pydantic models for type safety | The overlay Generator follows the same wiring pattern, targeting the `tenants` group. Note the naming split: the definition, file, and query use `generate-tenant`/`generate_tenant`, while the Python class is `OverlayGenerator`. ``` generator_definitions: - name: generate-tenant file_path: "./generators/generate_tenant.py" query: generate_tenant targets: tenants parameters: name: name__value class_name: OverlayGenerator convert_query_response: false execute_in_proposed_change: false execute_after_merge: false ``` ## GraphQL queries[​](#graphql-queries "Direct link to GraphQL queries") Each Generator has a paired `.gql` file that fetches the design object and its context. The query receives parameters mapped from the target object via the definition in `.infrahub.yml`. ### Fetching design context in a single query[​](#fetching-design-context-in-a-single-query "Direct link to Fetching design context in a single query") The PodGenerator needs data from both the Pod itself and its parent Fabric (super spine count, sorting methods). The query uses an inline fragment (`... on NetworkFabric`) to fetch parent-specific fields in a single request: ``` query PodGeneratorQuery($pod_name: String!) { NetworkPod(name__value: $pod_name) { edges { node { id amount_of_spines { value } name { value } index { value } role { value } spine_switch_template { node { __typename, id } } parent { node { __typename id name { value } ... on NetworkFabric { amount_of_super_spines { value } fabric_interface_sorting_method { value } spine_interface_sorting_method { value } } } } } } } } ``` The RackGenerator follows the same pattern — it fetches the Rack's attributes and traverses the `pod` relationship to get the pod's IP pools, spine count, and sorting methods. ### Parsing query responses with Pydantic[​](#parsing-query-responses-with-pydantic "Direct link to Parsing query responses with Pydantic") Each query has a corresponding Pydantic model file (e.g., `fabric_generator_query.py`, `pod_generator_query.py`) that mirrors the GraphQL response structure. The Generator parses the raw dict into typed models as the first step of `generate()`: ``` from .pod_generator_query import PodGeneratorQuery async def generate(self, data: dict) -> None: data: PodGeneratorQuery = PodGeneratorQuery(**data) self.pod_id = data.network_pod.edges[0].node.id self.pod_index = data.network_pod.edges[0].node.index.value self.pod_name = data.network_pod.edges[0].node.name.value.lower() self.fabric_amount_of_super_spines = ( data.network_pod.edges[0].node.parent.node.amount_of_super_spines.value ) ``` This gives type-safe access to nested fields and catches schema mismatches at parse time rather than deep in the generation logic. ## Device creation[​](#device-creation "Direct link to Device creation") Devices are created from object templates using the Infrahub SDK. The pattern is the same across all three Generators — only the role, naming convention, and template differ. ### Creating a device from a template[​](#creating-a-device-from-a-template "Direct link to Creating a device from a template") ``` device = await self.client.create( NetworkDevice, hostname=f"spine-{self.pod_name}-{idx}", object_template={"id": self.pod_spine_switch_template}, pod={"id": self.pod_id}, loopback_ip=self.loopback_pool, role="spine", member_of_groups=["devices"], ) await device.save(allow_upsert=True) ``` Key aspects: * **`object_template`** — references a `CoreObjectTemplate` by ID. Infrahub stamps out the device with the template's predefined interfaces (Loopback0, Ethernet ranges with role profiles) * **`loopback_ip`** — passing a pool object triggers automatic IP allocation from that pool * **`allow_upsert=True`** — if a device with this hostname already exists, the save updates it rather than creating a duplicate. This is what makes re-runs safe * **`member_of_groups`** — adds the device to the `devices` group, making it a target for artifact definitions ### Assigning the loopback IP to an interface[​](#assigning-the-loopback-ip-to-an-interface "Direct link to Assigning the loopback IP to an interface") After device creation, the allocated loopback IP must be assigned to the device's Loopback0 interface: ``` device = await self.client.get( NetworkDevice, id=device.id, include=["ip_address"], exclude=["rack", "pod", "role", "hostname", "object_template", "member_of_groups"], ) loopback_interface = await self.client.get( NetworkInterface, device__ids=[device.id], role__value="loopback" ) loopback_interface.status.value = "active" loopback_interface.ip_address = device.loopback_ip.id await loopback_interface.save(allow_upsert=True) ``` The device is re-fetched with `include=["ip_address"]` to retrieve the pool-allocated IP address ID, which is then assigned to the loopback interface. ## IP pool allocation[​](#ip-pool-allocation "Direct link to IP pool allocation") The Generators build a hierarchical IP pool tree using Infrahub's [Resource Manager](https://docs.infrahub.app/topics/resource-manager). Each tier carves a subnet from the pool created by the tier above. ### Hierarchical pool delegation[​](#hierarchical-pool-delegation "Direct link to Hierarchical pool delegation") The FabricGenerator creates the top-level pools: ``` # Allocate a /16 from the global FabricSupernetPool fabric_supernet_pool = await self.client.get( kind=CoreIPPrefixPool, name__value="FabricSupernetPool" ) fabric_supernet = await self.client.allocate_next_ip_prefix( resource_pool=fabric_supernet_pool, identifier=self.fabric_id, data={"role": "fabric_supernet"}, ) # Create a fabric-scoped prefix pool backed by the allocated /16 fabric_prefix_pool = await self.client.create( kind=CoreIPPrefixPool, name=f"{self.fabric_name}-prefix-pool", default_prefix_type="IpamIPPrefix", default_prefix_length=24, ip_namespace={"hfid": ["default"]}, resources=[fabric_supernet], ) await fabric_prefix_pool.save(allow_upsert=True) ``` The PodGenerator then allocates from the fabric prefix pool: ``` # Allocate a /19 pod supernet from the fabric prefix pool pod_supernet = await self.client.allocate_next_ip_prefix( resource_pool=fabric_prefix_pool, identifier=self.pod_id, member_type="prefix", prefix_length=19, data={"role": "pod_supernet"}, ) ``` ### Deterministic allocation with identifiers[​](#deterministic-allocation-with-identifiers "Direct link to Deterministic allocation with identifiers") Every `allocate_next_ip_prefix` call uses the object's ID as the `identifier` parameter. This makes allocation idempotent — calling the method again with the same identifier returns the previously allocated prefix rather than allocating a new one. For point-to-point link addressing, the identifier is the concatenation of both interface IDs: ``` prefix = await client.allocate_next_ip_prefix( resource_pool=pool, identifier=src_interface.id + dst_interface.id, member_type="address", prefix_length=31, data={"role": prefix_role}, ) ``` The same interface pair always produces the same /31 prefix. ## Cabling[​](#cabling "Direct link to Cabling") Cabling connects devices across tiers — spines to super spines, leafs to spines. The pattern has three phases: sort interfaces, build a cabling plan, then create links and assign IPs. See [Modular Generator architecture: Deterministic interface allocation](/infrahub-solution-ai-dc/solution-ai-dc/modular-generator-architecture.md#deterministic-interface-allocation) for the design rationale. ### Interface sorting[​](#interface-sorting "Direct link to Interface sorting") Interfaces are grouped by device and sorted using `netutils.interface.sort_interface_list`. The sorting function is selected at runtime based on the schema's sorting method attribute: ``` from infrahub_solution_ai_dc import sorting # Resolve the sorting function from the schema dropdown value fabric_interface_sorting_method = ( data.network_pod.edges[0].node.parent.node.fabric_interface_sorting_method.value ) self.fabric_interface_sorting_function = getattr( sorting, fabric_interface_sorting_method ) ``` The sorting functions produce a `dict[NetworkDevice, list[NetworkInterface]]` — a stable, ordered mapping from device to interfaces that the cabling plan consumes. ### Building and executing a cabling plan[​](#building-and-executing-a-cabling-plan "Direct link to Building and executing a cabling plan") The PodGenerator's cabling sequence illustrates the full pattern: ``` async def connect_spine_to_super_spine(self) -> None: # 1. Fetch interfaces by role spine_interfaces = await self.client.filters( kind=NetworkInterface, device__ids=[spine.id for spine in self.spine_switches], role__value="super_spine", ) super_spine_interfaces = await self.client.filters( kind=NetworkInterface, device__ids=[ss.id for ss in self.super_spine_switches], role__value="spine", ) # 2. Sort interfaces per device spine_interface_map = self.spine_interface_sorting_function(spine_interfaces) super_spine_interface_map = self.fabric_interface_sorting_function( super_spine_interfaces ) # 3. Build the cabling plan using the pod index cabling_plan = build_pod_cabling_plan( pod_index=self.pod_index, src_interface_map=spine_interface_map, dst_interface_map=super_spine_interface_map, ) # 4. Create NetworkLink objects and mark interfaces active await connect_interface_maps( client=self.client, logger=self.logger, cabling_plan=cabling_plan ) # 5. Allocate /31 prefixes and assign IPs to both endpoints await assign_ip_addresses_to_p2p_connections( client=self.client, logger=self.logger, connections=cabling_plan, prefix_len=31, prefix_role="pod_super_spine_spine", pool=self.pod_prefix_pool, ) ``` The RackGenerator follows the same five-step pattern with `build_rack_cabling_plan` and the rack index. ### Link creation[​](#link-creation "Direct link to Link creation") Each interface pair in the cabling plan becomes a `NetworkLink` object: ``` network_link = await client.create( kind="NetworkLink", name=f"{src_interface.device.display_label}-{src_interface.name.value}" f"__{dst_interface.device.display_label}-{dst_interface.name.value}", medium="copper", endpoints=[src_interface, dst_interface], ) await network_link.save(allow_upsert=True) ``` Both interfaces are then marked as `active`. The `allow_upsert=True` on the link save ensures that re-running the Generator does not create duplicate links. ## Checksum propagation[​](#checksum-propagation "Direct link to Checksum propagation") After the FabricGenerator and PodGenerator complete their work, they write a checksum to each child object to trigger the next tier. The RackGenerator is the final tier of the physical cascade and does not propagate a checksum to a child. The OverlayGenerator uses the same checksum mechanism differently: it stamps a content checksum on the tenant it just materialized (guarded so it only changes when the tenant's segment set changes), which is what the tenant trigger re-fires on — without creating a self-retrigger loop. ### Calculating the checksum[​](#calculating-the-checksum "Direct link to Calculating the checksum") The checksum is a SHA-256 hash of all object IDs accessed during the Generator run: ``` class GeneratorMixin: def calculate_checksum(self) -> str: related_ids = ( self.client.group_context.related_group_ids + self.client.group_context.related_node_ids ) sorted_ids = sorted(related_ids) joined = ",".join(sorted_ids) return hashlib.sha256(joined.encode("utf-8")).hexdigest() ``` The IDs are sorted before hashing, so the order in which objects were accessed does not matter — only the set of objects determines the checksum. ### Writing the checksum to children[​](#writing-the-checksum-to-children "Direct link to Writing the checksum to children") The FabricGenerator writes the checksum to all child Pods; the PodGenerator writes it to all child Racks: ``` async def update_checksum(self) -> None: racks = await self.client.filters(kind=LocationRack, pod__ids=[self.pod_id]) checksum = self.calculate_checksum() for rack in racks: if rack.checksum.value != checksum: rack.checksum.value = checksum await rack.save(allow_upsert=True) ``` The `if rack.checksum.value != checksum` guard prevents unnecessary saves — if the checksum has not changed (because the Generator produced identical results), no trigger fires and downstream execution stops. ## Shared library[​](#shared-library "Direct link to Shared library") The Generators share common logic through the `infrahub_solution_ai_dc` package (in `src/infrahub_solution_ai_dc/`). This package is installed into the custom Infrahub Docker image so that task workers can import it at runtime. | Module | Purpose | | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `generator.py` | `GeneratorMixin` — checksum calculation | | `cabling.py` | `build_pod_cabling_plan`, `build_rack_cabling_plan`, `connect_interface_maps` — cabling plan algorithms and link creation | | `sorting.py` | `create_sorted_device_interface_map`, `create_reverse_sorted_device_interface_map` — interface sorting strategies | | `addressing.py` | `assign_ip_addresses_to_p2p_connections`, `assign_ip_address_to_interface` — IP allocation and assignment | | `overlay.py` | `route_target`, `rr_client`, `upsert_evpn_session`, `resolve_segment_devices` — overlay route-target/route-reflector helpers, iBGP session upserts, and segment-to-leaf placement | | `vendors.py` | `vendor_group_for_manufacturer`, `vendor_group_for_template` — resolves a device's vendor group (`cisco_devices` / `arista_devices` / `dell_devices`) for per-vendor artifact targeting | | `protocols.py` | Auto-generated SDK protocol classes (`infrahubctl protocols`) — typed interfaces for `NetworkDevice`, `NetworkInterface`, `LocationRack`, etc. | ## Learn more[​](#learn-more "Direct link to Learn more") * [Modular Generator architecture](/infrahub-solution-ai-dc/solution-ai-dc/modular-generator-architecture.md) — signaling mechanism, validation gates, interface allocation design * [Design-driven automation](/infrahub-solution-ai-dc/solution-ai-dc/design-driven-automation.md) — what the Generators read and produce * [EVPN/VXLAN overlay](/infrahub-solution-ai-dc/solution-ai-dc/evpn-vxlan-overlay.md) — the OverlayGenerator's allocation and placement patterns * [Multivendor configuration](/infrahub-solution-ai-dc/solution-ai-dc/multivendor-config.md) — per-vendor resolution and artifact targeting * [Installation & Setup](/infrahub-solution-ai-dc/solution-ai-dc/installation-setup.md) — repository structure and environment setup * [Infrahub documentation: Generators](https://docs.infrahub.app/topics/generator) * [Infrahub documentation: Resource Manager](https://docs.infrahub.app/topics/resource-manager) --- # Installation & Setup Everything needed to run the AI/DC solution locally is included in the repository. The environment runs in Docker; you interact with it through the `infrahubctl` CLI and the Infrahub web UI. ## Requirements[​](#requirements "Direct link to Requirements") * **Python 3.11+** (3.12 recommended) * **Docker** and **Docker Compose** (v2) * **[uv](https://docs.astral.sh/uv/)** — Python package manager * **Git** — for cloning the repository infrahubctl is included The `infrahubctl` CLI is installed automatically as part of the `infrahub-sdk` dependency. No separate installation is needed. ## Getting started[​](#getting-started "Direct link to Getting started") ### Cloning the repository[​](#cloning-the-repository "Direct link to Cloning the repository") First we have to clone the repository. ``` git clone https://github.com/opsmill/infrahub-solution-ai-dc.git ``` ### Install dependencies[​](#install-dependencies "Direct link to Install dependencies") ``` uv sync --all-packages ``` This installs all Python dependencies including `infrahub-sdk` (which provides `infrahubctl`), `invoke` (task runner), and the solution's own `infrahub-solution-ai-dc` package. ### Configure environment variables[​](#configure-environment-variables "Direct link to Configure environment variables") ``` export INFRAHUB_USERNAME="admin" export INFRAHUB_PASSWORD="infrahub" ``` `INFRAHUB_USERNAME` and `INFRAHUB_PASSWORD` are used by `infrahubctl` to authenticate with the Infrahub API. `VERSION` sets the Infrahub image tag. If not using direnv, source the file manually or export these variables: Alternatively, you can use [direnv](https://direnv.net) and define the environment variables in a `.envrc` file at the root of the project. ### Build container image[​](#build-container-image "Direct link to Build container image") ``` uv run inv build ``` This will build a custom Infrahub container image, this is needed because this solution is using a custom Python package `src/infrahub_solution_ai_dc` that we want to use in our Generators. This makes importing classes and functions from this package easier and more reliable. ### Start Infrahub[​](#start-infrahub "Direct link to Start Infrahub") ``` uv run inv start ``` This downloads the base `docker-compose.yml` from `https://infrahub.opsmill.io` (if not already present), builds a custom Docker image, then runs `docker compose up -d`. The custom image extends the standard Infrahub image with the `infrahub-solution-ai-dc` Python package — a shared library (in `src/infrahub_solution_ai_dc/`) that the Generators and Transforms use for common logic such as IP addressing, cabling, and interface sorting. Without it, Generators running inside the task workers would not have access to that shared code. The override file (`docker-compose.override.yml`) ensures the custom image is used in place of the standard one. **Services started:** | Service | Description | Port | | ----------------- | --------------------------------------------- | -------------- | | `infrahub-server` | Infrahub API and web UI | `8000` | | `task-manager` | Prefect API server for workflow orchestration | `4200` | | `task-worker` | Infrahub task workers (2 replicas) | — | | `database` | Neo4j graph database | `7474`, `7687` | | `cache` | Redis | — | | `message-queue` | RabbitMQ | — | | `task-manager-db` | PostgreSQL for Prefect | — | Verify the environment Open `http://localhost:8000` in a browser and log in with username `admin`, password `infrahub`. ### Load data[​](#load-data "Direct link to Load data") ``` uv run inv load ``` This runs four steps in sequence: 1. `infrahubctl schema load schemas` — loads the 7 schema files defining the data model 2. `infrahubctl menu load menus/` — loads UI menu configuration 3. `infrahubctl object load objects/` — loads all design objects (groups, manufacturers, device types, IPAM, overlay resource pools, profiles, device templates, fabrics, pods, racks, and the seed `Blue` overlay tenant) 4. `infrahubctl object load repository.yml` — registers the Git repository with Infrahub Repository sync After `inv load`, Infrahub imports the repository and reads `.infrahub.yml` to register Generators, Transforms, queries, and artifact definitions. Verify with `infrahubctl repository list`. Generators cannot run until this completes. ### Load trigger rules[​](#load-trigger-rules "Direct link to Load trigger rules") ``` uv run infrahubctl object load triggers.yml ``` This creates the `CoreGeneratorAction` and `CoreNodeTriggerRule` objects that drive automatic modular Generator execution — including the tenant/overlay trigger (`run-tenant-generator`). `triggers.yml` is a committed top-level file that is **not** part of `inv load`: it must be loaded after the repository has synced, because the trigger rules reference Generator definitions that need to exist first. Load triggers after repository sync If loaded before Generator definitions are imported, the trigger rules will fail to resolve their action references. ### Load supplementary data (optional)[​](#load-supplementary-data-optional "Direct link to Load supplementary data (optional)") ``` # NOTE: the data/ directory is NOT loaded by `inv load`. uv run infrahubctl object load data/permissions.yml # sample operator account `john` uv run infrahubctl object load data/tenant-red.yml # second, day-two overlay tenant `Red` ``` The `data/` directory holds material that is intentionally kept out of the default load: a sample operator persona (account `john`, role and group definitions) and a second overlay tenant (`Red` on Fabric-A) used to demonstrate scoped, non-clobbering day-two multi-tenant generation. Load these manually when you want them. ## Repository structure[​](#repository-structure "Direct link to Repository structure") The repository is self-contained — everything below is included. You do not need to build any of these from scratch. ### Schemas[​](#schemas "Direct link to Schemas") | File | Contents | | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | `schemas/logical_design.yml` | `NetworkFabric` and `NetworkPod` — the design hierarchy with Generator signaling attributes (checksum, amount\_of\_spines) and the fabric overlay ASN | | `schemas/physical_location.yml` | `LocationHall` and `LocationRack` — physical locations with Generator target support | | `schemas/device.yml` | `NetworkDevice`, `NetworkInterface`, and `NetworkLink` — devices with computed attributes, ASN, route-reflector role, and VTEP loopback | | `schemas/ipam.yml` | `IpamIPPrefix` with role-based allocation and `IpamIPAddress` | | `schemas/overlay.yml` | `NetworkTenant`, `NetworkVrf`, and `NetworkSegment` — the overlay intent model | | `schemas/routing.yml` | `NetworkBGPSession` — the stored iBGP EVPN control-plane sessions | | `schemas/generator.yml` | `GeneratorTarget` generic enabling trigger-based modular Generator execution via checksum attribute | ### Generators[​](#generators "Direct link to Generators") | File | Responsibility | | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | `generators/generate_fabric.py` | FabricGenerator — IP pool and overlay ASN allocation, super spine creation, writes checksums to child Pods | | `generators/generate_pod.py` | PodGenerator — validates fabric complete, spine creation, spine-to-super-spine cabling, iBGP EVPN sessions, writes checksums to child Racks | | `generators/generate_rack.py` | RackGenerator — validates pod complete, leaf creation, leaf-to-spine cabling, VTEP loopbacks | | `generators/generate_tenant.py` | OverlayGenerator (registered as `generate-tenant`, targets the `tenants` group) — allocates overlay identifiers and materializes tenant/VRF/segment state | Each Generator has a paired `.gql` query file in the same directory. The `.infrahub.yml` file in the repository root wires Generator definitions to their queries and target groups. Note the naming split for the overlay Generator: the definition, file, and query are named `generate-tenant`/`generate_tenant`, while the Python class is `OverlayGenerator`. ### Transforms and artifacts[​](#transforms-and-artifacts "Direct link to Transforms and artifacts") | File | Purpose | | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `transforms/startup_config.gql` + `templates/startup_config_{cisco,arista,dell}.j2` | Three per-vendor Jinja2 Transforms, each producing a startup configuration artifact for the devices in its vendor group (`cisco_devices` / `arista_devices` / `dell_devices`). See [Multivendor configuration](/infrahub-solution-ai-dc/solution-ai-dc/multivendor-config.md). | | `transforms/cabling_plan.py` / `.gql` | Python Transform producing a CSV cabling plan artifact per fabric | | `transforms/computed_interface_description.py` / `.gql` | Python Transform applied as a computed attribute on each interface | ### Demo data (object files)[​](#demo-data-object-files "Direct link to Demo data (object files)") Loaded in numbered order by `infrahubctl object load objects/`: | File | Contents | | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `objects/01_groups.yml` | `CoreStandardGroup` objects: `halls`, `racks`, `fabrics`, `pods`, `devices`, `tenants`, and the per-vendor device groups `cisco_devices` / `arista_devices` / `dell_devices` | | `objects/02_manufacturer.yml` | Equipment manufacturers (Cisco, Arista, Dell) | | `objects/03_device_type.yml` | Per-vendor device types for leaf, spine, and super spine switches | | `objects/04_ipam.yml` | IP supernet (`10.0.0.0/8`) and `FabricSupernetPool` | | `objects/05_profiles.yml` | Interface role profiles (MTU, role assignments) | | `objects/06_device_template.yml` | Per-vendor device templates defining interface layouts per device role | | `objects/07_pools.yml` | Overlay resource pools: ASN, L2VNI, L3VNI, and VLAN number pools | | `objects/10_fabric.yml` | Fabric and Pod objects: Fabric-A (Cisco), Fabric-B (Arista), Fabric-C (Dell) — each single-vendor | | `objects/11_rack.yml` | Rack objects across all three fabrics with leaf counts and per-vendor template assignments | | `objects/12_overlay.yml` | Seed overlay tenant `Blue` on Fabric-A, with a VRF and segments (routed, rack-scoped, and L2-only examples) | Trigger rules live in the top-level `triggers.yml` (not under `objects/`) and are loaded separately after repository sync — see [Load trigger rules](#load-trigger-rules). Supplementary data (`data/permissions.yml`, `data/tenant-red.yml`) is loaded manually — see [Load supplementary data](#load-supplementary-data-optional). ### Infrastructure[​](#infrastructure "Direct link to Infrastructure") | File | Purpose | | ----------------------------- | ------------------------------------------------------------------------------------------- | | `Dockerfile` | Builds a custom Infrahub image with the `infrahub-solution-ai-dc` Python package installed | | `docker-compose.override.yml` | Replaces standard Infrahub image with the custom build; mounts `src/` for live code changes | | `repository.yml` | `CoreRepository` object pointing to `/upstream` (the mounted repository) | | `tasks.py` | Invoke task definitions for environment management | | `.infrahub.yml` | Central configuration wiring Generators, Transforms, queries, and artifacts | ## Invoke tasks[​](#invoke-tasks "Direct link to Invoke tasks") | Task | Command | Description | | ----------- | ----------------- | ------------------------------------------------------------------ | | Start | `inv start` | Download compose file (if needed) and start all services | | Stop | `inv stop` | Stop containers, remove networks (data preserved) | | Destroy | `inv destroy` | Stop containers, remove networks **and volumes** (full reset) | | Restart | `inv restart` | Restart all services (or `inv restart --component=` for one) | | Load | `inv load` | Load schemas, menus, objects, and register repository | | Load schema | `inv load-schema` | Load schema files only | | Load menu | `inv load-menu` | Load menu definitions only | | Build | `inv build` | Build the custom Docker image locally | | Test | `inv test` | Run pytest test suite | | Format | `inv format` | Run Ruff formatter and auto-fix linting issues | ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Services fail to start[​](#services-fail-to-start "Direct link to Services fail to start") * Check Docker is running: `docker info` * Check port conflicts: Infrahub uses ports 8000, 4200, 7474, 7687 * View logs: `docker compose logs infrahub-server` or `docker compose logs task-worker` * Full reset: `uv run inv destroy && uv run inv start` ### Repository not syncing[​](#repository-not-syncing "Direct link to Repository not syncing") * Verify with `infrahubctl repository list` * The repository mounts from `/upstream` inside the container — check `docker compose logs task-worker` for import errors * Schema mismatches between what `.infrahub.yml` references and what is loaded can block sync --- # Modular Generator architecture The AI/DC solution uses four modular Generators. Three build the physical fabric — FabricGenerator, PodGenerator, RackGenerator — connecting automatically through checksum-based triggers. A fourth, the OverlayGenerator (`generate-tenant`), layers EVPN/VXLAN services on top; it uses the same checksum-trigger mechanism but is signaled by tenant design changes rather than by the fabric cascade. This page explains the signaling mechanism, how each tier validates that the previous tier completed, and how interface allocation works. See [Design-driven automation](/infrahub-solution-ai-dc/solution-ai-dc/design-driven-automation.md) for what the Generators read and produce, or [Generator patterns](/infrahub-solution-ai-dc/solution-ai-dc/generator-patterns.md) for implementation-level code patterns. ![Modular Generator execution flow](/assets/images/modular-generator-flow-9c11d62bb17e252a49c7ce53da2ca303.svg) ## How the modular Generators connect[​](#how-the-modular-generators-connect "Direct link to How the modular Generators connect") ### Checksum-driven signaling[​](#checksum-driven-signaling "Direct link to Checksum-driven signaling") Each Generator writes a checksum to its child objects after it finishes. That checksum change fires a trigger rule, which runs the next Generator tier. The process repeats until the lowest tier completes. The `GeneratorTarget` generic (defined in `schemas/generator.yml`) adds a `checksum` attribute to both `NetworkPod` and `LocationRack`. This attribute is the only thing that flows from parent to child — it carries no configuration, only a signal that "something changed upstream." The checksum itself is a SHA-256 hash of all object IDs fetched during the Generator run. It is deterministic: the same set of objects produces the same hash. If a Generator re-runs and nothing has changed, the checksum stays the same and no downstream triggers fire. ### Trigger rules[​](#trigger-rules "Direct link to Trigger rules") Trigger rules connect checksum changes to Generator actions. Each rule watches for an attribute update on a specific node type and fires the corresponding Generator. | Trigger | Watches | Attribute | Fires | | ------------------------ | --------------- | ----------------------------------------------- | ----------------- | | Pod Generator triggers | `NetworkPod` | `checksum`, `index`, `amount_of_spines`, `role` | `generate-pod` | | Rack Generator triggers | `LocationRack` | `checksum`, `index`, `rack_type` | `generate-rack` | | Tenant Generator trigger | `NetworkTenant` | `checksum` | `generate-tenant` | The checksum trigger handles the modular execution flow. The additional attribute triggers allow a Generator to re-run when an operator changes a design parameter (for example, updating `amount_of_spines` on a Pod). The tenant trigger re-runs the OverlayGenerator whenever a tenant's overlay design changes — the same scoped, checksum-driven pattern, applied to overlay intent. Branch scope Trigger rules are scoped to branches other than `main`. Generators run on branches where changes can be reviewed before merging — they do not fire on `main` directly. ### Full execution sequence[​](#full-execution-sequence "Direct link to Full execution sequence") Running the FabricGenerator for a single fabric triggers the following sequence: 1. **FabricGenerator** creates super spine devices and IP pools, then writes a checksum to each child Pod 2. Checksum change on each Pod fires the **PodGenerator** 3. **PodGenerator** validates the parent Fabric is complete, creates spine devices and spine-to-super-spine links, then writes a checksum to each child Rack 4. Checksum change on each Rack fires the **RackGenerator** 5. **RackGenerator** validates the parent Pod is complete, creates leaf devices and leaf-to-spine links One action at the fabric level produces the entire data center — devices, links, and IP addressing across all tiers. ### Overlay generation[​](#overlay-generation "Direct link to Overlay generation") The overlay is not part of the fabric cascade — it is triggered independently by a tenant design change. When a `NetworkTenant`'s checksum updates, the trigger fires the **OverlayGenerator** (`generate-tenant`), which allocates the tenant's overlay identifiers and materializes which leaf switches carry which segments. It relies on the fabric build having already run: the three physical Generators were extended to support the overlay by allocating the per-fabric overlay **ASN**, stamping each device's ASN and **route-reflector role**, giving every leaf a dedicated **VTEP loopback**, and materializing the iBGP **EVPN sessions** along the cabling. The OverlayGenerator then only has to layer tenant services onto that control-plane baseline. See [EVPN/VXLAN overlay](/infrahub-solution-ai-dc/solution-ai-dc/evpn-vxlan-overlay.md) for the full flow. ## Validation gates[​](#validation-gates "Direct link to Validation gates") Each Generator validates that the parent tier completed before it runs. If validation fails, the Generator raises an error and aborts, preventing partial or inconsistent infrastructure from being generated. ### PodGenerator validation[​](#podgenerator-validation "Direct link to PodGenerator validation") Before creating spines, the PodGenerator checks: 1. **Role exclusion** — Pods with role `fabric` represent the super spine tier and are skipped 2. **Parent completeness** — the number of super spine devices in the fabric must match the fabric's `amount_of_super_spines`. If the FabricGenerator created only 4 of 6 expected super spines (for example, due to a failure), the PodGenerator refuses to run 3. **Template defined** — the Pod must have a `spine_switch_template` assigned ### RackGenerator validation[​](#rackgenerator-validation "Direct link to RackGenerator validation") Before creating leafs, the RackGenerator checks: 1. **Parent completeness** — the number of spine devices in the pod must match the pod's `amount_of_spines`. If the PodGenerator has not finished, the RackGenerator refuses to run 2. **Template defined** — the Rack must have a `leaf_switch_template` assigned These gates make the modular setup self-healing in practice: if a Generator fails partway through, the checksum still changes for child objects whose parent data was modified. The child Generators fire, detect that the parent is incomplete, and abort. When the failed Generator is re-run and completes, child checksums update again and execution resumes. ## Deterministic interface allocation[​](#deterministic-interface-allocation "Direct link to Deterministic interface allocation") Cabling — connecting spines to super spines, and leafs to spines — requires allocating specific interfaces on each device. The allocation must be **stable**: the same rack must always land on the same spine interfaces, regardless of what happens to other racks. ![Deterministic interface allocation](/assets/images/interface-allocation-ae79414d6cf69634a7b547edd6dbd6d2.svg) ### Why stability matters[​](#why-stability-matters "Direct link to Why stability matters") Consider a pod with 4 spines and 4 racks, fully cabled. Rack-3 is decommissioned — its leaf switches and links are removed. The spine interfaces that were connected to Rack-3 are now free, sitting in the middle of each spine's used range. If the Generator allocated interfaces by filling the next available slot, re-running it for the remaining racks would reassign them to a denser packing — shifting Rack-4's leafs onto the interfaces that Rack-3 previously occupied. In the data model, the cabling would look correct. In the physical world, those cables are already plugged in. The generated design would no longer match the actual infrastructure, and operators would need to re-cable interfaces to restore alignment. The AI/DC solution avoids this by tying interface selection to the rack's `index` attribute, not to the set of racks that currently exist. Rack-4 always connects to the same spine interfaces whether Rack-3 is present or not. Decommissioning a rack leaves a gap in the interface range — which is exactly what the physical fabric looks like. ### How it works[​](#how-it-works "Direct link to How it works") The allocation follows three steps: 1. **Collect** — fetch all interfaces of a given role (for example, all super-spine-facing interfaces on spine devices) 2. **Sort** — sort each device's interfaces by name using `netutils.interface.sort_interface_list`, producing a stable ordering (Ethernet1, Ethernet2, ... Ethernet10, Ethernet11 — not lexicographic) 3. **Index** — use the device's position (pod index or rack index) to calculate which slot in the sorted list each cable connects to Because the index is derived from the design object (`NetworkPod.index`, `LocationRack.index`), the same design always produces the same cabling. Re-running a Generator selects the same interfaces every time. ### Pod cabling: spine-to-super-spine[​](#pod-cabling-spine-to-super-spine "Direct link to Pod cabling: spine-to-super-spine") Each spine connects to every super spine. The pod's `index` attribute determines which slot on each super spine the connections use: * Pod with index 2 → spines connect starting at super spine interface slot 0 * Pod with index 3 → spines connect starting at a higher offset Within a pod, each spine occupies one slot per super spine. The offset formula ensures that pods do not compete for the same interfaces — each pod owns a contiguous range of slots on every super spine device. ### Rack cabling: leaf-to-spine[​](#rack-cabling-leaf-to-spine "Direct link to Rack cabling: leaf-to-spine") Each leaf connects to every spine in the pod. The rack's `index` attribute determines which slot on each spine the connections use: * Rack with index 1 → leafs connect to spine interface slots 0–1 (for a 2-leaf rack) * Rack with index 2 → leafs connect to spine interface slots 2–3 Within a rack, leaf 1 uses the first slot in the range and leaf 2 uses the second. Racks with a single leaf use only one slot. ### Configurable sorting direction[​](#configurable-sorting-direction "Direct link to Configurable sorting direction") The sorting direction is not hardcoded. Operators can choose between ascending (`create_sorted_device_interface_map`) and reverse (`create_reverse_sorted_device_interface_map`) per fabric level: | Schema attribute | Controls | | ----------------------------------------------- | ------------------------------------------- | | `NetworkFabric.fabric_interface_sorting_method` | Super spine interface ordering | | `NetworkFabric.spine_interface_sorting_method` | Spine interface ordering (for pod cabling) | | `NetworkPod.leaf_interface_sorting_method` | Leaf interface ordering (for rack cabling) | | `NetworkPod.spine_interface_sorting_method` | Spine interface ordering (for rack cabling) | This allows different cabling patterns on different fabrics — for example, one fabric might allocate interfaces top-down while another allocates bottom-up. ## Idempotency[​](#idempotency "Direct link to Idempotency") Every layer of the system is designed for safe re-runs: | Operation | Mechanism | | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | Device creation | `allow_upsert=True` — creating a device with an existing name updates rather than duplicates | | Link creation | `allow_upsert=True` — creating a link with existing endpoints is a no-op | | Interface allocation | Index-based — same design produces the same cabling plan every time | | IP allocation | Deterministic identifiers — the resource allocator key is derived from the interface pair IDs, so the same pair always receives the same prefix | | Checksum propagation | Deterministic hash — if nothing changed, the checksum stays the same and no downstream triggers fire | If a Generator fails mid-run, the already-created objects remain. Re-running the Generator picks up where it left off: existing objects are upserted (no duplicates), and new objects are created. The validation gates ensure that downstream Generators do not run until the failed tier completes. ## Learn more[​](#learn-more "Direct link to Learn more") * [Design-driven automation](/infrahub-solution-ai-dc/solution-ai-dc/design-driven-automation.md) — what the Generators read and produce * [Generator patterns](/infrahub-solution-ai-dc/solution-ai-dc/generator-patterns.md) — implementation-level code patterns * [EVPN/VXLAN overlay](/infrahub-solution-ai-dc/solution-ai-dc/evpn-vxlan-overlay.md) — how the OverlayGenerator layers tenant services on the fabric * [Infrahub documentation: Generators](https://docs.infrahub.app/topics/generator) * [Infrahub documentation: Resource Manager](https://docs.infrahub.app/topics/resource-manager) --- # Multivendor configuration The AI/DC solution is truly multivendor: the same design model produces **Cisco, Arista, and Dell** device configuration. A single set of Generators builds every fabric, and each device's startup configuration is rendered from a template owned by that device's vendor — not one template applied to every make. This page explains how a device's vendor is resolved, how per-vendor rendering is wired, and how the demo data is organized. For the fabric build itself, see [Design-driven automation](/infrahub-solution-ai-dc/solution-ai-dc/design-driven-automation.md) and [Modular Generator architecture](/infrahub-solution-ai-dc/solution-ai-dc/modular-generator-architecture.md). ## What it does[​](#what-it-does "Direct link to What it does") * **One design, per-vendor output.** Operators define fabrics, pods, and racks the same way regardless of vendor. When the Generators run, each device is placed into its manufacturer's device group and rendered from that vendor's configuration template. * **One Generator per layer, not per vendor.** The Generators are not forked per vendor. `generate-fabric`, `generate-pod`, and `generate-rack` each resolve a device's vendor from its template and route it to the right group — so adding a vendor does not mean adding Generators. * **Single-vendor fabrics in the demo.** The shipped dataset provides three clean single-vendor fabrics so you can exercise the solution on any of the three platforms. ## Vendor resolution[​](#vendor-resolution "Direct link to Vendor resolution") Every device is built from an object template that carries a `device_type`, and every device type carries a `manufacturer`. Vendor resolution walks that chain: ``` TemplateNetworkDevice.device_type → NetworkDeviceType.manufacturer → OrganizationManufacturer.name ``` The manufacturer name is mapped to a vendor device group by `src/infrahub_solution_ai_dc/vendors.py`: * `SUPPORTED_VENDORS = ("cisco", "arista", "dell")`. * `vendor_group_for_manufacturer(name, ...)` returns `"{vendor}_devices"` for a supported manufacturer. * `vendor_group_for_template(client, template_id)` resolves the whole chain for a device template. Because every device built from a template shares that template's manufacturer, each Generator resolves the vendor group **once per run** and stamps it onto every device it creates via `member_of_groups=["devices", "_devices"]`. Each device therefore belongs to the parent `devices` group **and** exactly one vendor group. Fail-loud resolution Resolution is deliberately fail-loud: a device whose manufacturer is missing or unsupported raises a `ValueError` that names the offending device. There is no silent skip and no default configuration — a data gap surfaces immediately rather than producing a device with no (or wrong) configuration. ## Device groups[​](#device-groups "Direct link to Device groups") `objects/01_groups.yml` defines three vendor groups as children of the existing `devices` group: | Group | Parent | Members | | ---------------- | --------- | ----------------------------------------- | | `cisco_devices` | `devices` | Every device whose manufacturer is Cisco | | `arista_devices` | `devices` | Every device whose manufacturer is Arista | | `dell_devices` | `devices` | Every device whose manufacturer is Dell | Group membership is what drives per-vendor artifact targeting — the artifact definitions target these groups directly. ## Per-vendor templates and wiring[​](#per-vendor-templates-and-wiring "Direct link to Per-vendor templates and wiring") The single `startup_config.j2` template was replaced by three per-vendor templates. All three share one GraphQL query (`transforms/startup_config.gql`); `.infrahub.yml` wires a transform and an artifact definition per vendor: | Vendor | Template | Jinja2 transform | Artifact definition (targets) | | ------ | ----------------------------------------------- | ------------------------------ | ------------------------------------------------- | | Cisco | `transforms/templates/startup_config_cisco.j2` | `cisco_device_startup_config` | `cisco_startup_configuration` (`cisco_devices`) | | Arista | `transforms/templates/startup_config_arista.j2` | `arista_device_startup_config` | `arista_startup_configuration` (`arista_devices`) | | Dell | `transforms/templates/startup_config_dell.j2` | `dell_device_startup_config` | `dell_startup_configuration` (`dell_devices`) | Because no artifact definition targets the parent `devices` group, each device renders **exactly one** startup configuration artifact — the one for its vendor. Every artifact uses the same `artifact_name` (`Startup configuration`), so the artifact reads consistently across vendors regardless of which template produced it. ## Demo data[​](#demo-data "Direct link to Demo data") The dataset is organized as three single-vendor fabrics: | Fabric | Vendor | Example switch templates | | -------- | ------ | ------------------------------------------------------------------------------------------------------------ | | Fabric-A | Cisco | `cisco-9364d-gx2-super-spine-switch`, `cisco-9364d-gx2-spine-switch`, `cisco-93400ld-h1-leaf-switch-compute` | | Fabric-B | Arista | `arista-7060dx5-64s-super-spine-switch`, `arista-7060dx5-64s-spine-switch` | | Fabric-C | Dell | `dell-z9864f-on-super-spine-switch`, `dell-z9864f-on-spine-switch` | Supporting data: * `objects/02_manufacturer.yml` — Cisco, Arista, and Dell manufacturers. * `objects/03_device_type.yml` — device types, each declaring a manufacturer. No vendor-less device types remain. * `objects/06_device_template.yml` — per-vendor device templates for each device role (including per-vendor compute and storage leaf templates). ## How to try it[​](#how-to-try-it "Direct link to How to try it") Because switching a fabric's vendor renames its interfaces, the multivendor dataset is meant to be applied to a **fresh stack** rather than in place: ``` uv sync --all-packages uv run inv destroy && uv run inv start uv run inv load ``` Then build a fabric (via the trigger pipeline, or by running `generate-fabric` for a target fabric — see the [Demo Guide](/infrahub-solution-ai-dc/solution-ai-dc/demo-guide.md)). To verify: * Each device is a member of `devices` and exactly one `{vendor}_devices` group. * Fabric-A is 100% Cisco, Fabric-B 100% Arista, Fabric-C 100% Dell. * Each device yields exactly one `Startup configuration` artifact, rendered from its vendor's template. * Editing one vendor's template changes only that vendor's device configs; the other two are byte-identical. The three templates start near-identical (NX-OS-style) and are diverged toward correct per-vendor syntax over time — editing one in isolation is safe precisely because each vendor's devices render only from their own template. ## Learn more[​](#learn-more "Direct link to Learn more") * [EVPN/VXLAN overlay](/infrahub-solution-ai-dc/solution-ai-dc/evpn-vxlan-overlay.md) — the overlay configuration is also rendered per vendor. * [Generator patterns](/infrahub-solution-ai-dc/solution-ai-dc/generator-patterns.md) — how the Generators resolve and stamp the vendor group. * The feature [specification](https://github.com/opsmill/infrahub-solution-ai-dc/blob/main/specs/002-multivendor-config/spec.md) and [quickstart](https://github.com/opsmill/infrahub-solution-ai-dc/blob/main/specs/002-multivendor-config/quickstart.md). --- # AI/DC solution The AI/DC solution is a reference implementation demonstrating how to use Infrahub to automate the full lifecycle of a large-scale AI data center. It takes a small number of design inputs — fabric topology, pod configuration, rack assignments — and generates a complete data center: devices, IP allocations, cabling plans, and configuration data. Four modular Generators connect automatically via event-driven triggers: three build the physical fabric (Fabric → Pod → Rack), and a fourth layers multi-tenant EVPN/VXLAN overlay services on top. Triggering one Generator at the fabric level causes the entire data center to build itself, and the same design renders per-vendor (Cisco, Arista, Dell) device configuration. The solution is fully functional today and is positioned as a demo and reference implementation. All of the content provided in this solution can be found on the [`infrahub-solution-ai-dc` GitHub repository](https://github.com/opsmill/infrahub-solution-ai-dc). ## The problem this solves[​](#the-problem-this-solves "Direct link to The problem this solves") * Large-scale data center operators must deploy standardized environments rapidly, repeatedly, and without deviation — while extending those environments over time without disrupting what is already running. * The capital cost of AI hardware means time-to-production directly affects ROI. Inconsistency between environments introduces risk at every layer. * Traditional scripted automation discards design intent after the run. Day-two operations require rebuilding from scratch. There is no parallelism, and no stored relationship between the configuration produced and the design that produced it. * Infrahub's design-driven approach solves this: operators define what infrastructure should look like, and Generators produce it — consistently, in parallel, and with surgical day-two change support. * The pattern is not AI-specific. It applies to any standardized, layered infrastructure domain. ## What the AI/DC solution is[​](#what-the-aidc-solution-is "Direct link to What the AI/DC solution is") The AI/DC solution builds a 5-stage Clos data center fabric from minimal design inputs. The hierarchy follows a three-level structure: * **Fabric** — the top level, containing super spine switches * **Pod** — the middle level, containing spine switches connected to the super spines above * **Rack** — the bottom level, containing leaf switches connected to the spines above Three Generators each own one layer of this hierarchy and connect automatically via checksum-triggered events: 1. **FabricGenerator** allocates IP pools and creates super spine switches, then signals child Pods to run. 2. **PodGenerator** creates spine switches, connects them to super spines, and signals child Racks to run. 3. **RackGenerator** creates leaf switches and connects them to spines. Trigger once at the fabric level — the entire data center builds itself. On top of this physical fabric, a fourth Generator — the **OverlayGenerator** (registered as `generate-tenant`) — turns tenant intent into multi-tenant EVPN/VXLAN services. See [EVPN/VXLAN overlay](/infrahub-solution-ai-dc/solution-ai-dc/evpn-vxlan-overlay.md). The demo data includes three complete, single-vendor fabrics: * **Fabric-A** — Cisco equipment * **Fabric-B** — Arista equipment * **Fabric-C** — Dell equipment Each fabric is built entirely from one vendor's device templates, and each device's configuration is rendered from that vendor's template. See [Multivendor configuration](/infrahub-solution-ai-dc/solution-ai-dc/multivendor-config.md). The solution generates IP pool allocations, interface-level cabling plans, OSPF underlay configuration, EVPN/VXLAN overlay configuration, and computed interface descriptions. ## Who this is for[​](#who-this-is-for "Direct link to Who this is for") Choose your path **Running a demo or evaluating Infrahub?** Start with the [Demo Guide](/infrahub-solution-ai-dc/solution-ai-dc/demo-guide.md) — it walks through the solution end to end with no code modifications required. **Building your own Generators or adapting the patterns?** Start with the [Reference Guide: Generator Patterns](/infrahub-solution-ai-dc/solution-ai-dc/generator-patterns.md) — it covers the implementation patterns in detail. ### Persona 1 — Evaluator / Learner[​](#persona-1--evaluator--learner "Direct link to Persona 1 — Evaluator / Learner") You want to see design-driven automation in action. You do not need to modify code. Your path: run the demo, understand what happened, and connect it to your own use case. → [Demo Guide](/infrahub-solution-ai-dc/solution-ai-dc/demo-guide.md) ### Persona 2 — Advanced Implementer[​](#persona-2--advanced-implementer "Direct link to Persona 2 — Advanced Implementer") You are already working with Infrahub. You want a production-quality reference for modular Generator patterns, checksum-triggered cascades, and IP space delegation. You will read and adapt the code. → [Reference Guide: Generator Patterns](/infrahub-solution-ai-dc/solution-ai-dc/generator-patterns.md) **Broader audience:** The patterns demonstrated here apply to any standardized, layered infrastructure at scale — edge deployments, service provider networks, enterprise campuses. The Fabric → Pod → Rack hierarchy is illustrative, not prescriptive. ## What's included[​](#whats-included "Direct link to What's included") ### Schema[​](#schema "Direct link to Schema") Seven schema files define the data model: | File | Contents | | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | `logical_design.yml` | NetworkFabric and NetworkPod — the design hierarchy with Generator signaling attributes and the fabric overlay ASN | | `physical_location.yml` | LocationHall and LocationRack — physical locations with Generator target support | | `device.yml` | NetworkDevice, NetworkInterface, and NetworkLink — devices with computed attributes, ASN, route-reflector role, and VTEP loopback | | `ipam.yml` | IpamIPPrefix with role-based allocation and IpamIPAddress | | `overlay.yml` | NetworkTenant, NetworkVrf, and NetworkSegment — the overlay intent model | | `routing.yml` | NetworkBGPSession — the stored iBGP EVPN control plane | | `generator.yml` | GeneratorTarget generic with checksum attribute enabling trigger-based cascades | ### Generators[​](#generators "Direct link to Generators") Four Generators — three own one layer of the physical hierarchy, and one materializes the overlay: | Generator | Responsibility | | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | | FabricGenerator | IP pool and overlay ASN allocation, super spine switch creation | | PodGenerator | Spine switch creation, spine-to-super-spine cabling, iBGP EVPN sessions | | RackGenerator | Leaf switch creation, leaf-to-spine cabling, VTEP loopbacks | | OverlayGenerator (`generate-tenant`) | Allocates overlay identifiers (VNI/VLAN/ASN/route target), places segments on leaf switches, materializes tenant/VRF/segment state | ### Transforms and artifacts[​](#transforms-and-artifacts "Direct link to Transforms and artifacts") * **Startup configuration** — three per-vendor Jinja2 templates (Cisco, Arista, Dell), each producing a text/plain artifact for the devices in its vendor group * **Cabling plan** — Python Transformation producing a CSV artifact per fabric * **Computed interface descriptions** — Python Transformation applied as a computed attribute on each interface ### Demo data[​](#demo-data "Direct link to Demo data") * Three complete single-vendor fabrics (Cisco, Arista, Dell) with pods, racks, device templates, and IP pools * A seed overlay tenant (`Blue` on Fabric-A) with VRFs and segments * Manufacturer and device type definitions * Overlay resource pools (ASN, L2VNI, L3VNI, VLAN) * Event trigger rules (`CoreNodeTriggerRule` and `CoreGeneratorAction`) for automatic modular Generator execution ### Tooling[​](#tooling "Direct link to Tooling") * Docker Compose environment for local development * Invoke tasks for setup, loading, and testing * Python package management via `uv` → [Installation & Setup](/infrahub-solution-ai-dc/solution-ai-dc/installation-setup.md) → [GitHub repository](https://github.com/opsmill/infrahub-solution-ai-dc) --- # infrahubctl `infrahubctl` is a command line utility designed to help with the day to day management of an Infrahub installation. It's meant to run on any laptop or server and it communicates with a remote Infrahub server over the network. `infrahubctl` can help you to: * Manage the branches in Infrahub: List, Create, Merge, Rebase, Delete. * Manage the schema and load new schema files into Infrahub. * Browse and download schemas and collections from the Infrahub Marketplace. * Execute any Python script that requires access to the Python SDK. * Render a Jinja Template locally for troubleshooting. * Execute a GraphQL query store in a Git repository for troubleshooting. * Validate that input files conform with the format expected by Infrahub. ## Installation[​](#installation "Direct link to Installation") The `infrahubctl` command line utility is installed as a part of the [Infrahub SDK for Python](/python-sdk/guides/installation.md#ctl). ## Configuration[​](#configuration "Direct link to Configuration") `infrahubctl` requires a minimum configuration in order to connect to the right Infrahub server with the correct credentials. These settings can be provided either in a configuration file, `infrahubctl.toml`, or via environment variables. ### Environment variables[​](#environment-variables "Direct link to Environment variables") | Name | Example value | | ------------------------- | -------------------------------------- | | `INFRAHUB_ADDRESS` | | | `INFRAHUB_API_TOKEN` | `06438eb2-8019-4776-878c-0941b1f1d1ec` | | `INFRAHUB_DEFAULT_BRANCH` | main | > You can also provide the location of a configuration file via the environment variable `INFRAHUBCTL_CONFIG`. ### `infrahubctl.toml` file[​](#infrahubctltoml-file "Direct link to infrahubctltoml-file") infrahubctl.toml ``` server_address="http://localhost:8000" api_token="06438eb2-8019-4776-878c-0941b1f1d1ec" ``` --- # `infrahubctl branch` Manage the branches in a remote Infrahub instance. List, create, merge, rebase .. **Usage**: ``` $ infrahubctl branch [OPTIONS] COMMAND [ARGS]... ``` **Options**: * `--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. **Commands**: * `list`: List all existing branches. * `create`: Create a new branch. * `delete`: Delete a branch. * `rebase`: Rebase a Branch with main. * `merge`: Merge a Branch with main. * `validate`: Validate if a branch has some conflict and... * `report`: Generate branch cleanup status report. ## `infrahubctl branch list`[​](#infrahubctl-branch-list "Direct link to infrahubctl-branch-list") List all existing branches. **Usage**: ``` $ infrahubctl branch list [OPTIONS] ``` **Options**: * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--help`: Show this message and exit. ## `infrahubctl branch create`[​](#infrahubctl-branch-create "Direct link to infrahubctl-branch-create") Create a new branch. **Usage**: ``` $ infrahubctl branch create [OPTIONS] BRANCH_NAME ``` **Arguments**: * `BRANCH_NAME`: Name of the branch to create \[required] **Options**: * `--description TEXT`: Description of the branch * `--sync-with-git / --no-sync-with-git`: Extend the branch to Git and have Infrahub create the branch in connected repositories. \[default: no-sync-with-git] * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--help`: Show this message and exit. ## `infrahubctl branch delete`[​](#infrahubctl-branch-delete "Direct link to infrahubctl-branch-delete") Delete a branch. **Usage**: ``` $ infrahubctl branch delete [OPTIONS] BRANCH_NAME ``` **Arguments**: * `BRANCH_NAME`: \[required] **Options**: * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--help`: Show this message and exit. ## `infrahubctl branch rebase`[​](#infrahubctl-branch-rebase "Direct link to infrahubctl-branch-rebase") Rebase a Branch with main. **Usage**: ``` $ infrahubctl branch rebase [OPTIONS] BRANCH_NAME ``` **Arguments**: * `BRANCH_NAME`: \[required] **Options**: * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--help`: Show this message and exit. ## `infrahubctl branch merge`[​](#infrahubctl-branch-merge "Direct link to infrahubctl-branch-merge") Merge a Branch with main. **Usage**: ``` $ infrahubctl branch merge [OPTIONS] BRANCH_NAME ``` **Arguments**: * `BRANCH_NAME`: \[required] **Options**: * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--help`: Show this message and exit. ## `infrahubctl branch validate`[​](#infrahubctl-branch-validate "Direct link to infrahubctl-branch-validate") Validate if a branch has some conflict and is passing all the tests (NOT IMPLEMENTED YET). **Usage**: ``` $ infrahubctl branch validate [OPTIONS] BRANCH_NAME ``` **Arguments**: * `BRANCH_NAME`: \[required] **Options**: * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--help`: Show this message and exit. ## `infrahubctl branch report`[​](#infrahubctl-branch-report "Direct link to infrahubctl-branch-report") Generate branch cleanup status report. **Usage**: ``` $ infrahubctl branch report [OPTIONS] BRANCH_NAME ``` **Arguments**: * `BRANCH_NAME`: Branch name to generate report for \[required] **Options**: * `--update-diff`: Update diff before generating report * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--help`: Show this message and exit. --- # `infrahubctl check` Execute user-defined checks. **Usage**: ``` $ infrahubctl check [OPTIONS] [CHECK_NAME] [VARIABLES]... ``` **Arguments**: * `[CHECK_NAME]`: Name of the Python check * `[VARIABLES]...`: Variables to pass along with the query. Format key=value key=value. **Options**: * `--branch TEXT` * `--path TEXT`: Root directory \[default: .] * `--debug / --no-debug`: \[default: no-debug] * `--format-json / --no-format-json`: \[default: no-format-json] * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--list`: Show available Python checks * `--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. --- # `infrahubctl dump` Export nodes and their relationships out of the database. **Usage**: ``` $ infrahubctl dump [OPTIONS] ``` **Options**: * `--namespace TEXT`: Namespace(s) to export * `--directory PATH`: Directory path to store export \[default: (dynamic)] * `--quiet / --no-quiet`: No console output \[default: no-quiet] * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--branch TEXT`: Branch from which to export * `--concurrent INTEGER`: Maximum number of requests to execute at the same time. \[env var: INFRAHUB\_MAX\_CONCURRENT\_EXECUTION; default: 4] * `--timeout INTEGER`: Timeout in sec \[env var: INFRAHUB\_TIMEOUT; default: 60] * `--exclude TEXT`: Prevent node kind(s) from being exported, CoreAccount is excluded by default \[default: CoreAccount] * `--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. --- # `infrahubctl generator` Run a generator script. **Usage**: ``` $ infrahubctl generator [OPTIONS] [GENERATOR_NAME] [VARIABLES]... ``` **Arguments**: * `[GENERATOR_NAME]`: Name of the Generator * `[VARIABLES]...`: Variables to pass along with the query. Format key=value key=value. **Options**: * `--branch TEXT` * `--path TEXT`: Root directory \[default: .] * `--debug / --no-debug`: \[default: no-debug] * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--list`: Show available Generators * `--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. --- # `infrahubctl graphql` Various GraphQL related commands. **Usage**: ``` $ infrahubctl graphql [OPTIONS] COMMAND [ARGS]... ``` **Options**: * `--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. **Commands**: * `query-report`: Run a GraphQL query through... * `export-schema`: Export the GraphQL schema to a file. * `generate-return-types`: Create Pydantic Models for GraphQL query... ## `infrahubctl graphql query-report`[​](#infrahubctl-graphql-query-report "Direct link to infrahubctl-graphql-query-report") Run a GraphQL query through InfrahubGraphQLQueryReport and report its analysis. **Usage**: ``` $ infrahubctl graphql query-report [OPTIONS] NAME ``` **Arguments**: * `NAME`: Name of the GraphQL query to analyze. \[required] **Options**: * `--online`: Fetch the query from the Infrahub server (CoreGraphQLQuery by name) instead of reading it from the local .infrahub.yml file. * `--branch TEXT`: Branch on which to run the report. * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--help`: Show this message and exit. ## `infrahubctl graphql export-schema`[​](#infrahubctl-graphql-export-schema "Direct link to infrahubctl-graphql-export-schema") Export the GraphQL schema to a file. **Usage**: ``` $ infrahubctl graphql export-schema [OPTIONS] ``` **Options**: * `--destination PATH`: Path to the GraphQL schema file. \[default: schema.graphql] * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--help`: Show this message and exit. ## `infrahubctl graphql generate-return-types`[​](#infrahubctl-graphql-generate-return-types "Direct link to infrahubctl-graphql-generate-return-types") Create Pydantic Models for GraphQL query return types. **Usage**: ``` $ infrahubctl graphql generate-return-types [OPTIONS] [QUERY] ``` **Arguments**: * `[QUERY]`: Location of the GraphQL query file(s). Defaults to current directory if not specified. **Options**: * `--schema PATH`: Path to the GraphQL schema file. \[default: schema.graphql] * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--help`: Show this message and exit. --- # `infrahubctl info` Display the status of the Python SDK. **Usage**: ``` $ infrahubctl info [OPTIONS] ``` **Options**: * `--detail / --no-detail`: Display detailed information. \[default: no-detail] * `--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. --- # `infrahubctl load` Import nodes and their relationships into the database. **Usage**: ``` $ infrahubctl load [OPTIONS] ``` **Options**: * `--directory PATH`: Directory path of exported data \[default: (dynamic)] * `--continue-on-error / --no-continue-on-error`: Allow exceptions during loading and display them when complete \[default: no-continue-on-error] * `--quiet / --no-quiet`: No console output \[default: no-quiet] * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--branch TEXT`: Branch from which to export * `--concurrent INTEGER`: Maximum number of requests to execute at the same time. \[env var: INFRAHUB\_MAX\_CONCURRENT\_EXECUTION] * `--timeout INTEGER`: Timeout in sec \[env var: INFRAHUB\_TIMEOUT; default: 60] * `--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. --- # `infrahubctl marketplace` Browse and download schemas from the Infrahub Marketplace. **Usage**: ``` $ infrahubctl marketplace [OPTIONS] COMMAND [ARGS]... ``` **Options**: * `--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. **Commands**: * `show`: Show full details of a schema or... * `list`: List schemas (default) or collections... * `search`: Search the Infrahub Marketplace for... * `get`: Fetch a schema or collection from the... ## `infrahubctl marketplace show`[​](#infrahubctl-marketplace-show "Direct link to infrahubctl-marketplace-show") Show full details of a schema or collection from the Infrahub Marketplace. **Usage**: ``` $ infrahubctl marketplace show [OPTIONS] IDENTIFIER ``` **Arguments**: * `IDENTIFIER`: Schema or collection identifier in namespace/name format \[required] **Options**: * `-c, --collection`: Force collection lookup. Default: auto-detect whether the identifier is a schema or collection. * `--json`: Output raw JSON to stdout instead of a table. * `--marketplace-url TEXT`: Base URL of the Infrahub Marketplace. Overrides configuration and environment. * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--help`: Show this message and exit. ## `infrahubctl marketplace list`[​](#infrahubctl-marketplace-list "Direct link to infrahubctl-marketplace-list") List schemas (default) or collections available on the Infrahub Marketplace. **Usage**: ``` $ infrahubctl marketplace list [OPTIONS] ``` **Options**: * `--collections`: List collections instead of schemas. * `-l, --limit INTEGER`: Maximum number of results to display. * `--json`: Output raw JSON to stdout instead of a table. * `--marketplace-url TEXT`: Base URL of the Infrahub Marketplace. Overrides configuration and environment. * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--help`: Show this message and exit. ## `infrahubctl marketplace search`[​](#infrahubctl-marketplace-search "Direct link to infrahubctl-marketplace-search") Search the Infrahub Marketplace for schemas (default) or collections. **Usage**: ``` $ infrahubctl marketplace search [OPTIONS] TERM ``` **Arguments**: * `TERM`: Search term matched against name, display name, and description. \[required] **Options**: * `--collections`: Search collections instead of schemas. * `-l, --limit INTEGER`: Maximum number of results to display. * `--json`: Output raw JSON to stdout instead of a table. * `--marketplace-url TEXT`: Base URL of the Infrahub Marketplace. Overrides configuration and environment. * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--help`: Show this message and exit. ## `infrahubctl marketplace get`[​](#infrahubctl-marketplace-get "Direct link to infrahubctl-marketplace-get") Fetch a schema or collection from the Infrahub Marketplace. By default, auto-detects whether `namespace/name` is a schema or a collection. Pass --collection to force the collection path when an identifier exists as both. **Usage**: ``` $ infrahubctl marketplace get [OPTIONS] IDENTIFIER ``` **Arguments**: * `IDENTIFIER`: Schema or collection identifier in namespace/name format \[required] **Options**: * `-v, --version TEXT`: Specific schema version, for example 1.2.0. Default: latest published. * `-c, --collection`: Force collection download. Default: auto-detect whether the identifier is a schema or collection. * `-s, --stdout`: Print content to stdout instead of writing to disk. Status messages go to stderr. * `-o, --output-dir PATH`: Directory to save downloaded files. \[default: schemas] * `--marketplace-url TEXT`: Base URL of the Infrahub Marketplace. Overrides configuration and environment. * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--help`: Show this message and exit. --- # `infrahubctl menu` Manage the menu in a remote Infrahub instance. **Usage**: ``` $ infrahubctl menu [OPTIONS] COMMAND [ARGS]... ``` **Options**: * `--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. **Commands**: * `load`: Load one or multiple menu files into... * `validate`: Validate one or multiple menu files. ## `infrahubctl menu load`[​](#infrahubctl-menu-load "Direct link to infrahubctl-menu-load") Load one or multiple menu files into Infrahub. **Usage**: ``` $ infrahubctl menu load [OPTIONS] MENUS... ``` **Arguments**: * `MENUS...`: \[required] **Options**: * `--debug / --no-debug`: \[default: no-debug] * `--branch TEXT`: Branch on which to load the menu. * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--help`: Show this message and exit. ## `infrahubctl menu validate`[​](#infrahubctl-menu-validate "Direct link to infrahubctl-menu-validate") Validate one or multiple menu files. **Usage**: ``` $ infrahubctl menu validate [OPTIONS] PATHS... ``` **Arguments**: * `PATHS...`: \[required] **Options**: * `--debug / --no-debug`: \[default: no-debug] * `--branch TEXT`: Branch on which to validate the objects. * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--help`: Show this message and exit. --- # `infrahubctl object` Manage objects in a remote Infrahub instance. **Usage**: ``` $ infrahubctl object [OPTIONS] COMMAND [ARGS]... ``` **Options**: * `--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. **Commands**: * `get`: Query and display Infrahub objects. * `create`: Create a new object in Infrahub. * `update`: Update an existing object in Infrahub. * `delete`: Delete an Infrahub object. * `load`: Load one or multiple objects files into... * `validate`: Validate one or multiple objects files. ## `infrahubctl object get`[​](#infrahubctl-object-get "Direct link to infrahubctl-object-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 object get InfraDevice infrahubctl object get InfraDevice spine01 infrahubctl object get InfraDevice --filter name\_\_value=spine01 infrahubctl object get InfraDevice --output json infrahubctl object 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**: ``` $ infrahubctl object get [OPTIONS] KIND [IDENTIFIER] ``` **Arguments**: * `KIND`: Infrahub schema kind to query \[required] * `[IDENTIFIER]`: UUID, name, or HFID (use / for multi-part, for example: 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] * `--help`: Show this message and exit. ## `infrahubctl object create`[​](#infrahubctl-object-create "Direct link to infrahubctl-object-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 object create InfraDevice --set name=spine01 --set status=active infrahubctl object create InfraDevice --set name=spine01 --set location=DC1 infrahubctl object create InfraDevice --file devices.yml **Usage**: ``` $ infrahubctl object 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] * `--help`: Show this message and exit. ## `infrahubctl object update`[​](#infrahubctl-object-update "Direct link to infrahubctl-object-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: infrahubctl object update InfraDevice spine01 --set status=active infrahubctl object update InfraDevice spine01 --set location=DC1 infrahubctl object update InfraDevice spine01 --file updates.yml **Usage**: ``` $ infrahubctl object update [OPTIONS] KIND IDENTIFIER ``` **Arguments**: * `KIND`: Infrahub schema kind \[required] * `IDENTIFIER`: UUID, name, or HFID (use / for multi-part, for example: 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] * `--help`: Show this message and exit. ## `infrahubctl object delete`[​](#infrahubctl-object-delete "Direct link to infrahubctl-object-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 object delete InfraDevice spine01 infrahubctl object delete InfraDevice spine01 --yes **Usage**: ``` $ infrahubctl object delete [OPTIONS] KIND IDENTIFIER ``` **Arguments**: * `KIND`: Infrahub schema kind \[required] * `IDENTIFIER`: UUID, name, or HFID (use / for multi-part, for example: 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] * `--help`: Show this message and exit. ## `infrahubctl object load`[​](#infrahubctl-object-load "Direct link to infrahubctl-object-load") Load one or multiple objects files into Infrahub. **Usage**: ``` $ infrahubctl object load [OPTIONS] PATHS... ``` **Arguments**: * `PATHS...`: \[required] **Options**: * `--debug / --no-debug`: \[default: no-debug] * `--branch TEXT`: Branch on which to load the objects. * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--help`: Show this message and exit. ## `infrahubctl object validate`[​](#infrahubctl-object-validate "Direct link to infrahubctl-object-validate") Validate one or multiple objects files. **Usage**: ``` $ infrahubctl object validate [OPTIONS] PATHS... ``` **Arguments**: * `PATHS...`: \[required] **Options**: * `--debug / --no-debug`: \[default: no-debug] * `--branch TEXT`: Branch on which to validate the objects. * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--help`: Show this message and exit. --- # `infrahubctl protocols` Export Python protocols corresponding to a schema. **Usage**: ``` $ infrahubctl protocols [OPTIONS] ``` **Options**: * `--schemas PATH`: List of schemas or directory to load. * `--branch TEXT`: Branch of schema to export Python protocols for. * `--sync / --no-sync`: Generate for sync or async. \[default: no-sync] * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--out TEXT`: Path to a file to save the result. \[default: schema\_protocols.py] * `--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. --- # `infrahubctl render` Render a local Jinja2 Transform for debugging purpose. **Usage**: ``` $ infrahubctl render [OPTIONS] [TRANSFORM_NAME] [VARIABLES]... ``` **Arguments**: * `[TRANSFORM_NAME]`: Name of the Python transformation * `[VARIABLES]...`: Variables to pass along with the query. Format key=value key=value. **Options**: * `--branch TEXT`: Branch on which to render the transform. * `--debug / --no-debug`: \[default: no-debug] * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--list`: Show available transforms * `--out TEXT`: Path to a file to save the result. * `--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. --- # `infrahubctl repository` Manage the repositories in a remote Infrahub instance. List, create, delete .. **Usage**: ``` $ infrahubctl repository [OPTIONS] COMMAND [ARGS]... ``` **Options**: * `--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. **Commands**: * `add`: Add a new repository. * `list` * `init`: Initialize a new Infrahub repository. ## `infrahubctl repository add`[​](#infrahubctl-repository-add "Direct link to infrahubctl-repository-add") Add a new repository. **Usage**: ``` $ infrahubctl repository add [OPTIONS] NAME LOCATION ``` **Arguments**: * `NAME`: \[required] * `LOCATION`: \[required] **Options**: * `--description TEXT` * `--username TEXT` * `--password TEXT` * `--ref TEXT` * `--read-only / --no-read-only`: \[default: no-read-only] * `--debug / --no-debug`: \[default: no-debug] * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--help`: Show this message and exit. ## `infrahubctl repository list`[​](#infrahubctl-repository-list "Direct link to infrahubctl-repository-list") **Usage**: ``` $ infrahubctl repository list [OPTIONS] ``` **Options**: * `--branch TEXT`: Branch on which to list repositories. * `--debug / --no-debug`: \[default: no-debug] * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--help`: Show this message and exit. ## `infrahubctl repository init`[​](#infrahubctl-repository-init "Direct link to infrahubctl-repository-init") Initialize a new Infrahub repository. **Usage**: ``` $ infrahubctl repository init [OPTIONS] ``` **Options**: * `--help`: Show this message and exit. --- # `infrahubctl run` Execute a script. **Usage**: ``` $ infrahubctl run [OPTIONS] SCRIPT [VARIABLES]... ``` **Arguments**: * `SCRIPT`: \[required] * `[VARIABLES]...`: Variables to pass along with the query. Format key=value key=value. **Options**: * `--method TEXT`: \[default: run] * `--debug / --no-debug`: \[default: no-debug] * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--branch TEXT`: Branch on which to run the script. * `--concurrent INTEGER`: Maximum number of requests to execute at the same time. \[env var: INFRAHUB\_MAX\_CONCURRENT\_EXECUTION] * `--timeout INTEGER`: Timeout in sec \[env var: INFRAHUB\_TIMEOUT; default: 60] * `--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. --- # `infrahubctl schema` Manage the schema in a remote Infrahub instance. **Usage**: ``` $ infrahubctl schema [OPTIONS] COMMAND [ARGS]... ``` **Options**: * `--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. **Commands**: * `load`: Load one or multiple schema files into... * `check`: Check if schema files are valid and what... * `export`: Export the schema from Infrahub as YAML... * `list`: List all available schema kinds. * `show`: Show details for a specific schema kind. ## `infrahubctl schema load`[​](#infrahubctl-schema-load "Direct link to infrahubctl-schema-load") Load one or multiple schema files into Infrahub. **Usage**: ``` $ infrahubctl schema load [OPTIONS] SCHEMAS... ``` **Arguments**: * `SCHEMAS...`: \[required] **Options**: * `--debug / --no-debug`: \[default: no-debug] * `--branch TEXT`: Branch on which to load the schema. * `--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 check`[​](#infrahubctl-schema-check "Direct link to infrahubctl-schema-check") Check if schema files are valid and what would be the impact of loading them with Infrahub. **Usage**: ``` $ infrahubctl schema check [OPTIONS] SCHEMAS... ``` **Arguments**: * `SCHEMAS...`: \[required] **Options**: * `--debug / --no-debug`: \[default: no-debug] * `--branch TEXT`: Branch on which to check the schema. * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--help`: Show this message and exit. ## `infrahubctl schema export`[​](#infrahubctl-schema-export "Direct link to infrahubctl-schema-export") Export the schema from Infrahub as YAML files, one per namespace. **Usage**: ``` $ infrahubctl schema export [OPTIONS] ``` **Options**: * `--directory PATH`: Directory path to store schema files \[default: (dynamic)] * `--branch TEXT`: Branch from which to export the schema * `--namespaces TEXT`: Namespace(s) to export (default: all user-defined) * `--debug / --no-debug`: \[default: no-debug] * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--help`: Show this message and exit. ## `infrahubctl schema list`[​](#infrahubctl-schema-list "Direct link to 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**: ``` $ 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 show`[​](#infrahubctl-schema-show "Direct link to 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**: ``` $ 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. --- # `infrahubctl task` Manage Infrahub tasks. **Usage**: ``` $ infrahubctl task [OPTIONS] COMMAND [ARGS]... ``` **Options**: * `--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. **Commands**: * `list`: List Infrahub tasks. ## `infrahubctl task list`[​](#infrahubctl-task-list "Direct link to infrahubctl-task-list") List Infrahub tasks. **Usage**: ``` $ infrahubctl task list [OPTIONS] ``` **Options**: * `-s, --state TEXT`: Filter by task state. Can be provided multiple times. * `--limit INTEGER`: Maximum number of tasks to retrieve. * `--offset INTEGER`: Offset for pagination. * `--include-related-nodes / --no-include-related-nodes`: Include related nodes in the output. \[default: no-include-related-nodes] * `--include-logs / --no-include-logs`: Include task logs in the output. \[default: no-include-logs] * `--json`: Output the result as JSON. * `--debug / --no-debug`: \[default: no-debug] * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--help`: Show this message and exit. --- # `infrahubctl telemetry` **Usage**: ``` $ infrahubctl telemetry [OPTIONS] COMMAND [ARGS]... ``` **Options**: * `--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. **Commands**: * `list`: List telemetry snapshots with summary... * `export`: Export telemetry snapshots to a JSON file. ## `infrahubctl telemetry list`[​](#infrahubctl-telemetry-list "Direct link to infrahubctl-telemetry-list") List telemetry snapshots with summary information. **Usage**: ``` $ infrahubctl telemetry list [OPTIONS] ``` **Options**: * `--start-date [%Y-%m-%d|%Y-%m-%dT%H:%M:%S|%Y-%m-%dT%H:%M:%S%z]`: Start date filter (ISO 8601) * `--end-date [%Y-%m-%d|%Y-%m-%dT%H:%M:%S|%Y-%m-%dT%H:%M:%S%z]`: End date filter (ISO 8601) * `--limit INTEGER`: Maximum number of results \[default: 50] * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--help`: Show this message and exit. ## `infrahubctl telemetry export`[​](#infrahubctl-telemetry-export "Direct link to infrahubctl-telemetry-export") Export telemetry snapshots to a JSON file. Pages through the API automatically so that all matching snapshots are exported, not just the first page. **Usage**: ``` $ infrahubctl telemetry export [OPTIONS] ``` **Options**: * `--output TEXT`: Output file path \[default: telemetry-export.json] * `--start-date [%Y-%m-%d|%Y-%m-%dT%H:%M:%S|%Y-%m-%dT%H:%M:%S%z]`: Start date filter (ISO 8601) * `--end-date [%Y-%m-%d|%Y-%m-%dT%H:%M:%S|%Y-%m-%dT%H:%M:%S%z]`: End date filter (ISO 8601) * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--help`: Show this message and exit. --- # `infrahubctl transform` Render a local transform (TransformPython) for debugging purpose. **Usage**: ``` $ infrahubctl transform [OPTIONS] [TRANSFORM_NAME] [VARIABLES]... ``` **Arguments**: * `[TRANSFORM_NAME]`: Name of the Python transformation * `[VARIABLES]...`: Variables to pass along with the query. Format key=value key=value. **Options**: * `--branch TEXT`: Branch on which to run the transformation * `--debug / --no-debug`: \[default: no-debug] * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--list`: Show available transforms * `--out TEXT`: Path to a file to save the result. * `--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. --- # `infrahubctl validate` Helper to validate the format of various files. **Usage**: ``` $ infrahubctl validate [OPTIONS] COMMAND [ARGS]... ``` **Options**: * `--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. **Commands**: * `schema`: Validate the format of a schema file... * `graphql-query`: Validate the format of a GraphQL Query... ## `infrahubctl validate schema`[​](#infrahubctl-validate-schema "Direct link to infrahubctl-validate-schema") Validate the format of a schema file either in JSON or YAML. **Usage**: ``` $ infrahubctl validate schema [OPTIONS] SCHEMA ``` **Arguments**: * `SCHEMA`: \[required] **Options**: * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--help`: Show this message and exit. ## `infrahubctl validate graphql-query`[​](#infrahubctl-validate-graphql-query "Direct link to infrahubctl-validate-graphql-query") Validate the format of a GraphQL Query stored locally by executing it on a remote GraphQL endpoint. **Usage**: ``` $ infrahubctl validate graphql-query [OPTIONS] QUERY [VARIABLES]... ``` **Arguments**: * `QUERY`: \[required] * `[VARIABLES]...`: Variables to pass along with the query. Format key=value key=value. **Options**: * `--debug / --no-debug`: Display more troubleshooting information. \[default: no-debug] * `--branch TEXT`: Branch on which to validate the GraphQL Query. * `--config-file TEXT`: \[env var: INFRAHUBCTL\_CONFIG; default: infrahubctl.toml] * `--out TEXT`: Path to a file to save the result. * `--help`: Show this message and exit. --- # `infrahubctl version` Display the version of Python and the version of the Python SDK in use. **Usage**: ``` $ infrahubctl version [OPTIONS] ``` **Options**: * `--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. --- # Infrahub Integrations Infrahub integrates with a wide range of infrastructure management and automation tools to help you synchronize data, automate workflows, and manage your infrastructure. This page provides an overview of all available integrations and their capabilities. ## Network automation and validation platforms[​](#network-automation-and-validation-platforms "Direct link to Network automation and validation platforms") ### Ansible[​](#ansible "Direct link to Ansible") The OpsMill Infrahub Ansible Collection provides modules and plugins to seamlessly interact with Infrahub through Ansible. This integration enables you to define and enforce the desired state of your infrastructure using Ansible playbooks. **Key capabilities:** * Dynamic inventory from Infrahub GraphQL data * Node creation, updates, and deletion * Branch management and manipulation * GraphQL queries and lookup operations * Artifact retrieval and management [Ansible Documentation/ansible](/ansible.md) ### Kriten[​](#kriten "Direct link to Kriten") Kriten is an API-first automation engine that transforms containerized scripts into secure REST API endpoints. The integration with Infrahub creates a powerful automation pipeline that separates data modeling from task execution, enabling secure cross-domain automation. **Key capabilities:** * Webhook-triggered automation from Infrahub events * Automatic API generation from containerized scripts * Enterprise-grade security with RBAC and audit trails * Support for synchronous and asynchronous job execution * Multi-language script support for diverse automation needs [Infrahub-Kriten Integrationhttps://opsmill.com/blog/infrahub-kriten-integration/](https://opsmill.com/blog/infrahub-kriten-integration/) ### Netpicker[​](#netpicker "Direct link to Netpicker") Netpicker provides network testing and compliance validation capabilities that integrate with Infrahub as a source of truth. This integration enables automated compliance checking by comparing live device configurations against Infrahub data. **Key capabilities:** * Device interface compliance validation * Configuration drift detection between Infrahub and live devices * Automated assertion-based testing for network infrastructure * Integration with pytest frameworks for network testing workflows [NetPicker Infrahub Integrationhttps://github.com/netpicker/pytests-for-networking/blob/main/Integrations/Infrahub/infrahub.py](https://github.com/netpicker/pytests-for-networking/blob/main/Integrations/Infrahub/infrahub.py) ### Nornir[​](#nornir "Direct link to Nornir") A Nornir plugin that allows Infrahub to serve as an inventory source for Nornir-based network automation workflows. This integration simplifies network automation by providing structured data directly from your Infrahub instance. **Key capabilities:** * Inventory management with GraphQL-based host and group data * Artifact management for configuration templates and files * Host-specific artifact regeneration * Bulk artifact generation across multiple devices [Nornir Documentation/nornir](/nornir.md) ## Data synchronization with Infrahub Sync[​](#data-synchronization-with-infrahub-sync "Direct link to Data synchronization with Infrahub Sync") Infrahub Sync is a versatile Python package that synchronizes data between source systems and Infrahub. Built on `diffsync`, it provides flexible and efficient data synchronization across different network management platforms. [Infrahub Sync Overview/sync](/sync.md) ### Network management systems[​](#network-management-systems "Direct link to Network management systems") #### LibreNMS[​](#librenms "Direct link to LibreNMS") Integrates with the open-source LibreNMS network monitoring system to import device and monitoring data into Infrahub for centralized infrastructure management. [LibreNMS Adapter/sync/adapters/librenms](/sync/adapters/librenms.md) #### Observium[​](#observium "Direct link to Observium") Connects with Observium network monitoring platform to synchronize device discovery and performance data with Infrahub for comprehensive network visibility. [Observium Adapter/sync/adapters/observium](/sync/adapters/observium.md) ### Infrastructure documentation systems[​](#infrastructure-documentation-systems "Direct link to Infrastructure documentation systems") #### Nautobot[​](#nautobot "Direct link to Nautobot") Synchronizes data from Nautobot, an open-source network source of truth platform. Supports both Nautobot v1 and v2 data models with flexible schema mapping. **Key data synchronized:** * Device and platform information * Location and site hierarchies * Manufacturer and hardware data * Tags and custom attributes [Nautobot Adapter/sync/adapters/nautobot](/sync/adapters/nautobot.md) #### Netbox[​](#netbox "Direct link to Netbox") Imports infrastructure data from NetBox, the popular open-source IPAM and DCIM tool, into Infrahub while maintaining data relationships and hierarchies. **Key data synchronized:** * Device inventory and rack layouts * IP address management (IPAM) data * Location hierarchies (regions, sites, racks) * Circuit and connection documentation [Netbox Adapter/sync/adapters/netbox](/sync/adapters/netbox.md) #### Peering manager[​](#peering-manager "Direct link to Peering manager") Bi-directional synchronization with Peering Manager for comprehensive BGP session and interconnection management. **Key data synchronized:** * BGP communities and routing policies * Peering session configurations * Internet exchange point (IXP) data * AS (Autonomous System) information [Peering Manager Adapter/sync/adapters/peering-manager](/sync/adapters/peering-manager.md) ### Network data collection[​](#network-data-collection "Direct link to Network data collection") #### IP Fabric[​](#ip-fabric "Direct link to IP Fabric") Synchronizes network discovery data from IP Fabric's network discovery and validation platform into Infrahub. IP Fabric provides automatic network discovery and advanced analytics through SSH/Telnet connectivity. **Key data synchronized:** * Device inventory with hardware details * Network topology and relationships * Platform and software version data [IP Fabric Adapter/sync/adapters/ipfabric](/sync/adapters/ipfabric.md) #### Slurp'it[​](#slurpit "Direct link to Slurp'it") Integrates with Slurp'it network data collection platform to import structured network configuration and operational data into Infrahub. **Key data synchronized:** * Device vendor and model information * Network configuration templates * Routing table and interface data * Custom planning and operational data [Slurp'It Adapter/sync/adapters/slurpit](/sync/adapters/slurpit.md) --- # Infrahub MCP Server Infrahub MCP Server connects AI assistants and IDE agents to [Infrahub](https://github.com/opsmill/infrahub) using the open [Model Context Protocol](https://modelcontextprotocol.io) standard — so agents can query, create, update, and propose changes to your infrastructure data through a consistent, audited interface. It works with any MCP-compatible client (Claude Desktop, VS Code, Cursor, CLI agents, and more) with no custom glue code required. All writes are branch-isolated and require human approval before merging — agents never modify your default branch directly. ## What you can do with it[​](#what-you-can-do-with-it "Direct link to What you can do with it") * **Query your infrastructure data from natural language** — ask an AI assistant to find devices, interfaces, IP addresses, or any node kind defined in your Infrahub schema, with attribute filtering and partial-match search. * **Explore your schema without leaving the conversation** — the server exposes your full schema catalog, per-kind attribute and filter maps, and the GraphQL SDL as MCP resources that agents read automatically. * **Make changes on isolated branches** — all writes happen on an auto-created session branch (`mcp/session-YYYYMMDD-`), so the default branch is never modified directly. * **Submit changes for human review** — when edits are ready, call `propose_changes` to open a Proposed Change (the Infrahub equivalent of a pull request) for approval before merging. * **Run arbitrary GraphQL queries** — for advanced use cases, execute any GraphQL query or mutation directly against the Infrahub API. ## Who this is for[​](#who-this-is-for "Direct link to Who this is for") **An infrastructure or network team already using Infrahub** that wants their AI coding assistants to understand and interact with their source of truth. You get schema-aware querying, safe branch-isolated writes, and a human-in-the-loop review path — all from your IDE or chat assistant. **A developer building AI-powered infrastructure automation** who needs a standardized interface between agents and Infrahub. The MCP server handles authentication, schema discovery, and branch management so you can focus on the agent logic. ## What's included[​](#whats-included "Direct link to What's included") * **Tools (read)** — `get_nodes` retrieves objects by kind with filters, `search_nodes` finds nodes by partial name match, `query_graphql` executes arbitrary GraphQL queries. * **Tools (write)** — `node_upsert` creates or updates nodes, `node_delete` removes nodes, `mutate_graphql` executes GraphQL mutations, `propose_changes` opens a Proposed Change for human review — all on an auto-created session branch. * **Resources** — `infrahub://schema` lists all available kinds, `infrahub://schema/{kind}` returns full attribute/filter details, `infrahub://graphql-schema` exposes the GraphQL SDL, `infrahub://branches` lists all branches. * **Prompts** — a built-in `infrahub_agent` system prompt that teaches agents the branch-per-session workflow and available tools. * **Middleware** — 17-layer production middleware stack with rate limiting, retry with exponential backoff, response caching, scope-based auth, OpenTelemetry tracing, Prometheus metrics, and read-only mode — all opt-in via environment variables. * **Infrastructure** — Dockerfile for container deployment, Docker Compose sidecar configuration, Streamable HTTP transport support, `/health` and `/metrics` endpoints. See the [Methods reference](/mcp/references/methods.md) for full details on every tool, resource, and prompt. ## Start here[​](#start-here "Direct link to Start here") * [Install the server](/mcp/getting-started/installation.md) — from clone to running in a few minutes. * [Set up authentication](/mcp/getting-started/authentication.md) — shared API token, per-client token pass-through, or OIDC. * [Your first agent run](/mcp/getting-started/first-agent-run.md) — connect Claude Desktop and ask an infrastructure question. * [Make a change through an agent](/mcp/getting-started/make-a-change.md) — walk the session-branch and `propose_changes` flow end-to-end. ## Go deeper[​](#go-deeper "Direct link to Go deeper") * **Deployment** — [Docker Compose](/mcp/guides/docker-compose.md) sidecar next to Infrahub. * **Use cases** — [Troubleshooting queries](/mcp/use-cases/troubleshooting-queries.md), [natural language to GraphQL](/mcp/use-cases/natural-language-graphql.md), [cross-system correlation](/mcp/use-cases/cross-system-correlation.md), [compliance analysis](/mcp/use-cases/compliance-analysis.md), [brownfield onboarding](/mcp/use-cases/brownfield-onboarding.md), and [safe changes via branch isolation](/mcp/use-cases/safe-changes-branch-isolation.md). * **Integrations** — [Claude Desktop](/mcp/integrations/claude-desktop.md), [Claude Code](/mcp/integrations/claude-code.md), [Cursor](/mcp/integrations/cursor.md), [VS Code](/mcp/integrations/vscode.md), [OpenAI Agents SDK](/mcp/integrations/openai-agents-sdk.md), [Claude Agent SDK](/mcp/integrations/claude-agent-sdk.md). * **Reference** — [Authentication architecture](/mcp/references/authentication.md), [configuration](/mcp/references/configuration.md), [methods](/mcp/references/methods.md). ## About Infrahub[​](#about-infrahub "Direct link to About Infrahub") [Infrahub](https://github.com/opsmill/infrahub) is an open source infrastructure data management and automation platform (AGPLv3), developed by [OpsMill](https://opsmill.com). It gives infrastructure and network teams a unified, schema-driven source of truth for all infrastructure data — devices, topology, IP space, configuration — with built-in version control, a generator framework for automation, and native integrations with Git, Ansible, Terraform, and CI/CD pipelines. --- # Set up authentication This guide walks through configuring each authentication mode. For a deeper explanation of how the auth architecture works — sequence diagrams, the two-layer model, audit logs, and branch-placeholder resolution — see [Authentication architecture](/mcp/references/authentication.md). ## Choosing a mode[​](#choosing-a-mode "Direct link to Choosing a mode") | Mode | When to use | Transport | Identity per request | | ------------------- | -------------------------------------------------------------------------------------------------------------- | ------------- | ----------------------------------- | | `none` (default) | Local development, single-user workstations, VPN-only deployments | stdio or HTTP | Shared `INFRAHUB_API_TOKEN` | | `token-passthrough` | Multi-tenant HTTP deployments where each user should act as themselves in Infrahub | HTTP only | Client sends own Infrahub API token | | `oidc` | Enterprise SSO (Google, Okta, Entra, Keycloak) with centralized identity and optional scope-based write gating | HTTP only | OIDC token validated per request | Username/password authentication is not supported — use OIDC when you need external identity. ## No authentication (default)[​](#no-authentication-default "Direct link to No authentication (default)") No configuration needed beyond Infrahub connection variables: ``` export INFRAHUB_ADDRESS=https://infrahub.example.com export INFRAHUB_API_TOKEN=your-token infrahub-mcp --transport streamable-http ``` All clients share the same Infrahub credentials. Use this for local development or behind a VPN. ## Token passthrough[​](#token-passthrough "Direct link to Token passthrough") Each client sends their own Infrahub API token. No shared server-side credentials required. ``` export INFRAHUB_ADDRESS=https://infrahub.example.com export INFRAHUB_MCP_AUTH_MODE=token-passthrough # No INFRAHUB_API_TOKEN needed — credentials come from the client infrahub-mcp --transport streamable-http ``` Clients include their token in the request header: ``` Authorization: Bearer ``` To use a different header (for example, `X-Infrahub-Token`), set: ``` export INFRAHUB_MCP_TOKEN_PASSTHROUGH_HEADER=X-Infrahub-Token ``` note Token passthrough requires Streamable HTTP transport. The server rejects `--transport stdio` at startup since stdio has no HTTP headers. For per-client `.mcp.json` configuration examples, see [Streamable HTTP with authentication](/mcp/guides/installation.md#streamable-http-with-authentication). ## Basic passthrough[​](#basic-passthrough "Direct link to Basic passthrough") Same fail-closed passthrough model as token passthrough, but clients send Infrahub username and password via the standard HTTP `Basic` scheme: ``` export INFRAHUB_ADDRESS=https://infrahub.example.com export INFRAHUB_MCP_AUTH_MODE=basic-passthrough # No INFRAHUB_USERNAME/INFRAHUB_PASSWORD needed — credentials come from the client infrahub-mcp --transport streamable-http ``` Clients include their credentials in the request header: ``` Authorization: Basic ``` The server decodes the header per request, constructs a fresh `InfrahubClient` with those credentials, and resets the per-request context after the call so credentials are never shared between requests. note Basic passthrough requires Streamable HTTP transport, same as token passthrough. To encode your credentials for the `Authorization` header: ``` echo -n "username:password" | base64 # dXNlcm5hbWU6cGFzc3dvcmQ= ``` For per-client `.mcp.json` configuration examples, see [Streamable HTTP with authentication](/mcp/guides/installation.md#streamable-http-with-authentication). ## OIDC[​](#oidc "Direct link to OIDC") ### Step 1: Register an OAuth client[​](#step-1-register-an-oauth-client "Direct link to Step 1: Register an OAuth client") In your Identity Provider, create an OAuth/OIDC client application: * **Application type**: Web application * **Redirect URI**: `/oauth/callback` (FastMCP handles this automatically) * **Scopes**: At minimum, `openid`, `email`, and `profile`. Add a custom scope (for example, `write` or `infrahub:write`) if you want to gate write access. Record the **client ID**, **client secret** (if not using PKCE), and the **OIDC discovery URL**. ### Step 2: Configure the MCP server[​](#step-2-configure-the-mcp-server "Direct link to Step 2: Configure the MCP server") ``` export INFRAHUB_MCP_AUTH_MODE=oidc export INFRAHUB_MCP_OIDC_CONFIG_URL=https://accounts.google.com/.well-known/openid-configuration export INFRAHUB_MCP_OIDC_CLIENT_ID=my-mcp-client export INFRAHUB_MCP_OIDC_BASE_URL=https://mcp.example.com # Optional — omit for PKCE flow (recommended for public clients) export INFRAHUB_MCP_OIDC_CLIENT_SECRET=my-secret ``` The server validates that all required OIDC fields are set at startup and fails fast with a clear error if anything is missing. ### Step 3: Configure write scopes (optional)[​](#step-3-configure-write-scopes-optional "Direct link to Step 3: Configure write scopes (optional)") By default, any authenticated user can call both read and write tools. To restrict write access to users with a specific scope in their token: ``` # Only users whose token contains "infrahub:write" can call write tools export INFRAHUB_MCP_AUTH_SCOPES_WRITE=infrahub:write ``` When configured, tools tagged `write` (`node_upsert`, `node_delete`, `propose_changes`, `mutate_graphql`) are: 1. **Hidden from discovery** — users without the required scope never see write tools in `tools/list`. 2. **Blocked at call time** — even if a client hardcodes a tool name, the call is rejected. Users without the write scope effectively get a read-only experience without needing `INFRAHUB_MCP_READ_ONLY=true`. ### Step 4: Verify[​](#step-4-verify "Direct link to Step 4: Verify") Start the server and confirm the OIDC configuration in the logs: ``` INFO oidc_auth enabled=true config_url=https://accounts.google.com/... client_id=my-mcp-client INFO auth_middleware enabled=true auth_mode=oidc write_scopes=['infrahub:write'] ``` ## Provider examples[​](#provider-examples "Direct link to Provider examples") ### Google[​](#google "Direct link to Google") ``` INFRAHUB_MCP_AUTH_MODE=oidc INFRAHUB_MCP_OIDC_CONFIG_URL=https://accounts.google.com/.well-known/openid-configuration INFRAHUB_MCP_OIDC_CLIENT_ID=123456789.apps.googleusercontent.com INFRAHUB_MCP_OIDC_CLIENT_SECRET=GOCSPX-... INFRAHUB_MCP_OIDC_BASE_URL=https://mcp.example.com ``` ### Okta[​](#okta "Direct link to Okta") ``` INFRAHUB_MCP_AUTH_MODE=oidc INFRAHUB_MCP_OIDC_CONFIG_URL=https://your-org.okta.com/.well-known/openid-configuration INFRAHUB_MCP_OIDC_CLIENT_ID=0oa... INFRAHUB_MCP_OIDC_BASE_URL=https://mcp.example.com INFRAHUB_MCP_AUTH_SCOPES_WRITE=infrahub:write ``` ### Microsoft Entra[​](#microsoft-entra "Direct link to Microsoft Entra") ``` INFRAHUB_MCP_AUTH_MODE=oidc INFRAHUB_MCP_OIDC_CONFIG_URL=https://login.microsoftonline.com/{tenant-id}/v2.0/.well-known/openid-configuration INFRAHUB_MCP_OIDC_CLIENT_ID=your-app-id INFRAHUB_MCP_OIDC_BASE_URL=https://mcp.example.com INFRAHUB_MCP_OIDC_AUDIENCE=api://your-app-id ``` ### Keycloak[​](#keycloak "Direct link to Keycloak") ``` INFRAHUB_MCP_AUTH_MODE=oidc INFRAHUB_MCP_OIDC_CONFIG_URL=https://keycloak.example.com/realms/infrahub/.well-known/openid-configuration INFRAHUB_MCP_OIDC_CLIENT_ID=infrahub-mcp INFRAHUB_MCP_OIDC_BASE_URL=https://mcp.example.com INFRAHUB_MCP_AUTH_SCOPES_WRITE=write ``` ## Configuration reference[​](#configuration-reference "Direct link to Configuration reference") For the complete list of environment variables, see the [Configuration reference](/mcp/references/configuration.md#authentication-and-authorization). For an architectural deep-dive on each mode, see [Authentication architecture](/mcp/references/authentication.md). | Variable | Required | Default | Description | | --------------------------------------- | ---------- | --------------- | ------------------------------------------------------------------------------ | | `INFRAHUB_MCP_AUTH_MODE` | No | `none` | Authentication mode (`none`, `oidc`, `token-passthrough`, `basic-passthrough`) | | `INFRAHUB_MCP_OIDC_CONFIG_URL` | Yes (oidc) | | OIDC discovery endpoint URL | | `INFRAHUB_MCP_OIDC_CLIENT_ID` | Yes (oidc) | | OAuth client ID registered with the IdP | | `INFRAHUB_MCP_OIDC_CLIENT_SECRET` | No | | OAuth client secret (omit for PKCE flow) | | `INFRAHUB_MCP_OIDC_BASE_URL` | Yes (oidc) | | Public URL where the MCP server is accessible | | `INFRAHUB_MCP_OIDC_AUDIENCE` | No | | Token audience claim | | `INFRAHUB_MCP_OIDC_USER_CLAIM` | No | `email` | JWT claim used for user identity | | `INFRAHUB_MCP_AUTH_SCOPES_WRITE` | No | `write` | Scopes required for write operations (comma-separated) | | `INFRAHUB_MCP_TOKEN_PASSTHROUGH_HEADER` | No | `Authorization` | HTTP header carrying the Infrahub API token (token-passthrough mode) | --- # Your first agent run This walkthrough connects an LLM client to your running Infrahub MCP server and asks it a read-only question. By the end you'll have confirmed the server is reachable, the agent can discover your schema, and the built-in `infrahub_agent` system prompt is guiding the conversation correctly. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * A running Infrahub MCP server — see [Install the Infrahub MCP server](/mcp/getting-started/installation.md). * An MCP-capable client installed. This guide uses **Claude Desktop** on stdio; the same flow works with [Claude Code](/mcp/integrations/claude-code.md), [Cursor](/mcp/integrations/cursor.md), [VS Code](/mcp/integrations/vscode.md), or any client that speaks MCP. * `INFRAHUB_ADDRESS` and `INFRAHUB_API_TOKEN` set in your environment. ## 1. Configure Claude Desktop[​](#1-configure-claude-desktop "Direct link to 1. Configure Claude Desktop") Open **Settings → Developer** in Claude Desktop, then click `Edit config` and add the server to `claude_desktop_config.json`: ``` { "mcpServers": { "infrahub_mcp": { "transport": "stdio", "command": "uv", "args": [ "run", "fastmcp", "run", "src/infrahub_mcp/server.py:mcp" ], "env": { "INFRAHUB_ADDRESS": "http://localhost:8000", "INFRAHUB_API_TOKEN": "your-api-token" } } } } ``` Save the file and restart Claude Desktop. The `infrahub_mcp` server should appear in the developer panel with a green status dot. If you're connecting to a remote server over HTTP instead, replace the `command`/`args`/`env` block with a `url` field — see the [Docker Compose guide](/mcp/guides/docker-compose.md#connect-your-mcp-client) for the HTTP transport configuration. ## 2. Load the agent prompt[​](#2-load-the-agent-prompt "Direct link to 2. Load the agent prompt") In a new Claude Desktop conversation, attach the built-in `infrahub_agent` prompt from the MCP server. The prompt teaches the model: * How to discover your schema via the `infrahub://schema` resource. * Which tools are read-only (`get_nodes`, `search_nodes`, `query_graphql`, `get_schema`) versus write (`node_upsert`, `node_delete`, `propose_changes`, `mutate_graphql`). * The session-branch workflow for any mutation. The prompt dynamically adjusts to read-only mode (`INFRAHUB_MCP_READ_ONLY=true`) and hides the write path when writes are disabled. ## 3. Ask a read-only question[​](#3-ask-a-read-only-question "Direct link to 3. Ask a read-only question") Try a prompt like: > What device kinds are defined in the schema? List the five most common ones. Behind the scenes the agent should: 1. **Read `infrahub://schema`** to list all non-internal kinds. 2. **Call `get_nodes(kind=...)`** for each candidate to sample the data. 3. **Return a natural-language summary** grounded in what's actually in your Infrahub. If your client shows tool-call traces, you should see those two MCP calls in the transcript. If the client hides them, watch the server logs — every call is logged with a request ID and latency. ## 4. Verify observability (optional)[​](#4-verify-observability-optional "Direct link to 4. Verify observability (optional)") If you're running the server with Streamable HTTP transport and enabled Prometheus metrics (`INFRAHUB_MCP_PROMETHEUS_ENABLED=true`), scrape `/metrics` after a few calls: ``` curl -s http://localhost:8001/metrics | grep infrahub_mcp_requests_total ``` You should see counters for `tools/call` and `resources/read` incrementing. See the [Configuration reference](/mcp/references/configuration.md#observability) for the full metrics list. ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") | Symptom | Likely cause | Fix | | -------------------------------------------------------------- | ---------------------------------------------------------- | --------------------------------------------------------------------------- | | Server won't start — `INFRAHUB_MCP_AUTH_MODE` validation error | Missing OIDC configuration or token-passthrough over stdio | Use `auth_mode=none` for stdio, or switch to `--transport streamable-http`. | | Client shows "server connected" but no tools | Stale `tools/list` cache | Restart the client; MCP capabilities are cached per session. | | `get_nodes` returns empty | Wrong kind casing, missing `__value` suffix in filters | Read `infrahub://schema/{kind}` first — it lists the exact filter keys. | | 401/403 from Infrahub | API token expired or lacks permissions | Regenerate the token in Infrahub → **Admin → API tokens**. | ## Next steps[​](#next-steps "Direct link to Next steps") * [Make a change through an agent](/mcp/getting-started/make-a-change.md) — walk the write path end-to-end. * [Use cases](/mcp/use-cases/troubleshooting-queries.md) — recipe-style scenarios for day-to-day work. * [Integrations](/mcp/integrations/claude-desktop.md) — per-client configuration details. --- # Install the Infrahub MCP server This guide gets you from a fresh checkout to a running Infrahub MCP server in a few minutes. Once it's running, continue with [Authentication](/mcp/getting-started/authentication.md) to pick a credential model, then [Your first agent run](/mcp/getting-started/first-agent-run.md) to connect an LLM. For a containerized deployment (Docker Compose, sidecar alongside Infrahub), see the [Docker Compose guide](/mcp/guides/docker-compose.md). ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Python 3.13 or newer * [uv](https://docs.astral.sh/uv/) for dependency management * An Infrahub instance you can reach over HTTP * An Infrahub API token (create one from **Admin → API tokens** in the Infrahub UI) ## Install[​](#install "Direct link to Install") The recommended way is via `uvx` (no clone needed): ``` uvx infrahub-mcp --help ``` Or install into an existing environment: ``` pip install infrahub-mcp ``` To develop or contribute, clone the repository: ``` git clone https://github.com/opsmill/infrahub-mcp.git cd infrahub-mcp uv sync ``` ## Configure the connection[​](#configure-the-connection "Direct link to Configure the connection") The server reads its configuration from environment variables. At minimum, set the Infrahub URL and an API token: ``` export INFRAHUB_ADDRESS=http://localhost:8000 export INFRAHUB_API_TOKEN=06438eb2-8019-4776-878c-0941b1f1d1ec ``` For the complete list of variables (rate limiting, caching, observability, auth), see the [Configuration reference](/mcp/references/configuration.md). ## Run the server[​](#run-the-server "Direct link to Run the server") ### Stdio transport (local clients)[​](#stdio-transport-local-clients "Direct link to Stdio transport (local clients)") Stdio is the default transport for MCP clients that spawn the server as a subprocess (Claude Desktop, Claude Code with `.mcp.json`): ``` uvx infrahub-mcp ``` ### Streamable HTTP transport (remote clients, Docker)[​](#streamable-http-transport-remote-clients-docker "Direct link to Streamable HTTP transport (remote clients, Docker)") HTTP is required for token pass-through, OIDC, or any client connecting over the network: ``` uvx infrahub-mcp --transport streamable-http --host 0.0.0.0 --port 8001 ``` The server exposes three endpoints on the configured port: | Path | Description | | ---------- | ----------------------------------------------------------- | | `/mcp` | MCP Streamable HTTP transport | | `/health` | Health probe — returns `{"status": "healthy"}` or HTTP 503 | | `/metrics` | Request counts, errors, latency (JSON or Prometheus format) | ## Verify it is running[​](#verify-it-is-running "Direct link to Verify it is running") With the HTTP transport: ``` curl -s http://localhost:8001/health # {"status":"healthy"} ``` Or inspect the server logs — you should see a line confirming the Infrahub connection and the middleware stack that is active. ## Next steps[​](#next-steps "Direct link to Next steps") * [Set up authentication](/mcp/getting-started/authentication.md) — choose between a shared API token, per-client token pass-through, or OIDC. * [Your first agent run](/mcp/getting-started/first-agent-run.md) — connect Claude Desktop and ask Infrahub a read-only question end-to-end. * [Make a change through an agent](/mcp/getting-started/make-a-change.md) — walk the session-branch + `propose_changes` workflow. --- # Make a change through an agent This walkthrough takes you through the full write path: ask an agent to update something in Infrahub, watch it auto-create an isolated session branch, and open a Proposed Change for human review. No data on your default branch is modified until a human approves the merge. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * The [First agent run](/mcp/getting-started/first-agent-run.md) walkthrough is complete (server reachable, client connected, read-only calls working). * `INFRAHUB_MCP_READ_ONLY` is not set (or is `false`) — writes are enabled. * Your Infrahub API token has permission to create branches and proposed changes. ## How it works[​](#how-it-works "Direct link to How it works") Every write starts by auto-creating a **session branch** on the first mutation. The default naming pattern is: ``` mcp/session-YYYYMMDD- ``` All subsequent writes in the same MCP session go to the same branch. When the agent calls `propose_changes`, Infrahub opens a `CoreProposedChange` (a pull-request equivalent) from the session branch back to the default branch. A human reviews, approves, and merges — only then does the change land. You can customize the pattern via `INFRAHUB_MCP_BRANCH_PATTERN`. Supported placeholders: | Placeholder | Resolves to | | ----------- | -------------------------------------------------------------------------------------------------------------------------------------- | | `{date}` | Current date in `YYYYMMDD` format | | `{hex}` | Short random hex suffix for uniqueness | | `{user}` | Authenticated user identity (OIDC mode only — see [Authentication architecture](/mcp/references/authentication.md#branch-placeholder)) | If the generated branch already exists (rare, with `{hex}`), the server retries up to `INFRAHUB_MCP_MAX_BRANCH_RETRIES` times (default 5). ## 1. Ask the agent to make a change[​](#1-ask-the-agent-to-make-a-change "Direct link to 1. Ask the agent to make a change") In your MCP client, prompt something like: > Update the description of the device named `leaf-01` to `"Rack 3A, upgraded 2026-04"`. The agent should: 1. **Discover the schema** — read `infrahub://schema` and then `infrahub://schema/{kind}` for the relevant device kind to confirm the attribute name. 2. **Look up the node** — call `search_nodes(query="leaf-01", kind="...")` or `get_nodes` with a `name__value` filter to resolve the UUID or human-friendly ID. 3. **Create the session branch** — happens implicitly on the first write. You'll see it in the `infrahub://branches` resource. 4. **Call `node_upsert`** with `id` or `hfid` to update the `description` attribute. 5. **Confirm the write** — report the branch name and node ID. ## 2. Open a Proposed Change for review[​](#2-open-a-proposed-change-for-review "Direct link to 2. Open a Proposed Change for review") Follow up with: > Open a proposed change titled "Update leaf-01 description" so the team can review. The agent calls `propose_changes(title=..., description=..., destination_branch=...)`. The response includes the `CoreProposedChange` ID and the source/destination branches. The session branch stays active, so you can keep making related changes and they'll all show up in the same proposed change. ## 3. Review in Infrahub[​](#3-review-in-infrahub "Direct link to 3. Review in Infrahub") Open the Infrahub UI and navigate to **Proposed Changes**. The change you just opened will show: * The diff between the session branch and the default branch. * The agent that created the branch (in audit logs, if OIDC is enabled). * Any check results, if you have [Infrahub checks](https://docs.infrahub.app/topics/proposed-change) configured. Approve and merge when ready. Until a human approves, the default branch is untouched. ## Guardrails at a glance[​](#guardrails-at-a-glance "Direct link to Guardrails at a glance") * **Branch isolation** — every write lives on an ephemeral session branch; the default branch is never modified directly. * **Read-only kill switch** — setting `INFRAHUB_MCP_READ_ONLY=true` hides all write tools from discovery and rejects calls that hardcode them. * **Scope gating (OIDC only)** — set `INFRAHUB_MCP_AUTH_SCOPES_WRITE=infrahub:write` to require a specific OAuth scope for write tools. * **Audit logs** — every tool call is logged with a request ID, tool name, branch, and (under OIDC) the authenticated user. * **Human approval** — `propose_changes` is the only way changes reach the default branch. Agents cannot merge. ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") | Symptom | Likely cause | Fix | | --------------------------------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | | `node_upsert` fails with "kind not found" | Schema name mismatch (case, namespace) | Ask the agent to read `infrahub://schema` first and copy the exact kind name. | | Agent tries to update a relationship and fails | `node_upsert` accepts scalar attributes only | Use `mutate_graphql` with a GraphQL mutation for relationship changes. | | `propose_changes` returns an error about no changes | Session branch is empty | Make at least one successful `node_upsert` / `node_delete` / `mutate_graphql` call before proposing. | | Write tools are hidden from the agent | Server is in read-only mode, or user lacks the write scope | Check `INFRAHUB_MCP_READ_ONLY` and `INFRAHUB_MCP_AUTH_SCOPES_WRITE` on the server. | ## Next steps[​](#next-steps "Direct link to Next steps") * [Safe changes via branch isolation](/mcp/use-cases/safe-changes-branch-isolation.md) — deeper look at the branch-per-session model. * [Brownfield network onboarding](/mcp/use-cases/brownfield-onboarding.md) — use the write path to bulk-import existing state. * [Methods reference](/mcp/references/methods.md) — every tool, resource, and prompt the server exposes. --- # Running with Docker This guide covers deploying the Infrahub MCP server as a container, including running it alongside Infrahub in the same Docker Compose stack. ## Quick start with Docker Compose[​](#quick-start-with-docker-compose "Direct link to Quick start with Docker Compose") ``` docker compose up -d ``` This pulls the published image from the Opsmill registry and starts the MCP server. Configure it with a `.env` file or environment variables (see [Configuration reference](/mcp/references/configuration.md)). ## Build the image locally[​](#build-the-image-locally "Direct link to Build the image locally") ``` docker build -f development/Dockerfile -t infrahub-mcp . ``` ## Run standalone[​](#run-standalone "Direct link to Run standalone") ``` docker run -p 8001:8001 \ -e INFRAHUB_ADDRESS=http://your-infrahub-host:8000 \ -e INFRAHUB_API_TOKEN=your-token \ registry.opsmill.io/opsmill/infrahub-mcp:latest ``` The server listens on `http://localhost:8001/mcp` using the [MCP Streamable HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) transport. ## Deploy alongside Infrahub[​](#deploy-alongside-infrahub "Direct link to Deploy alongside Infrahub") The MCP server connects to Infrahub over HTTP, making it a natural sidecar in a Docker Compose stack. Add the `infrahub-mcp` service to your existing `docker-compose.yml`: ``` services: # ... your existing Infrahub services ... infrahub-mcp: image: registry.opsmill.io/opsmill/infrahub-mcp:latest ports: - "8001:8001" environment: INFRAHUB_ADDRESS: "http://infrahub-server:8000" INFRAHUB_API_TOKEN: "${INFRAHUB_API_TOKEN}" MCP_HOST: "0.0.0.0" MCP_PORT: "8001" depends_on: - infrahub-server restart: unless-stopped ``` Replace `infrahub-server` with the service name of your Infrahub instance. ## Connect your MCP client[​](#connect-your-mcp-client "Direct link to Connect your MCP client") Once the container is running, point your MCP client at the HTTP endpoint instead of running a local subprocess: * Cursor * VS Code * Claude Desktop ``` { "mcpServers": { "infrahub_mcp": { "type": "streamable-http", "url": "http://localhost:8001/mcp" } } } ``` ``` { "servers": { "infrahub_mcp": { "type": "http", "url": "http://localhost:8001/mcp" } } } ``` ``` { "mcpServers": { "infrahub_mcp": { "transport": "streamable-http", "url": "http://localhost:8001/mcp" } } } ``` ## Configuration reference[​](#configuration-reference "Direct link to Configuration reference") | Variable | Description | Default | | --------------------------------- | ---------------------------------------- | ------------ | | `INFRAHUB_ADDRESS` | URL of your Infrahub instance | **required** | | `INFRAHUB_API_TOKEN` | API token *(or use username + password)* | — | | `INFRAHUB_USERNAME` | Username for basic-auth login | — | | `INFRAHUB_PASSWORD` | Password for basic-auth login | — | | `INFRAHUB_TIMEOUT` | HTTP request timeout in seconds | `30` | | `MCP_HOST` | Bind address for the HTTP server | `0.0.0.0` | | `MCP_PORT` | Port for the HTTP server | `8001` | | `INFRAHUB_MCP_READ_ONLY` | Disable write operations | `false` | | `INFRAHUB_MCP_RATE_LIMIT_RPS` | Rate limit (requests/sec, `0` = off) | `0` | | `INFRAHUB_MCP_CACHE_ENABLED` | Enable response caching | `false` | | `INFRAHUB_MCP_PROMETHEUS_ENABLED` | Prometheus format for `/metrics` | `false` | For the full list including retry, auth, OpenTelemetry, and more, see the [Configuration reference](/mcp/references/configuration.md). ## Endpoints[​](#endpoints "Direct link to Endpoints") | Path | Method | Description | | ---------- | ------ | -------------------------------------------------------- | | `/mcp` | POST | MCP Streamable HTTP transport | | `/health` | GET | Health check for container orchestration probes | | `/metrics` | GET | Request counts, errors, and latency (JSON or Prometheus) | --- # Docker Compose deployment This guide covers running the Infrahub MCP server as a container — standalone for development, or as a sidecar alongside Infrahub in the same Docker Compose stack for production. Kubernetes/Helm deployment is planned. Until then, you can run the MCP server as a plain `Deployment` next to an Infrahub installation managed by [`opsmill/infrahub-helm`](https://github.com/opsmill/infrahub-helm). ## Build the image[​](#build-the-image "Direct link to Build the image") From the repository root: ``` docker build -t infrahub-mcp . ``` The `Dockerfile` is a two-stage build: Python 3.13 + `uv` installs dependencies into `/opt/venv`, and the runtime stage copies them into a minimal `python:3.13-slim` image running as a non-root `app` user. The container exposes port 8001 and runs `infrahub-mcp --transport streamable-http --host 0.0.0.0 --port 8001` by default. ## Run standalone[​](#run-standalone "Direct link to Run standalone") ``` docker run --rm -p 8001:8001 \ -e INFRAHUB_ADDRESS=http://your-infrahub-host:8000 \ -e INFRAHUB_API_TOKEN=your-token \ infrahub-mcp ``` The server listens on `http://localhost:8001/mcp` using the [MCP Streamable HTTP transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http). Verify it's up: ``` curl -s http://localhost:8001/health # {"status":"healthy"} ``` ## Deploy as a sidecar to Infrahub[​](#deploy-as-a-sidecar-to-infrahub "Direct link to Deploy as a sidecar to Infrahub") The repository ships a `docker-compose.yml` that uses a YAML anchor (`x-infrahub-mcp-config`) to centralize the env-var list. This is the recommended starting point for real deployments — drop it next to (or merge it into) your existing Infrahub Compose file. ``` --- x-infrahub-mcp-config: &infrahub_mcp_config INFRAHUB_ADDRESS: ${INFRAHUB_ADDRESS:-http://infrahub-server:8000} INFRAHUB_API_TOKEN: ${INFRAHUB_INITIAL_AGENT_TOKEN:-44af444d-3b26-410d-9546-b758657e026c} INFRAHUB_MCP_AUTH_MODE: ${INFRAHUB_MCP_AUTH_MODE:-none} INFRAHUB_MCP_TOKEN_PASSTHROUGH_HEADER: ${INFRAHUB_MCP_TOKEN_PASSTHROUGH_HEADER:-Authorization} INFRAHUB_MCP_READ_ONLY: ${INFRAHUB_MCP_READ_ONLY:-false} INFRAHUB_MCP_BRANCH_PATTERN: ${INFRAHUB_MCP_BRANCH_PATTERN:-mcp/session-{date}-{hex}} INFRAHUB_MCP_MAX_BRANCH_RETRIES: ${INFRAHUB_MCP_MAX_BRANCH_RETRIES:-5} INFRAHUB_MCP_LOG_LEVEL: ${INFRAHUB_MCP_LOG_LEVEL:-info} INFRAHUB_MCP_RATE_LIMIT_RPS: ${INFRAHUB_MCP_RATE_LIMIT_RPS:-0} INFRAHUB_MCP_RATE_LIMIT_BURST: ${INFRAHUB_MCP_RATE_LIMIT_BURST:-0} INFRAHUB_MCP_RETRY_MAX_ATTEMPTS: ${INFRAHUB_MCP_RETRY_MAX_ATTEMPTS:-0} INFRAHUB_MCP_RETRY_BASE_DELAY: ${INFRAHUB_MCP_RETRY_BASE_DELAY:-1.0} INFRAHUB_MCP_CACHE_ENABLED: ${INFRAHUB_MCP_CACHE_ENABLED:-false} INFRAHUB_MCP_CACHE_LIST_TTL: ${INFRAHUB_MCP_CACHE_LIST_TTL:-300} INFRAHUB_MCP_CACHE_READ_TTL: ${INFRAHUB_MCP_CACHE_READ_TTL:-3600} INFRAHUB_MCP_OTEL_ENABLED: ${INFRAHUB_MCP_OTEL_ENABLED:-false} INFRAHUB_MCP_PROMETHEUS_ENABLED: ${INFRAHUB_MCP_PROMETHEUS_ENABLED:-false} INFRAHUB_MCP_DEREFERENCE_SCHEMAS: ${INFRAHUB_MCP_DEREFERENCE_SCHEMAS:-false} INFRAHUB_MCP_PING_INTERVAL_MS: ${INFRAHUB_MCP_PING_INTERVAL_MS:-0} INFRAHUB_MCP_AUTH_SCOPES_WRITE: ${INFRAHUB_MCP_AUTH_SCOPES_WRITE:-} services: infrahub-mcp: build: context: . dockerfile: Dockerfile ports: - "8001:8001" environment: <<: [*infrahub_mcp_config] depends_on: - infrahub-server restart: unless-stopped ``` Replace `infrahub-server` with the service name of your Infrahub instance. Every variable falls back to a sensible default; override the ones you care about in a `.env` file or your deployment pipeline. ## Choose a credential model[​](#choose-a-credential-model "Direct link to Choose a credential model") The Compose file defaults to shared API-token auth (`INFRAHUB_MCP_AUTH_MODE=none`, `INFRAHUB_API_TOKEN` set from `INFRAHUB_INITIAL_AGENT_TOKEN`). Switch to token pass-through or OIDC for multi-user deployments — see [Set up authentication](/mcp/getting-started/authentication.md). ## Connect your MCP client[​](#connect-your-mcp-client "Direct link to Connect your MCP client") With the container running on `http://localhost:8001/mcp`, point your client at the HTTP endpoint instead of spawning a subprocess: * Cursor * VS Code * Claude Desktop ``` { "mcpServers": { "infrahub_mcp": { "type": "streamable-http", "url": "http://localhost:8001/mcp" } } } ``` ``` { "servers": { "infrahub_mcp": { "type": "http", "url": "http://localhost:8001/mcp" } } } ``` ``` { "mcpServers": { "infrahub_mcp": { "transport": "streamable-http", "url": "http://localhost:8001/mcp" } } } ``` For per-client setup details (prompts, tool approval, troubleshooting), see the [Integrations section](/mcp/integrations/claude-desktop.md). ## Frequently-overridden variables[​](#frequently-overridden-variables "Direct link to Frequently-overridden variables") | Variable | Description | Default | | --------------------------------- | ----------------------------------------------- | ------------ | | `INFRAHUB_ADDRESS` | URL of your Infrahub instance | **required** | | `INFRAHUB_API_TOKEN` | API token for the MCP server's Infrahub session | — | | `INFRAHUB_TIMEOUT` | HTTP request timeout in seconds | `30` | | `MCP_HOST` | Bind address for the HTTP server | `0.0.0.0` | | `MCP_PORT` | Port for the HTTP server | `8001` | | `INFRAHUB_MCP_READ_ONLY` | Disable write operations | `false` | | `INFRAHUB_MCP_RATE_LIMIT_RPS` | Rate limit (requests/sec, `0` = off) | `0` | | `INFRAHUB_MCP_CACHE_ENABLED` | Enable response caching | `false` | | `INFRAHUB_MCP_PROMETHEUS_ENABLED` | Prometheus format for `/metrics` | `false` | For the full list including retry, auth, OpenTelemetry, and more, see the [Configuration reference](/mcp/references/configuration.md). ## Endpoints[​](#endpoints "Direct link to Endpoints") | Path | Method | Description | | ---------- | ------ | -------------------------------------------------------- | | `/mcp` | POST | MCP Streamable HTTP transport | | `/health` | GET | Health check for container orchestration probes | | `/metrics` | GET | Request counts, errors, and latency (JSON or Prometheus) | ## Operational tips[​](#operational-tips "Direct link to Operational tips") * **Health probes** — `/health` returns HTTP 503 when the server can't reach Infrahub. Wire it into your orchestrator's readiness probe with a generous timeout (Infrahub can be slow to start). * **Metrics** — set `INFRAHUB_MCP_PROMETHEUS_ENABLED=true` and scrape `/metrics` into Prometheus or a Grafana Agent. The JSON format (default) is useful for ad-hoc curls; the Prometheus format is what production monitoring wants. * **Log level** — `INFRAHUB_MCP_LOG_LEVEL=debug` logs full tracebacks and per-call timing; use it when diagnosing an issue, not in steady state. * **Token pass-through requires HTTP** — the Compose deployment is HTTP-only, which is what token pass-through and OIDC require. Don't mix stdio transport into a container deployment. ## Related reading[​](#related-reading "Direct link to Related reading") * [Installation](/mcp/getting-started/installation.md) — running the server without Docker. * [Set up authentication](/mcp/getting-started/authentication.md) — choosing between shared token, pass-through, and OIDC. * [Configuration reference](/mcp/references/configuration.md) — every environment variable. --- # Installing Infrahub MCP This guide provides step-by-step instructions for installing and configuring different MCP clients to connect to the Infrahub MCP server. For container-based deployments (Docker Compose, sidecar alongside Infrahub), see the [Docker deployment guide](/mcp/guides/docker.md). ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Python 3.13+ * Access to an Infrahub instance * An API token or credentials for your Infrahub instance ## Install the Infrahub MCP server[​](#install-the-infrahub-mcp-server "Direct link to Install the Infrahub MCP server") * uvx (recommended) * pip / uv pip * Docker * From source (development) No installation needed — `uvx` runs the package directly from PyPI: ``` uvx infrahub-mcp --help ``` note The `infrahub-mcp` CLI entry point requires version 1.1.0 or later. For earlier versions, use `uvx --from infrahub-mcp fastmcp run infrahub_mcp.server:mcp` instead. ``` pip install infrahub-mcp # or uv pip install infrahub-mcp ``` ``` docker pull registry.opsmill.io/opsmill/infrahub-mcp:latest ``` See the [Docker deployment guide](/mcp/guides/docker.md) for Docker Compose and sidecar setups. ``` git clone https://github.com/opsmill/infrahub-mcp.git cd infrahub-mcp uv sync ``` ## Run the server[​](#run-the-server "Direct link to Run the server") ``` # With uvx (no install) uvx infrahub-mcp # Or if installed via pip infrahub-mcp # With explicit transport infrahub-mcp --transport streamable-http --host 0.0.0.0 --port 8001 ``` ### Configuration[​](#configuration "Direct link to Configuration") Set the following environment variables as needed: | Variable | Description | Default | | ------------------------ | ---------------------------------------- | ------------ | | `INFRAHUB_ADDRESS` | URL of your Infrahub instance | **required** | | `INFRAHUB_API_TOKEN` | API token *(or use username + password)* | — | | `MCP_HOST` | Host for the web server | `0.0.0.0` | | `MCP_PORT` | Port for the web server | `8001` | | `INFRAHUB_MCP_READ_ONLY` | Disable write operations | `false` | | `INFRAHUB_MCP_LOG_LEVEL` | Set to `debug` for verbose logging | `info` | For the full list of configuration options (rate limiting, caching, auth, observability), see the [Configuration reference](/mcp/references/configuration.md). ## Add to your MCP client[​](#add-to-your-mcp-client "Direct link to Add to your MCP client") * Cursor * VS Code * Claude Desktop * Claude Code Go to **Settings > Cursor Settings > Tools & Integrations**, click **Add Custom MCP**, and paste the following into `mcp.json`: ``` { "mcpServers": { "infrahub_mcp": { "command": "uvx", "args": ["infrahub-mcp"], "env": { "INFRAHUB_ADDRESS": "http://localhost:8000", "INFRAHUB_API_TOKEN": "your-api-token" } } } } ``` For tool-approval tuning, workspace rules, and troubleshooting, see the [Cursor integration guide](/mcp/integrations/cursor.md). Open the MCP Servers configuration file (Cmd+Shift+P, type "MCP: Open User Configuration") and add: ``` { "servers": { "infrahub_mcp": { "type": "stdio", "command": "uvx", "args": ["infrahub-mcp"], "env": { "INFRAHUB_ADDRESS": "http://localhost:8000", "INFRAHUB_API_TOKEN": "your-api-token" } } } } ``` For Copilot compatibility (`$ref` inlining) and troubleshooting, see the [VS Code integration guide](/mcp/integrations/vscode.md). Go to **Settings > Developer**, click **Edit config** to open `claude_desktop_config.json`, and add to the `mcpServers` section: ``` { "mcpServers": { "infrahub_mcp": { "transport": "stdio", "command": "uvx", "args": ["infrahub-mcp"], "env": { "INFRAHUB_ADDRESS": "http://localhost:8000", "INFRAHUB_API_TOKEN": "your-api-token" } } } } ``` For remote server setup, prompts, and troubleshooting, see the [Claude Desktop integration guide](/mcp/integrations/claude-desktop.md). Add an `.mcp.json` file at the root of your project (Claude Code auto-detects it on startup): ``` { "mcpServers": { "infrahub": { "command": "uvx", "args": ["infrahub-mcp"], "env": { "INFRAHUB_ADDRESS": "http://localhost:8000", "INFRAHUB_API_TOKEN": "your-api-token" } } } } ``` For CLAUDE.md conventions, bundled prompts, and troubleshooting, see the [Claude Code integration guide](/mcp/integrations/claude-code.md). ## Streamable HTTP with authentication[​](#streamable-http-with-authentication "Direct link to Streamable HTTP with authentication") When the MCP server runs over Streamable HTTP with `token-passthrough` or `basic-passthrough` auth, clients connect via URL and pass credentials in the `Authorization` header instead of using stdio with env vars. See [Set up authentication](/mcp/getting-started/authentication.md) for server-side configuration. warning These configuration files contain credentials in plain text. Do not commit them to version control — add `.mcp.json` to `.gitignore`. * Claude Code * Cursor * Claude Desktop * VS Code In your `.mcp.json`: ``` { "mcpServers": { "infrahub": { "type": "http", "url": "http://localhost:8001/mcp", "headers": { "Authorization": "Bearer " } } } } ``` For basic passthrough, replace the `Authorization` value with `Basic `. In your `.cursor/mcp.json`: ``` { "mcpServers": { "infrahub_mcp": { "url": "http://localhost:8001/mcp", "headers": { "Authorization": "Bearer " } } } } ``` For basic passthrough, replace the `Authorization` value with `Basic `. In your `claude_desktop_config.json`: ``` { "mcpServers": { "infrahub_mcp": { "transport": "streamable-http", "url": "http://localhost:8001/mcp", "headers": { "Authorization": "Bearer " } } } } ``` For basic passthrough, replace the `Authorization` value with `Basic `. In your `.vscode/mcp.json`: ``` { "servers": { "infrahub_mcp": { "type": "http", "url": "http://localhost:8001/mcp", "headers": { "Authorization": "Bearer " } } } } ``` For basic passthrough, replace the `Authorization` value with `Basic `. ## Related resources[​](#related-resources "Direct link to Related resources") --- # Claude Agent SDK The [Claude Agent SDK](https://docs.claude.com/en/api/agent-sdk/overview) is Anthropic's framework for building custom agents on top of the Claude API. It has native MCP support, so connecting to the Infrahub MCP server is a short wiring exercise. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Python 3.13+ and an Anthropic API key. * The Infrahub MCP server running on HTTP (see [Docker Compose](/mcp/guides/docker-compose.md)). * `pip install claude-agent-sdk`. ## Minimal read-only agent[​](#minimal-read-only-agent "Direct link to Minimal read-only agent") ``` import anyio from claude_agent_sdk import ( ClaudeAgentOptions, ClaudeSDKClient, create_sdk_mcp_server, ) async def main() -> None: options = ClaudeAgentOptions( mcp_servers={ "infrahub": { "type": "http", "url": "http://localhost:8001/mcp", "headers": { # Token pass-through: each user forwards their own Infrahub token "Authorization": "Bearer your-infrahub-api-token", }, }, }, system_prompt=( "You are an infrastructure analyst with access to the Infrahub MCP server. " "Always read infrahub://schema before guessing kind names. Prefer read-only " "tools. For any mutation, call propose_changes at the end." ), ) async with ClaudeSDKClient(options=options) as client: await client.query( "List every schema kind whose name starts with Dcim, then use get_nodes " "(limit 3) to sample three of them." ) async for message in client.receive_response(): print(message) if __name__ == "__main__": anyio.run(main) ``` The SDK discovers `tools/list`, `resources/list`, and `prompts/list` automatically and exposes them to Claude as structured tools. ## Using the bundled `infrahub_agent` prompt[​](#using-the-bundled-infrahub_agent-prompt "Direct link to using-the-bundled-infrahub_agent-prompt") Instead of writing a system prompt, fetch the server's own `infrahub_agent` prompt: ``` async with ClaudeSDKClient(options=options) as client: prompt = await client.get_prompt("mcp__infrahub__infrahub_agent") await client.query( f"{prompt}\n\nList the five most populated device roles.", ) ``` The prompt adapts to read-only mode at registration time — if `INFRAHUB_MCP_READ_ONLY=true` on the server, the prompt omits the write workflow entirely. ## Multi-tenant: per-user tokens[​](#multi-tenant-per-user-tokens "Direct link to Multi-tenant: per-user tokens") For a service that serves many users, rebuild `ClaudeAgentOptions` per request with the user's token: ``` async def handle_user_request(user_token: str, question: str) -> str: options = ClaudeAgentOptions( mcp_servers={ "infrahub": { "type": "http", "url": "http://mcp.internal:8001/mcp", "headers": {"Authorization": f"Bearer {user_token}"}, }, }, system_prompt="Answer using Infrahub MCP tools.", ) async with ClaudeSDKClient(options=options) as client: await client.query(question) result_parts: list[str] = [] async for message in client.receive_response(): if hasattr(message, "content"): result_parts.append(str(message.content)) return "\n".join(result_parts) ``` Set `INFRAHUB_MCP_AUTH_MODE=token-passthrough` on the server (see [Authentication setup](/mcp/getting-started/authentication.md#token-passthrough)). ## Production considerations[​](#production-considerations "Direct link to Production considerations") * **Write guardrails** — set `INFRAHUB_MCP_AUTH_SCOPES_WRITE=infrahub:write` under OIDC so users need an explicit scope to mutate Infrahub. * **Audit** — the SDK captures tool-call traces; the server logs every call with request IDs. Correlate both for forensics. * **Timeouts** — tune `INFRAHUB_TIMEOUT` for slow Infrahub installations so the agent gets a clear error instead of hanging. * **Caching** — enable `INFRAHUB_MCP_CACHE_ENABLED=true` to cache schema lookups; this is a big win for agents that iterate. ## Related reading[​](#related-reading "Direct link to Related reading") * [Safe changes via branch isolation](/mcp/use-cases/safe-changes-branch-isolation.md) * [Authentication setup](/mcp/getting-started/authentication.md) * [Methods reference](/mcp/references/methods.md) --- # Claude Code [Claude Code](https://claude.com/claude-code) is Anthropic's CLI/IDE agent. It auto-detects `.mcp.json` files at project roots and loads the servers on startup — a perfect fit for the Infrahub MCP server when you're working inside a specific Infrahub consumer repository (IaC, Ansible, Python clients, etc.). ## Quick start[​](#quick-start "Direct link to Quick start") See the [Installation guide](/mcp/guides/installation.md#add-to-your-mcp-client) for the minimal Claude Code configuration snippet. This page covers Claude Code-specific workflows. ## Project-scoped setup[​](#project-scoped-setup "Direct link to Project-scoped setup") Create `.mcp.json` at the root of the repository where you want the Infrahub server available: ``` { "mcpServers": { "infrahub": { "command": "uvx", "args": ["infrahub-mcp"], "env": { "INFRAHUB_ADDRESS": "http://localhost:8000", "INFRAHUB_API_TOKEN": "your-api-token" } } } } ``` Claude Code loads the server on the next session. Run `claude` in the project directory and you should see `infrahub` listed under **/mcp**. ## User-scoped setup[​](#user-scoped-setup "Direct link to User-scoped setup") For always-on access across every project, add the same block to `~/.claude/settings.json` instead. The server is spawned once per Claude Code session. ## Using the bundled prompt[​](#using-the-bundled-prompt "Direct link to Using the bundled prompt") Invoke the `infrahub_agent` system prompt directly: ``` /infrahub_agent ``` The prompt teaches Claude the schema-discovery pattern, the session-branch write workflow, and when to use each tool. It dynamically reflects read-only mode — if the server is running with `INFRAHUB_MCP_READ_ONLY=true`, the prompt tells the model that write paths are unavailable. ## CLAUDE.md conventions[​](#claudemd-conventions "Direct link to CLAUDE.md conventions") If you're adding the MCP server to a shared repository, document the expected workflow in `CLAUDE.md` so collaborators' sessions behave consistently. Example snippet: ``` ## Infrahub MCP This repo has the Infrahub MCP server wired up via `.mcp.json`. Claude should: 1. Read `infrahub://schema` before guessing kind names. 2. Use `search_nodes` for human-entered identifiers. 3. Always call `propose_changes` after writes — never merge yourself. 4. Prefer read-only tools unless the user explicitly asks for a mutation. ``` ## Remote server[​](#remote-server "Direct link to Remote server") For a shared HTTP deployment, replace the stdio block with: ``` { "mcpServers": { "infrahub": { "transport": "streamable-http", "url": "http://mcp.internal:8001/mcp" } } } ``` Combine with [token pass-through](/mcp/getting-started/authentication.md#token-passthrough) or [OIDC](/mcp/getting-started/authentication.md#oidc) for multi-user deployments. ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") | Symptom | Fix | | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | `.mcp.json` not picked up | Claude Code requires the file at the project root. Confirm with `ls .mcp.json` in the directory where you launched Claude. | | "Unknown tool" errors | Cache stale; restart Claude Code to re-fetch `tools/list`. | | Permissions dialog on every call | Pre-approve write tools in `.claude/settings.json` or use the `/allow` command. | ## Related reading[​](#related-reading "Direct link to Related reading") * [Your first agent run](/mcp/getting-started/first-agent-run.md) * [Make a change through an agent](/mcp/getting-started/make-a-change.md) * [Methods reference](/mcp/references/methods.md) --- # Claude Desktop Claude Desktop is the default starting point for most users — it speaks MCP over stdio and can spawn the Infrahub MCP server locally without any infrastructure. ## Quick start[​](#quick-start "Direct link to Quick start") See the [Installation guide](/mcp/guides/installation.md#add-to-your-mcp-client) for the minimal Claude Desktop configuration snippet. This page covers Claude Desktop-specific details. ## Configure[​](#configure "Direct link to Configure") Open **Settings → Developer**, then click `Edit config`. Claude Desktop opens `claude_desktop_config.json`. Add an entry under `mcpServers`: ``` { "mcpServers": { "infrahub_mcp": { "transport": "stdio", "command": "uvx", "args": ["infrahub-mcp"], "env": { "INFRAHUB_ADDRESS": "http://localhost:8000", "INFRAHUB_API_TOKEN": "your-api-token" } } } } ``` Save and restart Claude Desktop. The `infrahub_mcp` server appears under **Developer → MCP servers** with a green status indicator once the handshake succeeds. ## Connect to a remote server[​](#connect-to-a-remote-server "Direct link to Connect to a remote server") If the MCP server runs in Docker or on a remote host, use the Streamable HTTP transport: ``` { "mcpServers": { "infrahub_mcp": { "transport": "streamable-http", "url": "http://mcp.example.com:8001/mcp" } } } ``` For multi-user deployments, combine this with [token pass-through](/mcp/getting-started/authentication.md#token-passthrough) — each user's Claude Desktop sends its own Infrahub token in the `Authorization` header. ## Try it[​](#try-it "Direct link to Try it") In a new conversation, attach the `infrahub_agent` prompt (available under the **Prompts** picker). Try: > List every schema kind whose name starts with `Dcim`. Or with writes enabled: > Create a new `DcimInterface` named `Ethernet99` on `spine-03` and open a proposed change for review. See [Your first agent run](/mcp/getting-started/first-agent-run.md) for the full walkthrough. ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") | Symptom | Fix | | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | Server shows red status | Check logs at **Developer → Open Logs Folder** — the server prints startup errors there. | | `command not found: uvx` | Claude Desktop doesn't inherit your shell's PATH. Use an absolute path to `uvx` in `command`. | | Connection keeps dropping | Enable [ping middleware](/mcp/references/configuration.md#client-compatibility) — set `INFRAHUB_MCP_PING_INTERVAL_MS=30000`. | | No tools appear in the UI | The server is starting but failing to register. Check `INFRAHUB_MCP_LOG_LEVEL=debug` for the full traceback. | ## Related reading[​](#related-reading "Direct link to Related reading") * [Your first agent run](/mcp/getting-started/first-agent-run.md) * [Authentication setup](/mcp/getting-started/authentication.md) --- # Cursor [Cursor](https://cursor.com) supports MCP servers via its **Tools & Integrations** panel. This page shows the Infrahub MCP server configuration and notes specific to Cursor's tool-approval UX. ## Quick start[​](#quick-start "Direct link to Quick start") See the [Installation guide](/mcp/guides/installation.md#add-to-your-mcp-client) for the minimal Cursor configuration snippet. This page covers Cursor-specific UX tuning beyond the basics. ## Configure[​](#configure "Direct link to Configure") 1. Open **Settings → Cursor Settings → Tools & Integrations**. 2. Under **MCP tools**, click **Add Custom MCP**. 3. Paste the configuration below into `mcp.json`: ``` { "mcpServers": { "infrahub_mcp": { "command": "uvx", "args": ["infrahub-mcp"], "env": { "INFRAHUB_ADDRESS": "http://localhost:8000", "INFRAHUB_API_TOKEN": "your-api-token" } } } } ``` 4. Save the file. Restart Cursor if prompted. The server appears under **MCP tools** once Cursor starts it. Click the entry to expand tool/resource details. ## Remote (HTTP) transport[​](#remote-http-transport "Direct link to Remote (HTTP) transport") For a shared MCP server deployment: ``` { "mcpServers": { "infrahub_mcp": { "type": "streamable-http", "url": "http://localhost:8001/mcp" } } } ``` ## Cursor's tool-approval model[​](#cursors-tool-approval-model "Direct link to Cursor's tool-approval model") Cursor prompts the user before every tool call by default, which is useful during onboarding but noisy for chatty read-only flows. Two ways to tune this: * **Auto-approve read tools** — in **Settings → MCP tools → infrahub\_mcp**, whitelist `get_schema`, `get_nodes`, `search_nodes`, `query_graphql`. * **Keep manual approval for writes** — leave `node_upsert`, `node_delete`, `propose_changes`, `mutate_graphql` on interactive approval so a human explicitly confirms each mutation. This matches Cursor's default for destructive actions. For a server-side hard lock, set `INFRAHUB_MCP_READ_ONLY=true` and Cursor will only ever see read tools. ## Workspace rules[​](#workspace-rules "Direct link to Workspace rules") Add a Cursor rule (`.cursor/rules/infrahub.mdc`) to bake the agent workflow into Cursor's system prompt: ``` When the Infrahub MCP server is connected: - Read infrahub://schema before guessing kind names - Use get_nodes or search_nodes before query_graphql - Always follow node_upsert / node_delete with propose_changes - Ask before calling write tools even if auto-approve is on ``` ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") | Symptom | Fix | | ----------------------------------------- | -------------------------------------------------------------------- | | Cursor shows `Failed to start MCP server` | Check Cursor's MCP log (**Output → MCP**) for the exact stderr. | | Tool descriptions look truncated | Some Cursor versions truncate MCP responses — upgrade to the latest. | | `$ref` errors in tool schemas | Enable `INFRAHUB_MCP_DEREFERENCE_SCHEMAS=true` to inline references. | ## Related reading[​](#related-reading "Direct link to Related reading") * [Your first agent run](/mcp/getting-started/first-agent-run.md) * [Configuration reference](/mcp/references/configuration.md) --- # OpenAI Agents SDK The [OpenAI Agents SDK](https://openai.github.io/openai-agents-python/) has first-class support for MCP servers. This page shows how to build a custom infrastructure-automation agent that talks to the Infrahub MCP server over Streamable HTTP. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Python 3.13+ and an OpenAI API key. * The Infrahub MCP server running on HTTP (see [Docker Compose](/mcp/guides/docker-compose.md) or the [installation guide](/mcp/getting-started/installation.md) for the `--transport streamable-http` path). * `pip install openai-agents httpx`. ## Minimal read-only agent[​](#minimal-read-only-agent "Direct link to Minimal read-only agent") ``` import asyncio from agents import Agent, Runner from agents.mcp import MCPServerStreamableHttp async def main() -> None: async with MCPServerStreamableHttp( name="infrahub", params={ "url": "http://localhost:8001/mcp", # Token pass-through: forward the user's Infrahub API token "headers": { "Authorization": "Bearer your-infrahub-api-token", }, }, ) as infrahub_server: agent = Agent( name="Infra Analyst", instructions=( "You are a network analyst with read-only access to Infrahub " "via MCP. Always read infrahub://schema before guessing kind " "names. Prefer get_nodes and search_nodes over query_graphql " "for simple lookups." ), mcp_servers=[infrahub_server], ) result = await Runner.run( agent, "List every schema kind whose name starts with Dcim, and pick three " "to sample with get_nodes (limit 3).", ) print(result.final_output) if __name__ == "__main__": asyncio.run(main()) ``` The SDK handles `tools/list`, `resources/list`, and translates MCP tool calls into the OpenAI tool-call format automatically. You get structured tool-call traces in the result object for auditing. ## Adding the built-in system prompt[​](#adding-the-built-in-system-prompt "Direct link to Adding the built-in system prompt") The server ships an `infrahub_agent` prompt that encodes the expected workflow. Fetch it and use it as your `instructions` override: ``` prompt_response = await infrahub_server.get_prompt("infrahub_agent", {}) agent = Agent( name="Infra Agent", instructions=prompt_response.messages[0].content.text, mcp_servers=[infrahub_server], ) ``` The prompt dynamically reflects the server's read-only mode. If the server runs with `INFRAHUB_MCP_READ_ONLY=true`, the prompt tells the agent that write tools are unavailable. ## Multi-tenant: per-user tokens[​](#multi-tenant-per-user-tokens "Direct link to Multi-tenant: per-user tokens") For a web app where each authenticated user has their own Infrahub API token, forward the token per request instead of hardcoding it: ``` async def handle_request(user_token: str, question: str) -> str: async with MCPServerStreamableHttp( name="infrahub", params={ "url": "http://mcp.internal:8001/mcp", "headers": {"Authorization": f"Bearer {user_token}"}, }, ) as server: agent = Agent( name="Infra Agent", instructions="Use Infrahub tools to answer the user's question.", mcp_servers=[server], ) result = await Runner.run(agent, question) return result.final_output ``` The MCP server must be configured with `INFRAHUB_MCP_AUTH_MODE=token-passthrough`. See [Authentication setup](/mcp/getting-started/authentication.md#token-passthrough). ## Production considerations[​](#production-considerations "Direct link to Production considerations") * **Rate limit** — set `INFRAHUB_MCP_RATE_LIMIT_RPS` on the server to cap per-client load. * **Retry** — enable server-side retries via `INFRAHUB_MCP_RETRY_MAX_ATTEMPTS=3` for transient network errors. * **Observability** — scrape `/metrics` (JSON or Prometheus) to track tool-call rates and error budgets. * **Audit** — with OIDC enabled, every call is logged with the authenticated user. See the [Authentication architecture](/mcp/references/authentication.md#audit-logs). ## Related reading[​](#related-reading "Direct link to Related reading") * [Docker Compose deployment](/mcp/guides/docker-compose.md) — to run the MCP server for your SDK client. * [Authentication setup](/mcp/getting-started/authentication.md) * [Methods reference](/mcp/references/methods.md) --- # VS Code VS Code supports MCP through its extension API — the Infrahub MCP server shows up as a tool provider alongside Copilot. This page covers the `code --add-mcp` setup and the VS Code Copilot compatibility flag. ## Quick start[​](#quick-start "Direct link to Quick start") See the [Installation guide](/mcp/guides/installation.md#add-to-your-mcp-client) for the minimal VS Code configuration snippet. This page covers VS Code-specific details and the Copilot compatibility flag. ## Register via CLI[​](#register-via-cli "Direct link to Register via CLI") The fastest way to register the server is with VS Code's `--add-mcp` flag: ``` code --add-mcp '{ "type": "stdio", "name": "infrahub-mcp", "description": "MCP server to interact with Infrahub", "command": "uvx", "args": ["infrahub-mcp"], "env": { "INFRAHUB_ADDRESS": "http://localhost:8000", "INFRAHUB_API_TOKEN": "placeholder-token" } }' ``` Then open the MCP user configuration (**Cmd/Ctrl+Shift+P → MCP: Open User Configuration**) and replace `placeholder-token` with your real API token. ## Remote (HTTP) transport[​](#remote-http-transport "Direct link to Remote (HTTP) transport") ``` { "servers": { "infrahub_mcp": { "type": "http", "url": "http://localhost:8001/mcp" } } } ``` ## VS Code Copilot compatibility[​](#vs-code-copilot-compatibility "Direct link to VS Code Copilot compatibility") VS Code Copilot Chat does not follow JSON Schema `$ref` references in tool definitions. If you see errors about unresolved schemas, enable `$ref` inlining on the server: ``` export INFRAHUB_MCP_DEREFERENCE_SCHEMAS=true ``` This makes every tool definition self-contained. There's no downside for clients that do handle `$ref` — the schemas just become more verbose. ## Using the tools[​](#using-the-tools "Direct link to Using the tools") Once registered, MCP tools show up alongside Copilot's native tools. In Copilot Chat, reference them like any other tool: > Use the Infrahub server to list all `DcimDevice` kinds and summarize their roles. ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") | Symptom | Fix | | ----------------------- | ------------------------------------------------------------------------------- | | MCP command not found | Update VS Code — MCP support lands in recent versions. | | `$ref` schema errors | Set `INFRAHUB_MCP_DEREFERENCE_SCHEMAS=true`. | | Server shows as stopped | Right-click the extension, choose **Start Server**, and check the Output panel. | ## Related reading[​](#related-reading "Direct link to Related reading") * [Configuration reference — Client compatibility](/mcp/references/configuration.md#client-compatibility) * [Your first agent run](/mcp/getting-started/first-agent-run.md) --- # Authentication architecture For the step-by-step configuration recipes, see [Set up authentication](/mcp/getting-started/authentication.md). This page explains *how* each mode works end-to-end. The MCP server sits between AI clients (Claude, Cursor, VS Code Copilot) and the Infrahub API. Authentication happens at two separate layers: 1. **MCP layer** — controls *who* can reach the MCP server and *what* they can do (read-only vs read-write). Configured via `INFRAHUB_MCP_AUTH_MODE`. 2. **Infrahub layer** — controls *what* the MCP server can do in Infrahub. Configured via `INFRAHUB_API_TOKEN` or `INFRAHUB_USERNAME`/`INFRAHUB_PASSWORD` (for `none` and `oidc` modes), or via the client's own credentials (for `token-passthrough` and `basic-passthrough` modes). In `none` and `oidc` modes, the Infrahub credentials are shared across all MCP sessions — they define the ceiling of what any client can do, and the MCP auth layer narrows that ceiling per-user. In `token-passthrough` and `basic-passthrough` modes, each client provides their own Infrahub credentials, so there are no shared credentials. ## Auth modes[​](#auth-modes "Direct link to Auth modes") ### `none` — shared credentials, no identity[​](#none--shared-credentials-no-identity "Direct link to none--shared-credentials-no-identity") No identity gate at the MCP level. Any client that can reach the server gets access using the shared Infrahub credentials. This is the right choice when: * You are running the server locally via **stdio** transport (single-user, no network exposure). * The server is behind a VPN or network-level access control and you trust all clients equally. * You want the simplest possible setup. No additional configuration is required beyond the standard Infrahub connection variables. ### `oidc` — identity + scopes via external IdP[​](#oidc--identity--scopes-via-external-idp "Direct link to oidc--identity--scopes-via-external-idp") External Identity Provider authentication via OpenID Connect. When enabled, the MCP server delegates authentication to a provider like Google, Okta, or Microsoft Entra using FastMCP's `OIDCProxy`. This is the right choice when: * Multiple users share a single MCP server instance over HTTP. * You need **audit trails** that identify who performed each action. * You want **role-based access control** — some users get read-only, others get read-write. * Compliance requires identity verification before infrastructure changes. OIDC mode requires the **Streamable HTTP transport** — it is not supported over stdio. ### `token-passthrough` — client's own Infrahub token[​](#token-passthrough--clients-own-infrahub-token "Direct link to token-passthrough--clients-own-infrahub-token") Each MCP client provides their own Infrahub API token in the HTTP request header. The server extracts it and creates a per-request `InfrahubClient` using that token — no shared server-side credentials needed. This is the right choice when: * Each user already has their own Infrahub API token. * You want **end-to-end credential isolation** — the server never stores or shares tokens between users. * You need a simpler setup than OIDC but still want multi-user support. * You want Infrahub's native RBAC to control what each user can do. Token passthrough is **fail-closed**: every request must carry a valid token. If the header is missing or empty, the request is rejected — there is no silent fallback to server-side credentials. Token passthrough requires the **Streamable HTTP transport** — it is not supported over stdio (no HTTP headers available). The server rejects this combination at startup. ### `basic-passthrough` — client's own Infrahub username + password[​](#basic-passthrough--clients-own-infrahub-username--password "Direct link to basic-passthrough--clients-own-infrahub-username--password") Same fail-closed per-request model as `token-passthrough`, but clients authenticate with their Infrahub username and password via the standard HTTP `Basic` scheme. Each request constructs a fresh `InfrahubClient` — credentials are never cached or shared between requests. Use this mode when clients hold Infrahub user accounts rather than API tokens. Like `token-passthrough`, this mode requires the **Streamable HTTP transport**. ## User identity and branch naming[​](#user-identity-and-branch-naming "Direct link to User identity and branch naming") When OIDC is enabled, the authenticated user identity flows into two features: ### Audit logs[​](#audit-logs "Direct link to Audit logs") Every tool call and resource read includes the user identity in structured log output: ``` INFO tool_call tool=get_nodes user=alice@example.com INFO resource_read uri=infrahub://schema user=alice@example.com ``` The claim used for identity is configurable via `INFRAHUB_MCP_OIDC_USER_CLAIM` (default: `email`). Common alternatives include `sub` (subject ID) or `preferred_username`. ### Branch placeholder[​](#branch-placeholder "Direct link to Branch placeholder") The `{user}` placeholder in `INFRAHUB_MCP_BRANCH_PATTERN` resolves to the authenticated user's identity, sanitized for git ref compatibility: ``` INFRAHUB_MCP_BRANCH_PATTERN=mcp/{user}/{date}-{hex} # Produces: mcp/alice-example.com/20260409-a1b2c3d4 ``` The sanitization follows `git check-ref-format` rules: characters in the set `[a-zA-Z0-9._/-]` (letters, digits, dot, underscore, slash, and dash) are preserved — all others are replaced with hyphens. For example, `alice@example.com` becomes `alice-example.com` because the `@` is replaced while the dot is preserved. Additionally, `..` sequences are collapsed to a single dot, `//` to a single slash, `/.` components are stripped (no component may start with a dot), and a trailing `.lock` suffix is removed. Leading/trailing dots, slashes, and hyphens are trimmed. If no user is available, `anonymous` is used. ## Read-only mode[​](#read-only-mode "Direct link to Read-only mode") Independent of auth mode, the server supports a hard read-only mode: ``` INFRAHUB_MCP_READ_ONLY=true ``` This provides two layers of protection: 1. **Registration time** — write tools are not mounted on the FastMCP server, so they do not appear in `tools/list`. 2. **Middleware time** — `ReadOnlyMiddleware` filters any tool tagged `write` from discovery and rejects calls, catching hardcoded tool names that bypass discovery. The `infrahub_agent` system prompt dynamically reflects the access mode, telling the AI agent that write operations are unavailable. Read-only mode and OIDC scope-based authorization can be combined. Use read-only mode for server-wide enforcement (for example, a monitoring-only deployment) and scopes for per-user access control. ## How auth modes interact with transports[​](#how-auth-modes-interact-with-transports "Direct link to How auth modes interact with transports") | Transport | `none` | `oidc` | `token-passthrough` | `basic-passthrough` | | ------------------- | --------------------------------------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------- | | **stdio** | Full access via shared credentials. | Not supported — no HTTP headers. | Not supported — no HTTP headers. | Not supported — no HTTP headers. | | **Streamable HTTP** | Full access via shared credentials. No identity tracking. | Full OIDC flow. Identity in audit logs. Scope-based write gating. | Per-request token. Each client uses their own Infrahub credentials. | Per-request username/password. Each client uses their own Infrahub credentials. | --- # Configuration reference The Infrahub MCP server is configured entirely through environment variables. All middleware features are **opt-in** — the server works out of the box with only the required connection variables set. ## Connection[​](#connection "Direct link to Connection") | Variable | Description | Default | | -------------------- | ----------------------------------------------------------------------------------------------- | ------------ | | `INFRAHUB_ADDRESS` | URL of your Infrahub instance | **required** | | `INFRAHUB_API_TOKEN` | API token used by the MCP server to call Infrahub (not needed in `token-passthrough` auth mode) | — | | `MCP_HOST` | Bind address for the HTTP server | `0.0.0.0` | | `MCP_PORT` | Port for the HTTP server | `8001` | Username/password authentication against Infrahub is not supported. Use [OIDC](/mcp/getting-started/authentication.md#oidc) when you need per-user identity. ## Server behavior[​](#server-behavior "Direct link to Server behavior") | Variable | Description | Default | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | | `INFRAHUB_MCP_READ_ONLY` | Disable all write operations (`true`, `1`, or `yes`) | `false` | | `INFRAHUB_MCP_BRANCH_PATTERN` | Branch naming pattern. Supports `{date}`, `{hex}`, `{user}` placeholders. Fixed names (no placeholders) must not already exist. | `mcp/session-{date}-{hex}` | | `INFRAHUB_MCP_MAX_BRANCH_RETRIES` | Max collision retries when generating branch names (1–20) | `5` | | `INFRAHUB_MCP_LOG_LEVEL` | Set to `debug` for verbose logging with tracebacks | `info` | ## Rate limiting[​](#rate-limiting "Direct link to Rate limiting") Token bucket rate limiter that protects the server from abuse. Disabled by default. | Variable | Description | Default | | ------------------------------- | -------------------------------------------------- | ------- | | `INFRAHUB_MCP_RATE_LIMIT_RPS` | Max sustained requests per second (`0` = disabled) | `0` | | `INFRAHUB_MCP_RATE_LIMIT_BURST` | Token bucket burst capacity (`0` = auto: 2x RPS) | `0` | ``` # Allow 10 requests/sec with bursts up to 20 INFRAHUB_MCP_RATE_LIMIT_RPS=10 INFRAHUB_MCP_RATE_LIMIT_BURST=20 ``` ## Retry[​](#retry "Direct link to Retry") Automatic retry with exponential backoff for transient failures (`ConnectionError`, `TimeoutError`, `OSError`). Disabled by default. | Variable | Description | Default | | --------------------------------- | ---------------------------------------- | ------- | | `INFRAHUB_MCP_RETRY_MAX_ATTEMPTS` | Max retry attempts (`0` = disabled) | `0` | | `INFRAHUB_MCP_RETRY_BASE_DELAY` | Initial delay between retries in seconds | `1.0` | ## Response caching[​](#response-caching "Direct link to Response caching") TTL-based caching for schema discovery and list operations. Reduces load on Infrahub for repeated schema lookups. | Variable | Description | Default | | ----------------------------- | -------------------------------------------------------------- | ------- | | `INFRAHUB_MCP_CACHE_ENABLED` | Enable response caching (`true`/`false`) | `false` | | `INFRAHUB_MCP_CACHE_LIST_TTL` | TTL for list operations (tools, resources, prompts) in seconds | `300` | | `INFRAHUB_MCP_CACHE_READ_TTL` | TTL for read operations (resources, tool calls) in seconds | `3600` | Only the `get_schema` tool is cached for tool calls — node queries and writes are never cached. ## Authentication and authorization[​](#authentication-and-authorization "Direct link to Authentication and authorization") The MCP server supports four authentication modes controlled by `INFRAHUB_MCP_AUTH_MODE`: ### `none` (default)[​](#none-default "Direct link to none-default") No identity gate. Any client that reaches the MCP server gets access using the shared `INFRAHUB_API_TOKEN`. Works with both stdio and HTTP transports. ### `oidc`[​](#oidc "Direct link to oidc") External Identity Provider authentication via OpenID Connect. When enabled, the MCP server uses FastMCP's OIDCProxy to authenticate users against providers like Google, Okta, or Microsoft Entra. This provides: * **Identity** — who is calling (for audit logs and `{user}` branch placeholder) * **Role gating** — two roles: read-only (read tools only) and read-write (all tools) Infrahub API calls still use the shared env var credentials. The OIDC token is for MCP-level access control only. Streamable HTTP transport is required (OIDC is not supported over stdio). ### `token-passthrough`[​](#token-passthrough "Direct link to token-passthrough") Each client sends their own Infrahub API token in the HTTP request header. The server creates a per-request `InfrahubClient` using that token — no shared server-side credentials required. Every request must carry a valid token (fail-closed). Streamable HTTP transport is required. ### `basic-passthrough`[​](#basic-passthrough "Direct link to basic-passthrough") Same fail-closed per-request model as `token-passthrough`, but the client sends an `Authorization: Basic ` header. The server decodes the header and constructs a fresh `InfrahubClient` with those credentials for each request. Streamable HTTP transport is required. | Variable | Required | Default | Description | | --------------------------------------- | ---------- | --------------- | ------------------------------------------------------------------------------ | | `INFRAHUB_MCP_AUTH_MODE` | No | `none` | Authentication mode (`none`, `oidc`, `token-passthrough`, `basic-passthrough`) | | `INFRAHUB_MCP_OIDC_CONFIG_URL` | Yes (oidc) | | OIDC discovery endpoint URL | | `INFRAHUB_MCP_OIDC_CLIENT_ID` | Yes (oidc) | | OAuth client ID registered with the IdP | | `INFRAHUB_MCP_OIDC_CLIENT_SECRET` | No | | OAuth client secret (omit for PKCE flow) | | `INFRAHUB_MCP_OIDC_BASE_URL` | Yes (oidc) | | Public URL where the MCP server is accessible | | `INFRAHUB_MCP_OIDC_AUDIENCE` | No | | Token audience claim | | `INFRAHUB_MCP_OIDC_USER_CLAIM` | No | `email` | JWT claim used for user identity | | `INFRAHUB_MCP_AUTH_SCOPES_WRITE` | No | `write` | Scopes required for write operations (comma-separated) | | `INFRAHUB_MCP_TOKEN_PASSTHROUGH_HEADER` | No | `Authorization` | HTTP header carrying the Infrahub API token (token-passthrough mode) | ### Scope-based write authorization[​](#scope-based-write-authorization "Direct link to Scope-based write authorization") When `INFRAHUB_MCP_AUTH_SCOPES_WRITE` is configured (in either auth mode over HTTP transport), tools tagged `write` are filtered from discovery and blocked at call time unless the access token contains all required scopes. STDIO connections skip auth checks. ``` # Require "infrahub:write" scope for node_upsert, node_delete, etc. INFRAHUB_MCP_AUTH_SCOPES_WRITE=infrahub:write ``` ## Observability[​](#observability "Direct link to Observability") ### OpenTelemetry tracing[​](#opentelemetry-tracing "Direct link to OpenTelemetry tracing") Wraps every MCP request in an OpenTelemetry span (`mcp.`). Requires the `opentelemetry-api` package — degrades gracefully to a no-op if not installed. | Variable | Description | Default | | --------------------------- | ---------------------------- | ------- | | `INFRAHUB_MCP_OTEL_ENABLED` | Enable OpenTelemetry tracing | `false` | ### Prometheus metrics[​](#prometheus-metrics "Direct link to Prometheus metrics") The `/metrics` endpoint returns JSON by default. Enable Prometheus exposition format for scraping by Prometheus, Grafana Agent, or Datadog. | Variable | Description | Default | | --------------------------------- | --------------------------------------------- | ------- | | `INFRAHUB_MCP_PROMETHEUS_ENABLED` | Return Prometheus text format from `/metrics` | `false` | Metrics exposed: * `infrahub_mcp_requests_total{method}` — total requests by MCP method * `infrahub_mcp_errors_total{method}` — total errors by MCP method * `infrahub_mcp_latency_ms_total{method}` — cumulative latency in milliseconds The JSON format also includes `error_stats` (per-exception-type counts from error handling) and `cache` statistics when caching is enabled. ## Client compatibility[​](#client-compatibility "Direct link to Client compatibility") | Variable | Description | Default | | ---------------------------------- | ------------------------------------------------------------------------------------------------------ | ------- | | `INFRAHUB_MCP_DEREFERENCE_SCHEMAS` | Inline `$ref` in JSON schemas for clients that do not handle references (for example, VS Code Copilot) | `false` | | `INFRAHUB_MCP_PING_INTERVAL_MS` | Send periodic pings to keep HTTP connections alive, in milliseconds (`0` = disabled, max `300000`) | `0` | ## Middleware stack[​](#middleware-stack "Direct link to Middleware stack") The server applies middleware in registration order. The full stack when all features are enabled: | Layer | Middleware | Always on | | ----- | ---------------------------------------------------------------------------------------- | --------- | | 1 | **RequestIdMiddleware** — unique correlation ID per request | yes | | 2 | **MetricsMiddleware** — request/error/latency counters for `/metrics` | yes | | 3 | **OTelTracingMiddleware** — OpenTelemetry spans | no | | 4 | **ErrorHandlingMiddleware** — exception-to-MCP-error mapping with error tracking | yes | | 5 | **InfrahubConnectionMiddleware** — friendly errors for unreachable/unresponsive Infrahub | yes | | 6 | **RetryMiddleware** — exponential backoff for transient failures | no | | 7 | **RateLimitingMiddleware** — token bucket rate limiting | no | | 8 | **StructuredLoggingMiddleware** — JSON logs with token estimates | yes | | 9 | **DetailedTimingMiddleware** — per-operation timing breakdown | yes | | 10 | **AuthMiddleware** — scope-based write tool authorization (HTTP only) | no | | 11 | **TokenPassthroughMiddleware** — fail-closed gate for token/basic passthrough modes | no | | 12 | **ReadOnlyMiddleware** — tag-based write tool blocking | no | | 13 | **AuditMiddleware** — structured audit trail for tool/resource access | yes | | 14 | **ResponseCachingMiddleware** — TTL-based response caching | no | | 15 | **DereferenceRefsMiddleware** — inline `$ref` in JSON schemas | no | | 16 | **PingMiddleware** — periodic keepalive pings | no | | 17 | **ResponseLimitingMiddleware** — truncates responses over 500 KB | yes | ## Endpoints[​](#endpoints "Direct link to Endpoints") | Path | Method | Description | | ---------------- | ------ | -------------------------------------------------------------------- | | `/mcp` | POST | MCP Streamable HTTP transport | | `/health` | GET | Health check — returns `{"status": "healthy"}` or 503 | | `/metrics` | GET | Request counts, error counts, latency (JSON or Prometheus format) | | `/.well-known/*` | GET | OAuth discovery — returns RFC 6749 JSON 404 when OIDC is not enabled | --- # Infrahub MCP methods ## Response formats[​](#response-formats "Direct link to Response formats") Structured array responses (schema details, node attributes) are encoded in **TOON** (Token-Oriented Object Notation) to reduce token usage by 33–45% compared to JSON. TOON declares field names once in a header and lists rows as CSV: ``` items[N]{field1,field2,field3}: value1,value2,value3 value1,value2,value3 ``` Scalar fields use standard `key: value` notation. Flat string maps (the schema catalog, branch lists) are returned as compact JSON where TOON offers no benefit. ## Schema[​](#schema "Direct link to Schema") ### get\_schema[​](#get_schema "Direct link to get_schema") #### Capabilities[​](#capabilities "Direct link to Capabilities") * read-only * idempotent * no destroy Discover available schema kinds or retrieve the full detail for a single kind. * Without `kind`: returns the catalog of all available kinds as compact JSON. * With `kind`: returns the kind's attributes, relationships, and the full set of filter keys accepted by `get_nodes` (TOON-encoded). Prefer reading the `infrahub://schema` resource if your client supports MCP resources — this tool provides the same data for clients that don't. #### Parameters[​](#parameters "Direct link to Parameters") * **kind** (string): kind to get detail for; omit to list all kinds * **branch** (string): branch to read from; default is the server's default branch * **expand** (boolean): inline one level of each relationship's peer schema; defaults to the server's `INFRAHUB_MCP_SCHEMA_EXPAND_PEERS` setting ## Nodes[​](#nodes "Direct link to Nodes") ### get\_nodes[​](#get_nodes "Direct link to get_nodes") #### Capabilities[​](#capabilities-1 "Direct link to Capabilities") * read-only * idempotent * no destroy List nodes of a specific kind — the default read path for typed queries. Supports filtering and pagination. Use `get_schema` to discover kinds and their filter keys. #### Parameters[​](#parameters-1 "Direct link to Parameters") * **kind** (string, required): kind of the objects to retrieve * **branch** (string): branch name (default: server's default branch) * **filters** (object): attribute/relationship filter map (e.g. `{"name__value": "atl1"}` or `{"site__name__value": "atl1"}`) * **partial\_match** (boolean, default: false): enable substring matches for string filters * **include\_attributes** (boolean, default: false): return full attribute values in TOON tabular format instead of just display labels * **limit** (integer, default: 50, min: -1): maximum nodes to return; `-1` returns all results * **offset** (integer, default: 0, min: 0): number of results to skip for pagination The response dict contains `nodes`, `count`, `total_count`, `has_more`, `offset`, and `limit`. `has_more` is `null` when the total count query failed and pagination cannot be confirmed. ### search\_nodes[​](#search_nodes "Direct link to search_nodes") #### Capabilities[​](#capabilities-2 "Direct link to Capabilities") * read-only * idempotent * no destroy Find nodes by partial name. Performs substring matching against the `name` attribute only (via `name__value` with `partial_match=true`). For other attributes, or combining filters, use `get_nodes` instead. #### Parameters[​](#parameters-2 "Direct link to Parameters") * **query** (string, required): partial name to search for (minimum 1 character) * **kind** (string, required): kind to search within * **branch** (string): branch name * **limit** (integer, default: 10, range: 1–100): maximum results to return ## Session[​](#session "Direct link to Session") ### get\_session\_info[​](#get_session_info "Direct link to get_session_info") #### Capabilities[​](#capabilities-3 "Direct link to Capabilities") * read-only * idempotent * no destroy Return the current MCP session state. Reports the active session branch (if any) and the Infrahub instance address. A session branch is lazily auto-created on the first write tool call and is named `mcp/session-YYYYMMDD-`. Before the first write, `session_branch` is `null` and all read tools target the default branch. ## GraphQL[​](#graphql "Direct link to GraphQL") ### query\_graphql[​](#query_graphql "Direct link to query_graphql") #### Capabilities[​](#capabilities-4 "Direct link to Capabilities") * read-only * idempotent * no destroy Execute a read-only GraphQL query against Infrahub. Mutations are rejected at the AST level — use `mutate_graphql` instead. For straightforward attribute reads, prefer `get_nodes` or `search_nodes`. #### Parameters[​](#parameters-3 "Direct link to Parameters") * **query** (string, required): GraphQL query document (mutations rejected) * **branch** (string): branch to execute against; default is the server's default branch ## Graph traversal[​](#graph-traversal "Direct link to Graph traversal") Graph traversal walks the live data graph to find how objects connect. It requires Infrahub 1.10 or later. Node arguments — `source` (both tools) and `destination` (`find_paths` only) — accept either a node UUID or a kind-qualified human-friendly ID (for example `InfraDevice__atl1-edge1`, the form `get_nodes` returns); an unresolvable human-friendly ID returns an error pointing at `get_nodes`. ### find\_paths[​](#find_paths "Direct link to find_paths") #### Capabilities[​](#capabilities-5 "Direct link to Capabilities") * read-only * idempotent * no destroy Find the shortest path(s) between two nodes. A `count` of 0 means no path within `max_depth`. Prefer this over a hand-built deep GraphQL query when you need to know how two objects are connected. #### Parameters[​](#parameters-4 "Direct link to Parameters") * **source** (string, required): start node — UUID or kind-qualified human-friendly ID * **destination** (string, required): end node — UUID or kind-qualified human-friendly ID * **branch** (string): branch to read from; default is the server's default branch * **max\_depth** (integer): maximum relationship hops to explore * **kind\_filter** (array of strings): only traverse through nodes of these kinds * **relationship\_filter** (array of strings): only follow these schema relationship identifiers ### find\_reachable[​](#find_reachable "Direct link to find_reachable") #### Capabilities[​](#capabilities-6 "Direct link to Capabilities") * read-only * idempotent * no destroy Find nodes of the given kinds reachable from a source node — impact and dependency analysis. #### Parameters[​](#parameters-5 "Direct link to Parameters") * **source** (string, required): source node — UUID or kind-qualified human-friendly ID * **target\_kinds** (array of strings, required): node kinds to search for * **branch** (string): branch to read from; default is the server's default branch * **max\_depth** (integer): maximum traversal depth * **max\_results** (integer, default: 20): maximum distinct reachable nodes returned * **shortest\_paths\_only** (boolean, default: true): return only the shortest path to each target ## Write operations[​](#write-operations "Direct link to Write operations") Write tools target the **active session branch**, auto-created on the first write of a session (`mcp/session-YYYYMMDD-`). Use `propose_changes` to open a review once your changes are ready. Write tools are hidden when the server runs in read-only mode. The session branch is tracked per session. If it has been merged or deleted, the next write automatically recovers onto a fresh branch — no server restart is needed. Use `reset_session_branch` to reset or switch branches deliberately. ### node\_upsert[​](#node_upsert "Direct link to node_upsert") #### Capabilities[​](#capabilities-7 "Direct link to Capabilities") * write * non-idempotent * non-destructive Create or update a node on the active session branch. * **Create**: omit both `id` and `hfid`. * **Update**: supply either `id` or `hfid` to identify the target node. Only scalar attribute fields are accepted in `data`. To set relationship fields, use `mutate_graphql` with an appropriate GraphQL mutation. #### Parameters[​](#parameters-6 "Direct link to Parameters") * **kind** (string, required): kind of the node to create or update * **data** (object, required): flat `{attribute: value}` map; scalar attributes only * **id** (string): UUID of an existing node to update (update mode) * **hfid** (array of string): human-friendly ID segments of an existing node (update mode) ### node\_delete[​](#node_delete "Direct link to node_delete") #### Capabilities[​](#capabilities-8 "Direct link to Capabilities") * write * non-idempotent * destructive Delete a node on the active session branch. The deletion is applied to the session branch only and is not visible on the default branch until a proposed change is merged. #### Parameters[​](#parameters-7 "Direct link to Parameters") * **kind** (string, required): kind of the node to delete * **id** (string): UUID of the node to delete * **hfid** (array of string): human-friendly ID segments of the node to delete Provide either `id` or `hfid` — both cannot be omitted. ### propose\_changes[​](#propose_changes "Direct link to propose_changes") #### Capabilities[​](#capabilities-9 "Direct link to Capabilities") * write * non-idempotent * non-destructive Open a proposed change (pull request) from the active session branch to the default branch. Creates a `CoreProposedChange` in Infrahub so a human can review, approve, and merge the changes made during this session. The session branch remains active after calling this — you can continue making changes. #### Parameters[​](#parameters-8 "Direct link to Parameters") * **title** (string, required): title for the proposed change * **description** (string): optional description explaining the motivation * **destination\_branch** (string): branch to merge into; defaults to the instance's default branch ### mutate\_graphql[​](#mutate_graphql "Direct link to mutate_graphql") #### Capabilities[​](#capabilities-10 "Direct link to Capabilities") * write * non-idempotent * potentially destructive Execute a GraphQL mutation against Infrahub — use only for complex writes that the typed tools can't express (relationship edits, bulk operations, mutations not covered by `node_upsert` / `node_delete`). The mutation always runs on the active session branch; there is no branch override. To target a different branch deliberately, switch the session with `reset_session_branch` first. Branch-management mutations (`BranchMerge`, `BranchRebase`, `BranchDelete`, …) and schema mutations are rejected — they would bypass session isolation and the review gate. #### Parameters[​](#parameters-9 "Direct link to Parameters") * **query** (string, required): GraphQL mutation to execute on the session branch ### reset\_session\_branch[​](#reset_session_branch "Direct link to reset_session_branch") #### Capabilities[​](#capabilities-11 "Direct link to Capabilities") * write * non-idempotent * non-destructive Reset or switch the active session branch for the current session. Call with no arguments to clear the cached branch so the next write creates a fresh one (for example, after merging your work). Pass a branch name to switch this session to it — the branch is created when it does not exist and the name matches the configured branch pattern. The instance default branch and merged or read-only branches are rejected. Affects only the calling session. A merged or deleted session branch is recovered automatically on the next write; this tool is the explicit override on top of that. #### Parameters[​](#parameters-10 "Direct link to Parameters") * **branch** (string): target branch; omit to reset to a fresh auto-created branch --- ## [📄️Release 1.1.7](/mcp/release-notes/release-1_1_7.md) [Trace how two infrastructure objects are connected and find what a change could reach, with two read-only graph-traversal tools backed by Infrahub 1.10.](/mcp/release-notes/release-1_1_7.md) --- # Release 1.0.0 | Release Number | 1.0.0 | | -------------- | --------------------------------------------------------------------- | | Release Type | Feature | | Release Date | March 26th, 2026 | | Tag | [v1.0.0](https://github.com/opsmill/infrahub-mcp/releases/tag/v1.0.0) | ## Release summary[​](#release-summary "Direct link to Release summary") Your AI agents can now gather Infrahub context directly through the MCP server instead of switching between GraphQL queries, the UI, and documentation. This allows AI tools to better understand relationships between objects and safely propose changes for review. ### Query Infrahub data from MCP-compatible clients[​](#query-infrahub-data-from-mcp-compatible-clients "Direct link to Query Infrahub data from MCP-compatible clients") Infrahub data is now available to AI agents through the MCP server, making it possible to retrieve objects, search relationships, and execute GraphQL queries from MCP-compatible clients. **What changed** * Retrieve infrastructure objects by kind using `get_nodes`, with support for attribute and relationship filters, partial matching, attribute selection, and limit/offset paging to narrow large datasets. * Search for infrastructure objects by partial name using `search_nodes`, making it easier to locate objects when only part of an identifier is known. * Run GraphQL read queries using `query_graphql` for cases that require more complex data retrieval. Mutations are rejected; write mutations go through `mutate_graphql` on the session branch. * Retrieve schema using `get_schema` when working with MCP clients that do not support resources. * Check which branch a write will target using `get_session_info`, which reports the active session branch (or `none` before the first write). ### Help AI understand Infrahub relationships and schema[​](#help-ai-understand-infrahub-relationships-and-schema "Direct link to Help AI understand Infrahub relationships and schema") Schema and branch information are now exposed as MCP resources, allowing AI agents to discover available kinds, attributes, relationships, and branches directly from Infrahub. **What changed** * Explore available kinds using `infrahub://schema`, allowing AI agents to discover the Infrahub data model dynamically. * Explore attributes, relationships, and filters for a specific kind using `infrahub://schema/{kind}`, making it possible to build queries from the live schema. * Access the GraphQL SDL through `infrahub://graphql-schema` when working directly with GraphQL-aware clients. * Access branch names, descriptions, and default branch information through `infrahub://branches` to understand the current branch context. ### Review and merge changes created with AI using existing Infrahub workflows[​](#review-and-merge-changes-created-with-ai-using-existing-infrahub-workflows "Direct link to Review and merge changes created with AI using existing Infrahub workflows") Changes created with AI are kept separate from the default branch and can be explicitly submitted as a Proposed Change, allowing them to follow the same review and merge process used elsewhere in Infrahub. **What changed** * Create a session branch automatically on the first write so changes remain isolated from the default branch. * Generate branch names using `INFRAHUB_MCP_BRANCH_PATTERN` to keep branch creation consistent across sessions. * Retry branch creation automatically when naming collisions occur. * Open a Proposed Change explicitly with `propose_changes`, which records the source and destination branches so reviewers can see exactly where changes will be merged. ## Upgrade notes[​](#upgrade-notes "Direct link to Upgrade notes") ### Reading schema and branches[​](#reading-schema-and-branches "Direct link to Reading schema and branches") Schema and branch information are available as MCP resources (`infrahub://schema`, `infrahub://branches`). Use `get_schema` when working with MCP clients that do not support resources. ## Full changelog[​](#full-changelog "Direct link to Full changelog") ### Added[​](#added "Direct link to Added") * Branch support for GraphQL queries ([#32](https://github.com/opsmill/infrahub-mcp/pull/32)). ### Changed[​](#changed "Direct link to Changed") * Exposed schema and branches as MCP resources and adopted a branch-per-session write model ([#35](https://github.com/opsmill/infrahub-mcp/pull/35)). * Migrated integration tests to the Anthropic SDK. --- # Release 1.0.1 | Release Number | 1.0.1 | | -------------- | --------------------------------------------------------------------- | | Release Type | Improvement | | Release Date | April 7th, 2026 | | Tag | [v1.0.1](https://github.com/opsmill/infrahub-mcp/releases/tag/v1.0.1) | ## Release summary[​](#release-summary "Direct link to Release summary") Use AI agents to query Infrahub data without being interrupted by invalid queries. Error messages now provide enough context for AI agents to identify valid alternatives and retry automatically. ### Query Infrahub data without interrupting a conversation to correct invalid queries[​](#query-infrahub-data-without-interrupting-a-conversation-to-correct-invalid-queries "Direct link to Query Infrahub data without interrupting a conversation to correct invalid queries") Schema information is now included in common error paths, allowing AI agents to correct invalid queries and continue working without manual intervention. **What changed** * Return valid kinds when an unknown kind is referenced, making it easier to correct invalid requests. * Return valid filters for the requested kind so AI agents can build a corrected query automatically. * Include `get_schema` in remediation guidance when additional schema information is needed. * Apply the same behavior to read, write, and GraphQL operations. ## Minor changes[​](#minor-changes "Direct link to Minor changes") ### Security[​](#security "Direct link to Security") * Updated dependencies, including `ujson`, `requests`, `aiohttp`, and `fastmcp`. ## Full changelog[​](#full-changelog "Direct link to Full changelog") ### Added[​](#added "Direct link to Added") * Schema discovery hints in MCP error messages ([#54](https://github.com/opsmill/infrahub-mcp/pull/54)). --- # Release 1.1.1 | Release Number | 1.1.1 | | -------------- | --------------------------------------------------------------------- | | Release Type | Feature | | Release Date | May 4th, 2026 | | Tag | [v1.1.1](https://github.com/opsmill/infrahub-mcp/releases/tag/v1.1.1) | ## Release summary[​](#release-summary "Direct link to Release summary") Run the MCP server as a shared service instead of a local process while preserving individual identities, controlling write access, and monitoring server health through dedicated operational endpoints. This makes it practical to deploy a single MCP server for an organization instead of asking each person to install and run their own. ### Access Infrahub using individual identities[​](#access-infrahub-using-individual-identities "Direct link to Access Infrahub using individual identities") Authentication can now be passed through to Infrahub so permissions and audit history remain associated with the person making the request instead of a shared service account. **What changed** * Configure authentication using `none`, `oidc`, `token-passthrough`, or `basic-passthrough`. * Pass user tokens through `INFRAHUB_MCP_TOKEN_PASSTHROUGH_HEADER` when using token-based authentication. * Pass Infrahub credentials through HTTP basic authentication. * Apply passthrough authentication to HTTP transports. * Reject unauthenticated requests in passthrough and OIDC modes. ### Share Infrahub data without enabling writes[​](#share-infrahub-data-without-enabling-writes "Direct link to Share Infrahub data without enabling writes") Infrahub data can now be exposed in read-only mode, making it possible to investigate, report on, and correlate data across systems without changing state. **What changed** * Remove write-tagged tools by setting `INFRAHUB_MCP_READ_ONLY=true`. * Block GraphQL mutations when read-only mode is enabled. ### Monitor the MCP server like any other service[​](#monitor-the-mcp-server-like-any-other-service "Direct link to Monitor the MCP server like any other service") Health endpoints, metrics, and middleware are now available so the MCP server can be operated and observed like any other service. **What changed** * Collect request IDs, Prometheus metrics, OpenTelemetry traces, structured logs, retries, response caching, and rate limiting through middleware. * Access `/health` for readiness checks. * Access `/metrics` for Prometheus or JSON-based monitoring. * Record audit logs for write operations. ## Upgrade notes[​](#upgrade-notes "Direct link to Upgrade notes") ### Configure authentication before exposing the server over HTTP[​](#configure-authentication-before-exposing-the-server-over-http "Direct link to Configure authentication before exposing the server over HTTP") If multiple people use a shared deployment, configure `INFRAHUB_MCP_AUTH_MODE` along with token passthrough or OIDC settings before exposing the server. `stdio` deployments continue to use environment variable credentials. ## Minor changes[​](#minor-changes "Direct link to Minor changes") ### Documentation[​](#documentation "Direct link to Documentation") * Upgraded the documentation build to Docusaurus 3.10.0. ### Release pipeline[​](#release-pipeline "Direct link to Release pipeline") * Added automated, categorized release drafts. ### Security[​](#security "Direct link to Security") * Updated dependencies, including `cryptography`, `authlib`, and `python-multipart`. ## Full changelog[​](#full-changelog "Direct link to Full changelog") ### Added[​](#added "Direct link to Added") * Production middleware stack: authentication, rate limiting, observability, read-only mode, and `/health` and `/metrics` endpoints ([#62](https://github.com/opsmill/infrahub-mcp/pull/62)). ### Changed[​](#changed "Direct link to Changed") * Documentation upgraded to Docusaurus 3.10.0 ([#65](https://github.com/opsmill/infrahub-mcp/pull/65)). ### Fixed[​](#fixed "Direct link to Fixed") * Removed a broken release-drafter workflow ([#69](https://github.com/opsmill/infrahub-mcp/pull/69)). ## Notes[​](#notes "Direct link to Notes") * `v1.1.0` was prepared internally but never published; this work ships in `v1.1.1`. --- # Release 1.1.2 | Release Number | 1.1.2 | | -------------- | --------------------------------------------------------------------- | | Release Type | Maintenance | | Release Date | May 4th, 2026 | | Tag | [v1.1.2](https://github.com/opsmill/infrahub-mcp/releases/tag/v1.1.2) | ## Release summary[​](#release-summary "Direct link to Release summary") Dependency and internal tooling updates only. There are no changes to MCP tools, workflows, or runtime behavior. ## Minor changes[​](#minor-changes "Direct link to Minor changes") ### Developer experience[​](#developer-experience "Direct link to Developer experience") * Updated Speckit and related extensions. * Hardened the release and packaging pipeline. ### Security[​](#security "Direct link to Security") * Updated `urllib3` and `python-multipart`. ## Full changelog[​](#full-changelog "Direct link to Full changelog") ### Changed[​](#changed "Direct link to Changed") * Updated internal developer tooling (Speckit and extensions) and hardened the release and packaging pipeline ([#85](https://github.com/opsmill/infrahub-mcp/pull/85)). ### Security[​](#security-1 "Direct link to Security") * Grouped dependency updates ([#91](https://github.com/opsmill/infrahub-mcp/pull/91), [#86](https://github.com/opsmill/infrahub-mcp/pull/86)). --- # Release 1.1.3 | Release Number | 1.1.3 | | -------------- | --------------------------------------------------------------------- | | Release Type | Improvement | | Release Date | May 12th, 2026 | | Tag | [v1.1.3](https://github.com/opsmill/infrahub-mcp/releases/tag/v1.1.3) | ## Release summary[​](#release-summary "Direct link to Release summary") Run non-HTTP transports without additional startup configuration. This also includes pipeline and developer tooling maintenance. ### Run non-HTTP transports correctly[​](#run-non-http-transports-correctly "Direct link to Run non-HTTP transports correctly") Host and port settings are now applied only to the `streamable-http` transport, allowing other transports to start correctly without additional configuration. **What changed** * Pass host and port to the runner only when using `streamable-http`. ## Minor changes[​](#minor-changes "Direct link to Minor changes") ### Release pipeline[​](#release-pipeline "Direct link to Release pipeline") * Corrected the release auto-labeling pipeline. ### Developer experience[​](#developer-experience "Direct link to Developer experience") * Refreshed Speckit and related extensions. No runtime behavior changed. ## Full changelog[​](#full-changelog "Direct link to Full changelog") ### Fixed[​](#fixed "Direct link to Fixed") * Startup now works correctly for non-HTTP transports ([#90](https://github.com/opsmill/infrahub-mcp/pull/90)). * Corrected the release auto-labeling pipeline ([#94](https://github.com/opsmill/infrahub-mcp/pull/94)). ### Changed[​](#changed "Direct link to Changed") * Refreshed internal developer tooling (Speckit and extensions). --- # Release 1.1.4 | Release Number | 1.1.4 | | -------------- | --------------------------------------------------------------------- | | Release Type | Maintenance | | Release Date | May 12th, 2026 | | Tag | [v1.1.4](https://github.com/opsmill/infrahub-mcp/releases/tag/v1.1.4) | ## Release summary[​](#release-summary "Direct link to Release summary") Alignment with current Infrahub SDK releases, plus routine dependency maintenance. There are no changes to MCP tools, workflows, or runtime behavior. ## Minor changes[​](#minor-changes "Direct link to Minor changes") ### SDK compatibility[​](#sdk-compatibility "Direct link to SDK compatibility") * Raised the Infrahub SDK minimum version to `>=1.20.0`. * Migrated a deprecated SDK keyword argument. ### Security[​](#security "Direct link to Security") * Updated dependencies and GitHub Actions. ## Full changelog[​](#full-changelog "Direct link to Full changelog") ### Changed[​](#changed "Direct link to Changed") * Raised the Infrahub SDK minimum version to `>=1.20.0` and migrated a deprecated SDK keyword argument ([#96](https://github.com/opsmill/infrahub-mcp/pull/96)). ### Security[​](#security-1 "Direct link to Security") * Grouped dependency and GitHub Actions updates ([#98](https://github.com/opsmill/infrahub-mcp/pull/98), [#97](https://github.com/opsmill/infrahub-mcp/pull/97)). --- # Release 1.1.5 | Release Number | 1.1.5 | | -------------- | --------------------------------------------------------------------- | | Release Type | Improvement | | Release Date | May 29th, 2026 | | Tag | [v1.1.5](https://github.com/opsmill/infrahub-mcp/releases/tag/v1.1.5) | ## Release summary[​](#release-summary "Direct link to Release summary") Know immediately when a session branch can no longer be used. Branch state is validated before reuse, making long-running sessions more predictable and laying the groundwork for the automatic recovery in [1.1.6](/mcp/release-notes/release-1_1_6.md). ### Get a clear signal when a session branch can no longer be used[​](#get-a-clear-signal-when-a-session-branch-can-no-longer-be-used "Direct link to Get a clear signal when a session branch can no longer be used") Branch state is now validated before reuse, so a session whose branch has been merged or deleted fails with a clear reason instead of an opaque error. This is the groundwork for the automatic recovery in [1.1.6](/mcp/release-notes/release-1_1_6.md). **What changed** * Detect when a cached branch has been deleted. * Detect when a cached branch has already been merged or is being deleted. * Report why a branch can no longer be used before attempting additional writes. ## Minor changes[​](#minor-changes "Direct link to Minor changes") ### Documentation[​](#documentation "Direct link to Documentation") * Updated the README. ### Security[​](#security "Direct link to Security") * Updated dependencies, including `idna` and `authlib`. ## Full changelog[​](#full-changelog "Direct link to Full changelog") ### Fixed[​](#fixed "Direct link to Fixed") * Validate the cached session branch before reuse ([#110](https://github.com/opsmill/infrahub-mcp/pull/110)). ### Security[​](#security-1 "Direct link to Security") * Updated dependencies, including `idna` ([#105](https://github.com/opsmill/infrahub-mcp/pull/105)) and `authlib` ([#103](https://github.com/opsmill/infrahub-mcp/pull/103)). --- # Release 1.1.6 | Release Number | 1.1.6 | | -------------- | --------------------------------------------------------------------- | | Release Type | Feature | | Release Date | June 8th, 2026 | | Tag | [v1.1.6](https://github.com/opsmill/infrahub-mcp/releases/tag/v1.1.6) | ## Release summary[​](#release-summary "Direct link to Release summary") A session works on a single branch and opens one Proposed Change from it. When that branch is merged or is no longer available, the session recovers automatically so work can continue on a fresh branch, and you can reset or switch branches explicitly to start the next change set — all while still following existing Infrahub review workflows. ### Continue a session after its branch is merged or deleted[​](#continue-a-session-after-its-branch-is-merged-or-deleted "Direct link to Continue a session after its branch is merged or deleted") When a Proposed Change has been merged or its branch is no longer available, a replacement branch is created automatically so work can continue in the same session. **What changed** * Detect when a branch has been deleted, merged, or is in the process of being deleted. * Create a replacement branch automatically when the current branch can no longer accept changes. * Surface a retryable error when a branch changes state mid-write — the failed write is not replayed automatically (a partial mutation could apply), so the next attempt runs on a fresh branch. ### Start fresh or switch to a different branch[​](#start-fresh-or-switch-to-a-different-branch "Direct link to Start fresh or switch to a different branch") A session branch can now be reset or changed explicitly, making it easier to start over or continue work on a branch that was prepared ahead of time. **What changed** * Reset the current branch using `reset_session_branch()` so the next write starts with a new branch. * Switch to a branch by name using `reset_session_branch()` — created automatically if it does not exist and matches the configured branch pattern. * Validate branch names against the configured branch pattern. * Reject the default branch and merged or deleting branches. ### Review and merge changes created with AI using existing Infrahub workflows[​](#review-and-merge-changes-created-with-ai-using-existing-infrahub-workflows "Direct link to Review and merge changes created with AI using existing Infrahub workflows") Changes created with AI are kept separate from the default branch and can be explicitly submitted as a Proposed Change, allowing them to follow the same review and merge process used elsewhere in Infrahub. **What changed** * Always write to the active session branch. * Block branch and schema mutations, including those nested in GraphQL fragments. * Continue using `propose_changes` to submit changes for review. ## Full changelog[​](#full-changelog "Direct link to Full changelog") ### Added[​](#added "Direct link to Added") * `reset_session_branch` tool to reset or switch the session branch ([#114](https://github.com/opsmill/infrahub-mcp/pull/114)). ### Changed[​](#changed "Direct link to Changed") * `search_nodes` now uses `any__value` with `partial_match`, making substring searches consistent across concrete and abstract kinds ([#93](https://github.com/opsmill/infrahub-mcp/pull/93)). * Writes always target the active session branch, and privileged branch and schema mutations are blocked ([#114](https://github.com/opsmill/infrahub-mcp/pull/114)). ### Fixed[​](#fixed "Direct link to Fixed") * Recover stale (merged or deleted) session branches automatically ([#114](https://github.com/opsmill/infrahub-mcp/pull/114)). --- # Release 1.1.7 | Release Number | 1.1.7 | | -------------- | --------------------------------------------------------------------- | | Release Type | Feature | | Release Date | July 3rd, 2026 | | Tag | [v1.1.7](https://github.com/opsmill/infrahub-mcp/releases/tag/v1.1.7) | ## Release summary[​](#release-summary "Direct link to Release summary") Trace how two objects in your infrastructure are connected, or find everything of a given kind that a change could reach — without inspecting the schema and hand-building a deep, nested GraphQL query. Two read-only tools, `find_paths` and `find_reachable`, expose the server-side graph traversal added in Infrahub 1.10. Reading a schema also becomes more controllable, with peer-schema expansion you can turn on or off. ### Trace how objects connect and what a change can reach[​](#trace-how-objects-connect-and-what-a-change-can-reach "Direct link to Trace how objects connect and what a change can reach") Instead of studying the schema and hand-building a deep, nested GraphQL query, an agent can now ask two direct questions of the graph. Both tools build on the server-side path traversal added in Infrahub 1.10. **What changed** * `find_paths` returns the shortest path(s) between a source and a destination node, as ordered hops — answering "how are these two connected?". A `count` of `0` means no path exists within the search depth. * `find_reachable` returns the nodes of the kinds you name that are reachable from a source node — answering "what could a change here affect?". * Both accept either a UUID or the kind-qualified HFID that `get_nodes` and `search_nodes` already return (for example, `InfraDevice__atl1-edge1`), and both can be bounded by depth, node kind, or relationship. * Both are read-only and return compact TOON output. Against an Infrahub server older than 1.10, they fail with a clear version message instead of an opaque error. ### Expand related schemas only when you need them[​](#expand-related-schemas-only-when-you-need-them "Direct link to Expand related schemas only when you need them") When reading a schema, related peer schemas are expanded one level deep so an agent can see how kinds connect. That expansion is now optional, so you can keep schema responses lean when the extra detail is not needed. **What changed** * Peer-schema expansion is controlled by the new `INFRAHUB_MCP_SCHEMA_EXPAND_PEERS` setting, enabled by default so existing behavior is preserved. * `get_schema` accepts an `expand` argument to override that default for a single call. ## Minor changes[​](#minor-changes "Direct link to Minor changes") ### Testing[​](#testing "Direct link to Testing") * Added an opt-in integration test suite (`tests/integration/`, run with `pytest -m integration`) that exercises the server end to end against an ephemeral Infrahub provisioned by infrahub-testcontainers. The default `uv run pytest` remains Docker-free. ### Security[​](#security "Direct link to Security") * Resolved Python and npm dependency security alerts. ## Upgrade notes[​](#upgrade-notes "Direct link to Upgrade notes") * The graph-traversal tools require Infrahub 1.10 or later and `infrahub-sdk >= 1.22`, which is now the minimum supported version. On Infrahub servers older than 1.10, `find_paths` and `find_reachable` return a clear version error; every other tool is unaffected. ## Full changelog[​](#full-changelog "Direct link to Full changelog") ### Added[​](#added "Direct link to Added") * Graph-traversal tools `find_paths` and `find_reachable`, exposing the server-side path traversal added in Infrahub 1.10 ([#74](https://github.com/opsmill/infrahub-mcp/pull/74)). * `INFRAHUB_MCP_SCHEMA_EXPAND_PEERS` setting and a per-call `expand` argument on `get_schema` to control peer-schema expansion ([#74](https://github.com/opsmill/infrahub-mcp/pull/74)). * Opt-in integration test suite backed by infrahub-testcontainers ([#118](https://github.com/opsmill/infrahub-mcp/pull/118)). ### Changed[​](#changed "Direct link to Changed") * Raised the Infrahub SDK minimum version to `>=1.22.0` ([#74](https://github.com/opsmill/infrahub-mcp/pull/74)). * Grouped dependency and GitHub Actions updates ([#136](https://github.com/opsmill/infrahub-mcp/pull/136), [#131](https://github.com/opsmill/infrahub-mcp/pull/131)). ### Security[​](#security-1 "Direct link to Security") * Resolved Python dependency security alerts ([#133](https://github.com/opsmill/infrahub-mcp/pull/133)). * Resolved npm dependency security alerts in the documentation site ([#134](https://github.com/opsmill/infrahub-mcp/pull/134)). --- # Brownfield network onboarding Onboarding an existing ("brownfield") network into Infrahub is a long tail of small writes: one device per rack, one interface per device, one cable per interface. Agents shine here — they can read source-of-truth data from CSV exports, SNMP, LLDP neighbours, or NetBox, and turn it into idempotent Infrahub upserts on a session branch you can review all at once. ## Scenario[​](#scenario "Direct link to Scenario") A team is migrating from a spreadsheet-based inventory to Infrahub. They have a CSV with \~500 devices. They want an agent to import them safely, with a single Proposed Change at the end that a senior engineer reviews. ## The plan[​](#the-plan "Direct link to The plan") 1. Confirm the Infrahub schema covers every field in the CSV. 2. Do a dry-run import on a session branch (no merge). 3. Review the diff, iterate on any mapping issues. 4. Open a Proposed Change and hand it off for human review. 5. Merge once approved. ## 1. Align the CSV to the schema[​](#1-align-the-csv-to-the-schema "Direct link to 1. Align the CSV to the schema") Before any writes, the agent reads `infrahub://schema` and then `infrahub://schema/{kind}` for each kind it plans to populate. It maps CSV columns to Infrahub attributes and flags anything that doesn't align: | CSV column | Kind | Attribute | Notes | | ----------- | ------------ | ----------------- | ------------------------------------- | | `hostname` | `DcimDevice` | `name` | Also used as HFID segment | | `role` | `DcimDevice` | `role` | Dropdown — check allowed values | | `site_code` | `DcimDevice` | `site` (relation) | Resolve by `LocationSite.name__value` | If the schema is missing a field, that's the first write to make — via the Infrahub UI or a schema-extension proposed change — before the bulk import. ## 2. Use human-friendly IDs for idempotency[​](#2-use-human-friendly-ids-for-idempotency "Direct link to 2. Use human-friendly IDs for idempotency") Brownfield imports run multiple times as mappings get fixed. Use **human-friendly IDs (HFIDs)** rather than UUIDs so re-running the import updates existing nodes instead of creating duplicates: ``` node_upsert( kind="DcimDevice", hfid=["spine-01"], data={ "name": "spine-01", "role": "spine", "serial": "JPE12345", }, ) ``` If a `DcimDevice` with `name=spine-01` already exists on the session branch, `node_upsert` updates it. If not, it's created. Either way, running the same command twice has the same effect. `node_upsert` accepts **scalar attributes only**. For relationships (assigning a device to a site, wiring interfaces to circuits), use `mutate_graphql` with a targeted mutation. ## 3. Watch the session branch accumulate[​](#3-watch-the-session-branch-accumulate "Direct link to 3. Watch the session branch accumulate") All writes in the session go to the auto-created `mcp/session-YYYYMMDD-` branch. The default branch stays clean. You can pause mid-import, read the branches resource, and confirm what's been written: ``` resource: infrahub://branches ``` If the agent catches a mapping issue at row 200, it can course-correct — re-run the earlier writes with corrected data, and the HFID-keyed upserts deduplicate transparently. ## 4. Batch size and rate limiting[​](#4-batch-size-and-rate-limiting "Direct link to 4. Batch size and rate limiting") For a 500-device import, you'll want two operational controls: * `INFRAHUB_MCP_RATE_LIMIT_RPS=10` — cap agent throughput so it doesn't saturate Infrahub. * `INFRAHUB_MCP_RETRY_MAX_ATTEMPTS=3` and `INFRAHUB_MCP_RETRY_BASE_DELAY=1.0` — retry transient failures with exponential backoff. See the [Configuration reference](/mcp/references/configuration.md#rate-limiting) for the full list. ## 5. Propose the change[​](#5-propose-the-change "Direct link to 5. Propose the change") Once the session branch has the full import: ``` propose_changes( title="Brownfield import: 512 devices from legacy inventory", description="Automated import of devices.csv exported 2026-04-17. See attached diff.", ) ``` A senior engineer reviews the diff in Infrahub, runs any repository-defined checks, and merges. The default branch only changes once a human approves. ## 6. Iterate[​](#6-iterate "Direct link to 6. Iterate") The session branch remains active after `propose_changes`. If review turns up issues, the agent can: * Keep writing to the same branch — new changes show up in the same proposed change. * Or close the proposed change, start fresh on a new session, and redo the import. ## Patterns worth keeping[​](#patterns-worth-keeping "Direct link to Patterns worth keeping") * **HFID-keyed upserts** are the cornerstone of brownfield work: they let you re-run imports without duplicates. * **Scalar vs. relationship writes**: `node_upsert` for scalars, `mutate_graphql` for relationships. * **One proposed change per logical batch**: one for devices, another for interfaces, another for cabling — reviewers can reason about each independently. * **Dry-run with `INFRAHUB_MCP_READ_ONLY=true` first**: have the agent generate the *plan* as text, not writes, so you can sanity-check the mapping before enabling writes. ## Related reading[​](#related-reading "Direct link to Related reading") * [Safe changes via branch isolation](/mcp/use-cases/safe-changes-branch-isolation.md) — the branch-per-session model in depth. * [Make a change through an agent](/mcp/getting-started/make-a-change.md) — the same workflow for a single change. * [Methods reference — node\_upsert](/mcp/references/methods.md) — full `node_upsert` contract. --- # Compliance analysis Agents are a natural fit for ad-hoc compliance questions: "do all edge routers have dual uplinks to different spines?", "are all production VLANs documented?", "which devices are missing an owner?". This recipe shows how to run those checks as read-only agent workflows against Infrahub. ## Scenario[​](#scenario "Direct link to Scenario") A network architect asks: > Every device tagged `role=edge` must have at least two uplink interfaces, and those uplinks must connect to different spine devices. Find any violations. ## Recommended setup[​](#recommended-setup "Direct link to Recommended setup") Compliance checks are read-only by definition. Run the MCP server with writes disabled so no accidental mutation slips through: ``` export INFRAHUB_MCP_READ_ONLY=true ``` Under read-only mode, the write tools (`node_upsert`, `node_delete`, `propose_changes`, `mutate_graphql`) are hidden from discovery and rejected if hard-coded — the agent literally cannot write. If you want compliance checks to run as a specific identity (for audit logs), also enable [OIDC](/mcp/references/authentication.md#oidc--identity--scopes-via-external-idp) and give the compliance user read-only scopes. ## The flow[​](#the-flow "Direct link to The flow") ### 1. Enumerate the population[​](#1-enumerate-the-population "Direct link to 1. Enumerate the population") ``` query EdgeDevices { DcimDevice(role__value: "edge") { count edges { node { name { value } id } } } } ``` `query_graphql` returns the full list. For very large populations, iterate in batches via `get_nodes(kind=..., limit=N)`. ### 2. For each device, walk its uplinks[​](#2-for-each-device-walk-its-uplinks "Direct link to 2. For each device, walk its uplinks") The agent could make one GraphQL call that returns everything (uplinks + remote device per uplink) and then analyze client-side: ``` query EdgeUplinks { DcimDevice(role__value: "edge") { edges { node { name { value } interfaces(role__value: "uplink") { edges { node { name { value } connected_endpoint { node { device { node { name { value } role { value } } } } } } } } } } } } ``` ### 3. Score each device against the rule[​](#3-score-each-device-against-the-rule "Direct link to 3. Score each device against the rule") Per device: * Count interfaces with `role=uplink` → must be ≥ 2. * Map each uplink to its remote device → the set must have ≥ 2 distinct spines. A violation is any device that fails either condition. The agent returns a structured report: | Device | Uplinks | Unique remote spines | Status | | ------- | ------- | -------------------- | ------------------------------------ | | edge-01 | 2 | 2 | pass | | edge-02 | 1 | 1 | **fail: single uplink** | | edge-03 | 2 | 1 | **fail: both uplinks to same spine** | ### 4. Persist the report (optional)[​](#4-persist-the-report-optional "Direct link to 4. Persist the report (optional)") The agent can write the report to a file, post it to Slack, or attach it to a GitHub issue — whatever tools it already has. The Infrahub MCP server stays strictly read-only; remediation happens through a separate human-driven workflow. ## Tips for compliance agents[​](#tips-for-compliance-agents "Direct link to Tips for compliance agents") * **Cache read-only resources**: enable `INFRAHUB_MCP_CACHE_ENABLED=true` to cut schema-lookup chatter. * **Use GraphQL fragments**: when the same check runs repeatedly, define the query once and parameterize it. * **Run on a frozen branch**: pass `branch="compliance-snapshot-2026-04-17"` to make the check reproducible even as the default branch drifts. * **Rate-limit in production**: set `INFRAHUB_MCP_RATE_LIMIT_RPS` to cap the load an agent can put on Infrahub. * **Keep a spot-check human loop**: have the agent post its findings for a human reviewer before filing tickets or opening PRs. ## Related reading[​](#related-reading "Direct link to Related reading") * [Natural-language to GraphQL](/mcp/use-cases/natural-language-graphql.md) — for writing the underlying queries. * [Safe changes via branch isolation](/mcp/use-cases/safe-changes-branch-isolation.md) — if your compliance flow needs to *fix* violations. * [Configuration reference — Observability](/mcp/references/configuration.md#observability) — scrape metrics into Grafana for compliance dashboards. --- # Cross-system correlation Infrastructure data doesn't live in one place. A ticket in Jira mentions a device, a Git commit touches a configuration file, an Ansible run fails against a hostname. Connecting those dots — "which device is this about, what depends on it, what changed recently?" — is a natural fit for an agent that can read Infrahub through MCP and your other systems through their own tools. ## Scenario[​](#scenario "Direct link to Scenario") A support engineer is triaging a Jira ticket: > "SR-4812 — `spine-03` flapping interface `Ethernet1/5`, downstream services degraded." The agent's job: enrich the ticket with Infrahub context — what is `spine-03`, what connects to `Ethernet1/5`, which services depend on that link, and when did it last change. ## The flow[​](#the-flow "Direct link to The flow") ### 1. Start with the signal[​](#1-start-with-the-signal "Direct link to 1. Start with the signal") The agent already has the ticket text from the Jira MCP server (or its own Jira tool). It extracts the device name `spine-03` and the interface `Ethernet1/5`. ### 2. Resolve the device in Infrahub[​](#2-resolve-the-device-in-infrahub "Direct link to 2. Resolve the device in Infrahub") ``` search_nodes(query="spine-03", kind="DcimDevice", limit=5) ``` Once the agent has the canonical name and UUID, it fetches the full record: ``` get_nodes( kind="DcimDevice", filters={"name__value": "spine-03"}, include_attributes=True, ) ``` ### 3. Walk the relationships[​](#3-walk-the-relationships "Direct link to 3. Walk the relationships") The interesting context lives in related nodes — interfaces, connected circuits, upstream/downstream peers, the rack, the site, any services that reference this device. Use `get_nodes` with `prefetch_relationships` to pull related objects in a single call, or drop to `query_graphql` for multi-hop traversals: ``` get_nodes( kind="DcimDevice", filters={"name__value": "spine-03"}, prefetch_relationships=True, include_attributes=True, ) ``` For the specific interface: ``` query { DcimInterface( device__name__value: "spine-03", name__value: "Ethernet1/5" ) { edges { node { description { value } connected_endpoint { node { name { value } device { node { name { value } } } } } } } } } ``` ### 4. Correlate with change history[​](#4-correlate-with-change-history "Direct link to 4. Correlate with change history") Ask your Git MCP server (or a git log tool) for recent commits touching `spine-03`'s configuration. Infrahub's own branch history surfaces recently-merged proposed changes via the branches resource: ``` resource: infrahub://branches ``` ### 5. Return a triage summary[​](#5-return-a-triage-summary "Direct link to 5. Return a triage summary") The agent synthesizes: > `spine-03` is an Arista 7280 in Site AMS1, Rack 4B. Interface `Ethernet1/5` is a 100G uplink to `leaf-12 Ethernet49`. Two services depend on this link: `edge-fabric-eu` and `transit-carrier-a`. No Infrahub changes in the last 7 days; the last Git configuration push was 3 days ago by `@alice`. ## Patterns[​](#patterns "Direct link to Patterns") * **Name → ID**: always resolve human-friendly names to UUIDs before fetching related data. Use `search_nodes` for fuzzy names, `get_nodes` with exact filters for clean ones. * **Relationship names are schema-defined**: use `infrahub://schema/{kind}` to discover the exact relationship field names (`interfaces`, `connected_endpoint`, `site`, etc.) rather than guessing. * **GraphQL for multi-hop**: if you need to cross 3+ relationships in one call, write a GraphQL query via `query_graphql` — it's cheaper than chaining multiple `get_nodes` calls. * **Branch awareness**: if the ticket is about a pending change, pass `branch=...` to your queries. The same device can have different state on different branches. ## Related reading[​](#related-reading "Direct link to Related reading") * [Natural-language to GraphQL](/mcp/use-cases/natural-language-graphql.md) — for multi-hop joins. * [Compliance analysis](/mcp/use-cases/compliance-analysis.md) — for correlating across *many* devices. * [Troubleshooting data queries](/mcp/use-cases/troubleshooting-queries.md) — when the correlation returns nothing. --- # Natural language to GraphQL `get_nodes` and `search_nodes` handle most everyday queries, but some questions need the full expressive power of GraphQL: aggregates, deep joins, multi-kind filters, and computed fields. This recipe shows how an agent goes from a free-form English question to a working `query_graphql` call, using the server's own schema as the grounding. ## Scenario[​](#scenario "Direct link to Scenario") A capacity planner asks: > Show me every /24 prefix in the `EU` region that has less than 50% of its addresses allocated, sorted by available space descending. No canned tool covers that shape. The agent needs to generate a GraphQL query against Infrahub's schema. ## The flow[​](#the-flow "Direct link to The flow") ### 1. Ground the model in the real schema[​](#1-ground-the-model-in-the-real-schema "Direct link to 1. Ground the model in the real schema") Reading `infrahub://graphql-schema` returns the full GraphQL SDL for *this* Infrahub instance — including any schema extensions. This is the single source of truth the agent should consult before writing a query. For clients that don't support the `text/plain` resource type, the agent can fall back to calling `get_schema()` for the kind catalog, then `get_schema(kind="IpamIPPrefix")` (or whichever kind applies) for the full field list. ### 2. Draft the query[​](#2-draft-the-query "Direct link to 2. Draft the query") With the SDL in context, the agent drafts: ``` query PrefixCapacity($region: String!) { IpamIPPrefix( prefix_length__value: 24, region__name__value: $region ) { count edges { node { prefix { value } utilization { value } region { node { name { value } } } } } } } ``` The agent inspects the returned `utilization` values, filters client-side to those below 50%, and sorts by available space. ### 3. Execute via `query_graphql`[​](#3-execute-via-query_graphql "Direct link to 3-execute-via-query_graphql") ``` query_graphql( query="", branch=None, ) ``` Mutations are rejected by `query_graphql` — this is a read-only path. If the agent tries to mutate, it gets a clear error telling it to use `mutate_graphql` or `node_upsert` instead. ### 4. Narrate the result[​](#4-narrate-the-result "Direct link to 4. Narrate the result") The agent summarizes in natural language: "8 prefixes in EU have less than 50% utilization. The three with the most headroom are ..." ## When to use which tool[​](#when-to-use-which-tool "Direct link to When to use which tool") | Question shape | Use | | ------------------------------------------ | ---------------------------------------------- | | "Show me all X where attribute = Y" | `get_nodes` with filters | | "Find X whose name contains Y" | `search_nodes` | | "What relationships does X have?" | `get_nodes` with `prefetch_relationships=True` | | "Aggregate, join, or compute across kinds" | `query_graphql` | | "Build a custom change pipeline" | `mutate_graphql` (write) | ## The `answer_infra_question` prompt[​](#the-answer_infra_question-prompt "Direct link to the-answer_infra_question-prompt") The server ships a prompt called `answer_infra_question` that encodes this exact pipeline — discover kinds, read schema detail, pick the right tool, traverse relationships, summarize with provenance. Invoke it directly from your client when you want a canned read-only investigation flow: ``` /answer_infra_question question="How many /24s in EU have <50% utilization?" ``` ## Related reading[​](#related-reading "Direct link to Related reading") * [Troubleshooting data queries](/mcp/use-cases/troubleshooting-queries.md) — when the query structure is wrong. * [Cross-system correlation](/mcp/use-cases/cross-system-correlation.md) — combining Infrahub data with external signals. * [Methods reference](/mcp/references/methods.md#query_graphql) — the full `query_graphql` contract. --- # Safe changes via branch isolation Branch isolation is the single most important safety property of the Infrahub MCP server: **an agent can never write directly to your default branch**. Every mutation lives on an ephemeral session branch that only becomes part of the default branch once a human reviews and merges a Proposed Change. This page explains the model end-to-end and shows how to tune it. ## The lifecycle of a session branch[​](#the-lifecycle-of-a-session-branch "Direct link to The lifecycle of a session branch") Key properties: * **The branch is auto-created on the first write** — the agent never names or creates it explicitly. * **All subsequent writes in the same MCP session go to the same branch**, so a multi-step change is atomic from the reviewer's perspective. * **`propose_changes` does not close the branch** — the session stays active, so the agent can keep iterating and the new writes land in the same proposed change. * **Merging is human-only** — no MCP tool merges branches; that happens in the Infrahub UI or API, under whatever approval policy your organization enforces. ## Customizing the branch name[​](#customizing-the-branch-name "Direct link to Customizing the branch name") The default pattern is `mcp/session-{date}-{hex}`. Override it via `INFRAHUB_MCP_BRANCH_PATTERN`: ``` # Fixed branch name (must not already exist) INFRAHUB_MCP_BRANCH_PATTERN=mcp/automation # Per-user branch (requires OIDC auth mode) INFRAHUB_MCP_BRANCH_PATTERN=mcp/{user}/{date}-{hex} ``` Supported placeholders: | Placeholder | Resolves to | | ----------- | -------------------------------------------------- | | `{date}` | `YYYYMMDD` | | `{hex}` | Short random hex | | `{user}` | OIDC-authenticated user ID, sanitized for git refs | If the generated name collides, the server retries up to `INFRAHUB_MCP_MAX_BRANCH_RETRIES` times (default 5, max 20). A fixed name with no placeholders always resolves to the same branch, so startup fails if that branch already exists. See [Authentication architecture — Branch placeholder](/mcp/references/authentication.md#branch-placeholder) for how `{user}` is sanitized. ## Making a change dry-run-able[​](#making-a-change-dry-run-able "Direct link to Making a change dry-run-able") There's no explicit dry-run flag — the session branch *is* the dry run. Review it before calling `propose_changes`: 1. Agent makes writes via `node_upsert` / `node_delete` / `mutate_graphql`. 2. Open the Infrahub UI and navigate to the session branch (listed under `infrahub://branches`). 3. Review the diff. If something is wrong, just discard the branch — the default branch never saw it. 4. Re-run the agent on a fresh session. For a hard read-only posture (compliance analysis, monitoring agents), set `INFRAHUB_MCP_READ_ONLY=true` — writes are hidden from discovery and rejected if hardcoded. ## Combining with scope-based authorization[​](#combining-with-scope-based-authorization "Direct link to Combining with scope-based authorization") Under OIDC auth, you can gate write tools behind an OAuth scope: ``` INFRAHUB_MCP_AUTH_MODE=oidc INFRAHUB_MCP_AUTH_SCOPES_WRITE=infrahub:write ``` Users whose token doesn't include `infrahub:write` cannot see or call write tools. Branch isolation stops agents from damaging the default branch by accident; scope gating stops *specific users* from writing at all. ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") | Symptom | Likely cause | Fix | | -------------------------------------------- | ----------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | | "Branch already exists" error at startup | Fixed `INFRAHUB_MCP_BRANCH_PATTERN` and the branch is left over from a previous run | Merge or delete the branch in Infrahub, or use a pattern with `{hex}`. | | `propose_changes` fails with "no changes" | No successful writes on the session branch | Confirm at least one `node_upsert`/`node_delete`/`mutate_graphql` succeeded. | | Agent can't find the session branch | Branch creation failed silently | Check server logs for the branch-creation error; often a permission issue on the Infrahub API token. | | `{user}` placeholder resolves to `anonymous` | Auth mode is `none` | Enable OIDC to get a real user identity, or use a different placeholder. | ## Related reading[​](#related-reading "Direct link to Related reading") * [Make a change through an agent](/mcp/getting-started/make-a-change.md) — the first-time walkthrough. * [Brownfield network onboarding](/mcp/use-cases/brownfield-onboarding.md) — branch isolation applied to bulk imports. * [Authentication architecture](/mcp/references/authentication.md) — how identity flows into branch naming and audit logs. --- # Troubleshooting data queries When an agent answers "I couldn't find that device" — it's almost always a data-query problem, not a data problem. This recipe walks through the diagnostic flow for "why is my query returning nothing?" ## Scenario[​](#scenario "Direct link to Scenario") A network engineer asks their assistant: > What interfaces are on `leaf-01`? The agent replies "no device named `leaf-01` found," but the engineer can see it in the Infrahub UI. Something is wrong with the query, not the data. ## The diagnostic flow[​](#the-diagnostic-flow "Direct link to The diagnostic flow") ### 1. Confirm the kind exists[​](#1-confirm-the-kind-exists "Direct link to 1. Confirm the kind exists") Ask the agent to read `infrahub://schema` (or call `get_schema()` with no arguments). This returns the catalog of all non-internal schema kinds. Expected output includes entries like `DcimDevice`, `InfraDevice`, or whatever your schema defines. If `leaf-01` belongs to a kind the agent didn't try, you've already found the bug. ### 2. Inspect the kind's filters[​](#2-inspect-the-kinds-filters "Direct link to 2. Inspect the kind's filters") Read `infrahub://schema/{kind}` (or call `get_schema(kind="DcimDevice")`) to see: * Every attribute (with its type). * Every relationship. * The exact filter keys accepted by `get_nodes`. The filter keys follow a strict naming convention: | Filter type | Example | | ----------------------- | ------------------------------------ | | Attribute equality | `name__value` | | Attribute partial match | `name__value` + `partial_match=True` | | Relationship attribute | `site__name__value` | | Relationship ID | `site__id` | A common mistake: filtering on `name` instead of `name__value`. The latter is correct. ### 3. Try partial match for human-entered names[​](#3-try-partial-match-for-human-entered-names "Direct link to 3. Try partial match for human-entered names") `leaf-01` might actually be stored as `leaf-01.example.com` or `LEAF-01`. Use `search_nodes`, which is a convenience wrapper around `get_nodes` with `partial_match=True`: ``` search_nodes(query="leaf-01", kind="DcimDevice", limit=10) ``` This returns up to 10 display labels containing the substring. If it returns one match, you have your canonical name. ### 4. Traverse relationships[​](#4-traverse-relationships "Direct link to 4. Traverse relationships") If the engineer actually wants *interfaces on leaf-01*, the agent can use `get_nodes` with `prefetch_relationships` to pull the device and its interfaces in one call: ``` get_nodes( kind="DcimDevice", filters={"name__value": "leaf-01"}, prefetch_relationships=True, include_attributes=True, ) ``` Or in one GraphQL call via `query_graphql`: ``` query { DcimDevice(name__value: "leaf-01") { edges { node { interfaces { edges { node { name { value } } } } } } } } ``` ## Common filter pitfalls[​](#common-filter-pitfalls "Direct link to Common filter pitfalls") | Pitfall | Fix | | --------------------------------------------- | -------------------------------------------------------------------------- | | `filters={"name": "leaf-01"}` returns nothing | Use `name__value`, not `name` | | Case mismatch (`LEAF-01` vs `leaf-01`) | Add `partial_match=True` or use `search_nodes` | | Filtering a relationship as a string | Use the nested `relation__attribute__value` syntax | | No results because of the default limit | Pass `limit=-1` to `get_nodes` for all results | | Wrong branch | Pass `branch=` explicitly or confirm the default via `infrahub://branches` | ## Related reading[​](#related-reading "Direct link to Related reading") * [Methods reference](/mcp/references/methods.md) — the full tool and resource list. * [Natural-language to GraphQL](/mcp/use-cases/natural-language-graphql.md) — when filters aren't expressive enough. --- # Nornir Integration with Infrahub This integration bridges [Nornir's](https://github.com/nornir-automation/nornir) powerful automation framework with [Infrahub's](https://github.com/opsmill/infrahub) next-generation infrastructure data management platform. ## Why use this integration?[​](#why-use-this-integration "Direct link to Why use this integration?") Traditional network automation tools struggle with: * **Static inventory management**: Manually maintained host files that become outdated * **Configuration drift**: No systematic way to track and validate infrastructure state * **Environment isolation**: Difficulty managing different configurations across dev/staging/production * **Audit trails**: Limited visibility into who changed what and when The Nornir-Infrahub integration solves these challenges by: * **Dynamic inventory**: Automatically sync device inventory from Infrahub's graph database * **Version control**: Leverage Infrahub's Git-like branching for configuration management * **Artifact management**: Generate, validate, and deploy configurations with full traceability * **Flexible grouping**: Create dynamic device groups based on any Infrahub attribute or relationship ## Common use cases[​](#common-use-cases "Direct link to Common use cases") * **Multi-environment deployments**: Use Infrahub branches to manage configurations across development, staging, and production environments * **Compliance automation**: Generate compliance reports and configuration audits as artifacts * **Configuration validation**: Compare intended vs. actual device configurations * **Disaster recovery**: Maintain authoritative source of network configurations with full history ## Quick start[​](#quick-start "Direct link to Quick start") ### Installation[​](#installation "Direct link to Installation") ``` pip install nornir-infrahub ``` ### Next steps[​](#next-steps "Direct link to Next steps") * [Getting Started](/nornir/getting-started.md) - Set up your first Nornir-Infrahub integration * [Understanding the Integration](/nornir/topics/understanding-nornir-infrahub-integration.md) - Learn core concepts and architecture * [Your First Automation Workflow](/nornir/guides/your-first-automation-workflow.md) - Build a complete automation workflow ## Documentation[​](#documentation "Direct link to Documentation") ### Topics[​](#topics "Direct link to Topics") * [Understanding Nornir-Infrahub Integration](/nornir/topics/understanding-nornir-infrahub-integration.md) * [Infrahub Inventory Concepts](/nornir/topics/infrahub-inventory-concepts.md) * [Artifact Lifecycle Management](/nornir/topics/artifact-lifecycle-management.md) ### Guides[​](#guides "Direct link to Guides") * [Your First Automation Workflow](/nornir/guides/your-first-automation-workflow.md) * [Configuring Schema Mappings](/nornir/guides/configuring-schema-mappings.md) ### References[​](#references "Direct link to References") #### Inventory plugin[​](#inventory-plugin "Direct link to Inventory plugin") * [InfrahubInventory](/nornir/references/plugins/infrahub_inventory.md#infrahubinventory) #### Artifact management tasks[​](#artifact-management-tasks "Direct link to Artifact management tasks") * [regenerate\_host\_artifact](/nornir/references/plugins/artifact_tasks.md#regenerate_host_artifact) * [generate\_artifacts](/nornir/references/plugins/artifact_tasks.md#generate_artifacts) * [get\_artifact](/nornir/references/plugins/artifact_tasks.md#get_artifact) #### CoreFileObject management tasks[​](#corefileobject-management-tasks "Direct link to CoreFileObject management tasks") * [upload\_file\_object](/nornir/references/plugins/file_object_tasks.md#upload_file_object) * [download\_file\_object](/nornir/references/plugins/file_object_tasks.md#download_file_object) ## Additional resources[​](#additional-resources "Direct link to Additional resources") * [Blog: Simplifying Network Automation Workflows with Infrahub, Nornir, and Jinja2](https://www.opsmill.com/simplifying-network-automation-workflows-with-infrahub-nornir-and-jinja2/) * [Infrahub Documentation](https://docs.infrahub.app/) * [Nornir Documentation](https://nornir.readthedocs.io/) --- # Getting Started This guide helps you set up your first integration between Nornir and Infrahub. By the end, you'll have a working automation setup that demonstrates the power of dynamic inventory management. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before starting, ensure you have: * Python 3.10 or higher installed * An Infrahub instance running (local or remote) * You can use the [Infrahub sandbox instance](https://sandbox.infrahub.app) for testing * Basic familiarity with Nornir concepts * At least one device defined in your Infrahub schema ## Installation[​](#installation "Direct link to Installation") Install the Nornir-Infrahub plugin: * pip * uv ``` pip install nornir-infrahub nornir_netmiko ``` ``` uv add nornir-infrahub nornir_netmiko ``` This automatically installs all required dependencies including: * `nornir` - The automation framework * `infrahub-sdk` - Python SDK for Infrahub API * `nornir-utils` - Helpful utilities for Nornir ## Setting up your first integration[​](#setting-up-your-first-integration "Direct link to Setting up your first integration") ### Step 1: Obtain your Infrahub API token[​](#step-1-obtain-your-infrahub-api-token "Direct link to Step 1: Obtain your Infrahub API token") note You can skip this step if you will be using the Infrahub Sandbox as the `config.yaml` file below already has the Infrahub Sandbox token! First, you'll need an API token to authenticate with Infrahub: 1. Log into your Infrahub web interface 2. Navigate to your user profile settings 3. Generate or copy your API token 4. Store it securely (we'll use it in the next step) ### Step 2: Understand your Infrahub schema[​](#step-2-understand-your-infrahub-schema "Direct link to Step 2: Understand your Infrahub schema") Before configuring Nornir, you need to understand your Infrahub schema structure. The sandbox instance at [sandbox.infrahub.app](https://sandbox.infrahub.app) uses a schema similar to this example: #### Example schema structure[​](#example-schema-structure "Direct link to Example schema structure") For the examples in this documentation to work, you'll need a schema with at least these node types: ``` # Device node definition nodes: - name: Device namespace: Infra attributes: - name: name kind: Text unique: true - name: description kind: Text optional: true - name: type kind: Text relationships: - name: site peer: BuiltinLocation cardinality: one - name: role peer: BuiltinRole cardinality: one - name: primary_address peer: InfraIPAddress cardinality: one optional: true - name: platform peer: InfraPlatform cardinality: one optional: true # Supporting nodes - name: IPAddress namespace: Infra attributes: - name: address kind: IPHost - name: Platform namespace: Infra attributes: - name: name kind: Text - name: nornir_platform kind: Text ``` #### Key schema requirements[​](#key-schema-requirements "Direct link to Key schema requirements") For Nornir integration, ensure your schema has: 1. **Device node** (`InfraDevice` or similar) representing network devices 2. **IP Address relationship** for device connectivity (`primary_address`) 3. **Platform relationship** for device type identification 4. **Location/Site relationship** for grouping devices by location 5. **Role relationship** for grouping devices by function #### Checking your schema[​](#checking-your-schema "Direct link to Checking your schema") To verify your schema structure: 1. Open your Infrahub web interface 2. Navigate to the Schema section 3. Look for device node types and their relationships 4. Note the exact attribute names and relationship paths for mapping ### Step 3: Create your Nornir configuration[​](#step-3-create-your-nornir-configuration "Direct link to Step 3: Create your Nornir configuration") Create a file named `config.yaml` with your Nornir configuration: ``` --- inventory: plugin: InfrahubInventory options: address: "https://sandbox.infrahub.app" # Infrahub instance URL token: "1808d43b-c370-48e8-d0ef-c51781c02ddf" # Replace with your actual token branch: "main" # Infrahub branch to use # Define which Infrahub node type represents network devices # Using the schema example above host_node: kind: "InfraDevice" # Matches the Device node with Infra namespace include: - "platform" # Map Infrahub attributes to Nornir host properties schema_mappings: - name: "hostname" mapping: "name" # Use device name as hostname - name: "platform" mapping: "platform.nornir_platform" # Platform attribute for Nornir compatibility # Create Nornir groups based on Infrahub attributes # Note: Disabled due to some devices missing site/role in sandbox # group_mappings: # - "site.name" # - "role.name" runner: plugin: threaded options: num_workers: 20 ``` ### Step 4: Test your connection[​](#step-4-test-your-connection "Direct link to Step 4: Test your connection") Create a Python script called `test_connection.py` to verify your setup: ``` from nornir import InitNornir from nornir_utils.plugins.functions import print_result def main(): # Initialize Nornir with your configuration nr = InitNornir(config_file="config.yaml") # Display discovered inventory print("Discovered hosts:") for host in nr.inventory.hosts.values(): print(f" - {host.name} ({host.hostname})") print("\nDiscovered groups:") for group in nr.inventory.groups.values(): print(f" - {group.name}") return 0 if __name__ == "__main__": raise SystemExit(main()) ``` Run the script: ``` python test_connection.py ``` You should see a list of hosts and groups discovered from your Infrahub instance. ### Step 5: Run your first task[​](#step-5-run-your-first-task "Direct link to Step 5: Run your first task") Now let's run a task against your inventory: ``` from nornir import InitNornir from nornir.core.task import Result, Task from nornir_utils.plugins.functions import print_result def hello_world(task: Task) -> Result: """A task that returns a greeting.""" return Result( host=task.host, result=f"Hello from {task.host.name}!" ) def main(): # Initialize Nornir nr = InitNornir(config_file="config.yaml") # Run the hello_world task on all hosts results = nr.run(task=hello_world) # Display results print_result(results) return 0 if __name__ == "__main__": raise SystemExit(main()) ``` ## Verifying your setup[​](#verifying-your-setup "Direct link to Verifying your setup") To ensure everything is working correctly: 1. **Check Host Discovery**: Verify that all expected hosts appear in your inventory 2. **Validate Groups**: Confirm that dynamic groups are created based on your mappings 3. **Test Connectivity**: Run a task to ensure Nornir can process your inventory ## Common issues and solutions[​](#common-issues-and-solutions "Direct link to Common issues and solutions") ### Authentication errors[​](#authentication-errors "Direct link to Authentication errors") If you see authentication errors: * Verify your API token is correct * Ensure your Infrahub instance is accessible at the specified address * Check that your token has appropriate permissions ### Missing hosts[​](#missing-hosts "Direct link to Missing hosts") If hosts aren't appearing: * Verify the `host_node.kind` matches your Infrahub schema * Check that devices exist in the specified branch * Ensure devices have the required attributes for your schema mappings ### Mapping errors[​](#mapping-errors "Direct link to Mapping errors") If you see errors about missing attributes: * Verify your schema mappings match your Infrahub data model * Use the Infrahub GraphQL explorer to check available attributes * Ensure related objects (like `primary_address`) are properly defined ## Next steps[​](#next-steps "Direct link to Next steps") Now that you have a working integration: * Explore [Your First Automation Workflow](/nornir/guides/your-first-automation-workflow.md) to build a complete automation * Learn about [Schema Mappings](/nornir/guides/configuring-schema-mappings.md) for advanced inventory configuration * Understand [Artifact Management](/nornir/topics/artifact-lifecycle-management.md) for configuration generation ## Getting help[​](#getting-help "Direct link to Getting help") * Check the [Infrahub Documentation](https://docs.infrahub.app/) for schema and API details * Review the [Nornir Documentation](https://nornir.readthedocs.io/) for automation concepts * Join the Infrahub community for support and best practices --- # How to Configure Schema Mappings This guide shows you how to configure schema mappings to control how Infrahub data translates into Nornir inventory properties. Schema mappings are the bridge between your Infrahub data model and Nornir's expected host attributes. ## What you'll accomplish[​](#what-youll-accomplish "Direct link to What you'll accomplish") By following this guide, you'll be able to: * Map Infrahub node attributes to Nornir host properties * Handle nested relationships and complex data structures * Create custom mappings for specialized use cases * Debug and troubleshoot mapping issues ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Working Nornir-Infrahub installation * Infrahub instance with network devices configured * You can use the [Infrahub sandbox instance](https://sandbox.infrahub.app) to follow along * Understanding of your Infrahub schema (see example schema in [Getting Started](/nornir/getting-started.md#example-schema-structure)) * Access to Infrahub GraphQL explorer (for testing queries) * Sandbox GraphQL explorer: [sandbox.infrahub.app/graphql](https://sandbox.infrahub.app/graphql) ## Understanding schema mappings[​](#understanding-schema-mappings "Direct link to Understanding schema mappings") Schema mappings define how to extract values from Infrahub nodes and assign them to Nornir host properties. Each mapping consists of: * `name`: The Nornir property to set (i.e., "hostname", "platform") * `mapping`: The path to the value in the Infrahub node (i.e., "primary\_address.address") ## Step 1: Identify required Nornir properties[​](#step-1-identify-required-nornir-properties "Direct link to Step 1: Identify required Nornir properties") First, determine which Nornir properties your automation tasks need: | Property | Purpose | Common Mapping Source | | ---------- | ----------------------- | -------------------------- | | `hostname` | IP/FQDN for connections | Device primary address | | `platform` | Device OS type | Platform or OS attribute | | `username` | Authentication user | Credential or static value | | `password` | Authentication password | Credential or static value | | `port` | Connection port | Interface or static value | | `data` | Custom data | Any Infrahub attributes | ## Step 2: Explore your Infrahub schema[​](#step-2-explore-your-infrahub-schema "Direct link to Step 2: Explore your Infrahub schema") Use the Infrahub GraphQL explorer to understand available attributes. For the [Infrahub sandbox](https://sandbox.infrahub.app/graphql), you can query: ``` query { InfraDevice { edges { node { name { value } primary_address { node { address { value } } } platform { node { name { value } nornir_platform { value } } } site { node { name { value } } } role { value } } } } } ``` note The query structure depends on your schema. In the sandbox, `role` is a Dropdown attribute (use `value`), while `site` and `platform` are relationships (use `node`). Adapt your queries to match your schema structure. This helps you identify the exact paths to your data based on your schema structure. ## Step 3: Configure basic mappings[​](#step-3-configure-basic-mappings "Direct link to Step 3: Configure basic mappings") ### Basic attribute mapping[​](#basic-attribute-mapping "Direct link to Basic attribute mapping") For direct attributes on the node: ``` schema_mappings: - name: "hostname" mapping: "name" # Maps device name to hostname ``` ### Nested relationship mapping[​](#nested-relationship-mapping "Direct link to Nested relationship mapping") For attributes through relationships (based on the example schema): ``` schema_mappings: - name: "hostname" mapping: "primary_address.address" # Traverses to InfraIPAddress node - name: "platform" mapping: "platform.nornir_platform" # Traverses to InfraPlatform node ``` important Every relationship traversed in `schema_mappings` or `group_mappings` must be listed in `host_node.include`. Without this, the related node's attributes are not fetched and mappings will fail. ``` host_node: kind: "InfraDevice" include: - "primary_address" # required for primary_address.address - "platform" # required for platform.nornir_platform ``` ### Customizing the Nornir host name[​](#customizing-the-nornir-host-name "Direct link to Customizing the Nornir host name") By default, the plugin uses the node's `name` attribute as the Nornir host name. If your schema does not define a `name` attribute, or you want a different attribute to drive the host name, add a schema mapping whose `name` is `"name"`: ``` schema_mappings: - name: "name" mapping: "hostname" # Use the node's "hostname" attribute as the Nornir host name ``` The `mapping` follows the same rules as any other mapping, so you can also resolve the name through a single relationship (for example, `primary_address.address`). When no `name` mapping is provided and the node has no `name` attribute, the host is skipped. ### Multiple level nesting[​](#multiple-level-nesting "Direct link to Multiple level nesting") The plugin supports traversing one relationship level before reaching an attribute. For example, `site.name` follows the `site` relationship then reads its `name` attribute. Deeper chains (e.g., `site.location.name`) are not supported. ## Step 4: Handle complex mappings[​](#step-4-handle-complex-mappings "Direct link to Step 4: Handle complex mappings") ### IP address handling[​](#ip-address-handling "Direct link to IP address handling") Infrahub stores IP addresses as interface objects. The integration automatically extracts the IP: ``` schema_mappings: - name: "hostname" mapping: "primary_address.address" # Automatically converts IPHost to string ``` ### Infrahub Node Object[​](#infrahub-node-object "Direct link to Infrahub Node Object") Along with schema mappings that allow mapping Infrahub inventory data to Nornir host attributes, we also include the Infrahub object that can be used in tasks, etc. ``` def example_task(task): # Access the Infrahub node data infrahub_node = task.host.data["InfrahubNode"] # Access node attributes directly if hasattr(infrahub_node, 'serial_number'): serial = infrahub_node.serial_number.value print(f"Device {task.host.name} serial: {serial}") ``` ## Step 5: Configure group mappings[​](#step-5-configure-group-mappings "Direct link to Step 5: Configure group mappings") Group mappings create dynamic Nornir groups from Infrahub node attributes or relationships: ``` host_node: kind: "InfraDevice" include: - "site" # required for site.name group mapping - "platform" # required for platform.name group mapping group_mappings: - "site.name" # Creates groups like "site__chicago" - "platform.name" # Creates groups like "platform__eos" ``` Groups are automatically prefixed with the first part of the mapping path to avoid conflicts. Additionally, the plugin will auto add groups based on each node's groups it is assigned to within Infrahub. ## Step 6: Test your mappings[​](#step-6-test-your-mappings "Direct link to Step 6: Test your mappings") Create a test script to verify mappings: ``` from nornir import InitNornir def test_mappings(): nr = InitNornir(config_file="config.yaml") # Check hosts print("Sample hosts:") for host in list(nr.inventory.hosts.values())[:3]: # First 3 hosts print(f"\n {host.name}:") print(f" Hostname: {host.hostname}") print(f" Platform: {host.platform}") print(f" Groups: {[g.name for g in host.groups]}") # List all groups print("\nAll groups:") for group in nr.inventory.groups: print(f" - {group}") if __name__ == "__main__": test_mappings() ``` ## Validation[​](#validation "Direct link to Validation") ### Check mapping success[​](#check-mapping-success "Direct link to Check mapping success") After configuration, verify: 1. All hosts have required properties set 2. Groups are created as expected 3. No mapping errors in logs ### Validation Example[​](#validation-example "Direct link to Validation Example") ``` # Check all hosts have hostname for host in nr.inventory.hosts.values(): assert host.hostname is not None, f"{host.name} missing hostname" # Verify platform mappings platforms = {h.platform for h in nr.inventory.hosts.values()} print(f"Discovered platforms: {platforms}") # Check group membership for group in nr.inventory.groups.values(): print(f"{group.name}: {len(group.hosts)} hosts") ``` ## Advanced usage[​](#advanced-usage "Direct link to Advanced usage") ### Using mapping results in tasks[​](#using-mapping-results-in-tasks "Direct link to Using mapping results in tasks") Access mapped data in your tasks: ``` def device_info(task): # Access standard properties print(f"Connecting to {task.host.hostname}") print(f"Platform: {task.host.platform}") # Access Infrahub node node = task.host.data["InfrahubNode"] print(f"Infrahub ID: {node.id}") ``` ### Troubleshooting mapping failures[​](#troubleshooting-mapping-failures "Direct link to Troubleshooting mapping failures") If mappings fail silently: 1. Enable debug logging: ``` import logging logging.basicConfig(level=logging.DEBUG) ``` 2. Check the Infrahub GraphQL query result for the specific node: ``` # Add to your script print(vars(nr.inventory.hosts["device1"].data["InfrahubNode"])) ``` 3. Verify relationship cardinality: * Single relationships: `mapping: "platform.name"` * Many relationships not supported directly ## Related resources[​](#related-resources "Direct link to Related resources") * [Understanding Nornir-Infrahub Integration](/nornir/topics/understanding-nornir-infrahub-integration.md) * [Infrahub Inventory Concepts](/nornir/topics/infrahub-inventory-concepts.md) * [Your First Automation Workflow](/nornir/guides/your-first-automation-workflow.md) --- # How to Create Your First Automation Workflow This guide walks you through building a complete network automation workflow using Infrahub as your inventory source and artifact management system. You'll retrieve device configurations managed by Infrahub. ## What you'll accomplish[​](#what-youll-accomplish "Direct link to What you'll accomplish") By the end of this guide, you'll have: * Connected Nornir to your Infrahub instance * Retrieved device configurations from Infrahub artifacts * Used dynamic device groups for targeted operations * Understood how to integrate Nornir with Infrahub's artifact system ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Infrahub instance with network devices and artifact definitions configured * You can use the [Infrahub sandbox instance](https://sandbox.infrahub.app) to try this workflow * Nornir-Infrahub plugin installed (`pip install nornir-infrahub`) * Basic Python and Nornir knowledge ## Step 1: Set up your project structure[​](#step-1-set-up-your-project-structure "Direct link to Step 1: Set up your project structure") Create a project directory with the following structure: ``` mkdir network-automation cd network-automation # Create directory structure mkdir -p configs touch config.yaml touch workflow.py ``` ## Step 2: Configure Nornir with Infrahub[​](#step-2-configure-nornir-with-infrahub "Direct link to Step 2: Configure Nornir with Infrahub") Create your `config.yaml` file: config.yaml ``` --- inventory: plugin: InfrahubInventory options: address: "https://sandbox.infrahub.app" # Infrahub instance URL token: "1808d43b-c370-48e8-d0ef-c51781c02ddf" # Replace with your actual token branch: "main" # Infrahub branch to use # Define which Infrahub node type represents network devices # Using the schema example above host_node: kind: "InfraDevice" # Matches the Device node with Infra namespace include: - "platform" # Map Infrahub attributes to Nornir host properties schema_mappings: - name: "hostname" mapping: "name" # Use device name as hostname - name: "platform" mapping: "platform.nornir_platform" # Platform attribute for Nornir compatibility # Create Nornir groups based on Infrahub attributes # Note: Disabled due to some devices missing site/role in sandbox # group_mappings: # - "site.name" # - "role.name" runner: plugin: threaded options: num_workers: 20 ``` ## Step 3: Understand Infrahub artifact system[​](#step-3-understand-infrahub-artifact-system "Direct link to Step 3: Understand Infrahub artifact system") Before proceeding, ensure your Infrahub instance has artifact definitions configured. In this workflow, we'll use Infrahub's artifact system to retrieve generated configurations rather than creating templates locally. Infrahub artifacts provide: * **Centralized Templates**: Jinja2 templates stored in Git repositories * **Data-Driven Generation**: Configurations generated from your Infrahub data model * **Version Control**: All templates and generated artifacts are versioned * **Consistency**: Ensure all network configurations follow organizational standards The Nornir-Infrahub plugin provides tasks to interact with these artifacts: * `get_artifact()`: Retrieve generated configuration content * `generate_artifacts()`: Trigger artifact generation for targets * `regenerate_host_artifact()`: Regenerate configuration for a specific device ## Step 4: Build the workflow script[​](#step-4-build-the-workflow-script "Direct link to Step 4: Build the workflow script") Create `workflow.py` with your automation workflow: workflow\.py ``` #!/usr/bin/env python3 """Network automation workflow using Nornir and Infrahub artifacts. This module implements a complete configuration management workflow that: 1. Connects to Infrahub to load device inventory 2. Regenerates device configurations using Infrahub's artifact system 3. Retrieves the generated configurations 4. Validates the generated configurations The workflow is designed to work with the Infrahub sandbox environment and targets edge devices that have the "Startup Config for Edge devices" artifact definition configured. Requirements: - nornir: Core automation framework - nornir-infrahub: Infrahub inventory and task plugins - nornir-utils: Result printing utilities Example: Run the workflow from the command line:: $ python workflow.py Or import and customize:: from workflow import main main() """ import logging from pathlib import Path from nornir import InitNornir from nornir.core.task import Task, Result from nornir_utils.plugins.functions import print_result from nornir_infrahub.plugins.tasks import ( get_artifact, regenerate_host_artifact, ) # Configure logging logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" ) logger = logging.getLogger(__name__) def retrieve_configuration(task: Task) -> Result: """Retrieve device configuration from Infrahub artifacts. Fetches the generated configuration artifact from Infrahub and saves it locally for validation. The configuration is saved to the `configs/` directory with the filename `{hostname}.cfg`. Args: task: Nornir task object containing host information and Infrahub client. Returns: Result object with: - On success: Path to saved configuration file - On failure: Error message describing the issue Raises: No exceptions are raised; all errors are captured in the Result object. """ try: # Get the configuration artifact from Infrahub # Using the artifact name from the sandbox result = get_artifact( task, artifact="Startup Config for Edge devices", ) # Check if we got actual content if not result.result or result.result.strip() == "": return Result( host=task.host, failed=True, result="Artifact exists but has no content", ) # Save configuration locally output_dir = Path("configs") output_dir.mkdir(exist_ok=True) config_file = output_dir / f"{task.host.name}.cfg" config_file.write_text(result.result) return Result( host=task.host, result=f"Configuration retrieved from Infrahub and saved to {config_file}", ) except Exception as e: error_msg = str(e) if "NodeNotFoundError" in error_msg or "Unable to find" in error_msg: return Result( host=task.host, failed=True, result="No artifact found for this device (may not be configured in Infrahub)", ) return Result( host=task.host, failed=True, result=f"Failed to retrieve configuration: {error_msg}", ) def regenerate_configuration(task: Task) -> Result: """Trigger regeneration of device configuration in Infrahub. Requests Infrahub to regenerate the configuration artifact for the host. This ensures the latest data from Infrahub is used to generate a fresh configuration before retrieval. Args: task: Nornir task object containing host information and Infrahub client. Returns: Result object with: - On success: Confirmation message - On failure: Error message describing the issue """ try: # Regenerate the artifact in Infrahub regenerate_host_artifact( task, artifact="Startup Config for Edge devices", ) return Result( host=task.host, result="Configuration regenerated successfully in Infrahub" ) except Exception as e: error_msg = str(e) if "NodeNotFoundError" in error_msg or "Unable to find" in error_msg: return Result( host=task.host, failed=True, result="No artifact definition found for this device", ) return Result( host=task.host, failed=True, result=f"Failed to regenerate configuration: {error_msg}", ) def validate_configuration(task: Task) -> Result: """Validate the retrieved configuration. Performs basic validation checks on the configuration file to ensure it meets basic requirements. Currently validates that the configuration is not empty. Args: task: Nornir task object containing host information. Returns: Result object with: - On success: Validation passed message with file size - On failure: List of failed validation checks """ config_file = Path("configs") / f"{task.host.name}.cfg" if not config_file.exists(): return Result( host=task.host, failed=True, result="Configuration file not found" ) config_content = config_file.read_text() # Basic validation checks - relaxed for sandbox artifacts validations = { "not_empty": len(config_content.strip()) > 0, } failed_checks = [check for check, passed in validations.items() if not passed] if failed_checks: return Result( host=task.host, failed=True, result=f"Validation failed: {', '.join(failed_checks)}", ) return Result( host=task.host, result=f"Configuration validation passed ({len(config_content)} bytes)", ) def main() -> int: """Execute the complete network automation workflow. Orchestrates the following steps: 1. Load device inventory from Infrahub 2. Filter to edge devices (which have startup config artifacts) 3. Regenerate configurations in Infrahub 4. Retrieve configurations to local `configs/` directory 5. Validate all retrieved configurations The workflow will abort if any validation failures occur. Returns: Exit code: 0 for success, 1 for validation failures. """ # Initialize Nornir # Step 1: Load devices/init Nornir dynamic inventory from Infrahub nr = InitNornir(config_file="config.yaml") logger.info(f"Loaded {len(nr.inventory.hosts)} hosts from Infrahub") # Filter to only edge devices (which have the Startup Config artifact) edge_devices = nr.filter(filter_func=lambda h: "edge" in h.name) logger.info( f"Filtered to {len(edge_devices.inventory.hosts)} edge devices with artifacts" ) # Step 2: Regenerate configurations in Infrahub logger.info("Step 2: Regenerating configurations in Infrahub...") regen_results = edge_devices.run(task=regenerate_configuration) print_result(regen_results) # Step 3: Retrieve updated configurations from Infrahub logger.info("Step 3: Retrieving configurations from Infrahub...") retrieve_results = edge_devices.run(task=retrieve_configuration) print_result(retrieve_results) # Step 4: Validate configurations logger.info("Step 4: Validating configurations...") validation_results = edge_devices.run(task=validate_configuration) print_result(validation_results) # Check for validation failures failed_hosts = [ host for host, result in validation_results.items() if result.failed ] if failed_hosts: logger.error(f"Validation failed for hosts: {failed_hosts}") logger.info("Aborting due to validation failures") return 1 return 0 if __name__ == "__main__": raise SystemExit(main()) ``` ## Step 5: Execute the workflow[​](#step-5-execute-the-workflow "Direct link to Step 5: Execute the workflow") Run your automation workflow: ``` python workflow.py ``` The workflow will: 1. Connect to Infrahub and load device inventory 2. Regenerate configurations in Infrahub using artifact system 3. Retrieve updated configurations from Infrahub 4. Validate the retrieved configurations ## Validation[​](#validation "Direct link to Validation") ### Check retrieved configurations[​](#check-retrieved-configurations "Direct link to Check retrieved configurations") Review the configuration files retrieved from Infrahub in the `configs/` directory: ``` ls -la configs/ cat configs/router1.cfg ``` ## Advanced usage[​](#advanced-usage "Direct link to Advanced usage") ### Filtering devices[​](#filtering-devices "Direct link to Filtering devices") Target specific devices or groups: ``` from nornir.core.filter import F # Filter by platform junos_devices = nr.filter(platform="juniper_junos") results = junos_devices.run(task=retrieve_configuration) # Filter by group core_devices = nr.filter(F(groups__contains="core_router")) core_results = core_devices.run(task=retrieve_configuration) ``` Further information on [Nornir filters](https://nornir.readthedocs.io/en/latest/tutorial/inventory.html#Filtering-the-inventory). ### Parallel execution control[​](#parallel-execution-control "Direct link to Parallel execution control") Adjust parallelism for different tasks: ``` # Fewer workers for controlled execution nr.config.runner.options["num_workers"] = 2 results = nr.run(task=retrieve_configuration) # More workers for parallel operations nr.config.runner.options["num_workers"] = 50 results = nr.run(task=retrieve_configuration) ``` ### Integration with CI/CD[​](#integration-with-cicd "Direct link to Integration with CI/CD") Create a CI-friendly version: ``` # ci_workflow.py import sys import os from nornir.core.filter import F def ci_workflow(): # Get parameters from environment target_branch = os.getenv("INFRAHUB_BRANCH", "main") target_site = os.getenv("TARGET_SITE", None) # Initialize with CI parameters nr = InitNornir( config_file="config.yaml", inventory={ "options": { "branch": target_branch } } ) # Filter by site group if specified (site.name group_mapping creates groups like "site__chicago") if target_site: nr = nr.filter(F(groups__contains=f"site__{target_site}")) # Run workflow without prompts results = nr.run(task=retrieve_configuration) # Exit with proper code failed_count = len([r for r in results.values() if r.failed]) sys.exit(failed_count) ``` ## Related resources[​](#related-resources "Direct link to Related resources") * [Understanding Nornir-Infrahub Integration](/nornir/topics/understanding-nornir-infrahub-integration.md) * [Configuring Schema Mappings](/nornir/guides/configuring-schema-mappings.md) * [Artifact Lifecycle Management](/nornir/topics/artifact-lifecycle-management.md) * [Nornir Task Documentation](https://nornir.readthedocs.io/) --- # Artifact management plugin ## `regenerate_host_artifact`[​](#regenerate_host_artifact "Direct link to regenerate_host_artifact") Regenerates a host artifact for a given task. This function regenerates the named artifact for the host's InfrahubNode through the Infrahub SDK. ### Parameters[​](#parameters "Direct link to Parameters") | Parameter | Type | Required | Default | Description | | ---------- | ------ | -------- | ------- | ----------------------------------------------- | | `task` | `Task` | Yes | | The task instance containing host-related data. | | `artifact` | `str` | Yes | | The name of the artifact to regenerate. | ### Returns[​](#returns "Direct link to Returns") `Result` — An object representing the outcome of the operation, indicating success or failure. ### Raises[​](#raises "Direct link to Raises") | Exception | Description | | ----------- | ------------------------------------------------------------------------------------ | | `Exception` | Propagates any error raised by the Infrahub SDK if the artifact cannot be generated. | ### Examples[​](#examples "Direct link to Examples") #### Regenerate artifact for a given device[​](#regenerate-artifact-for-a-given-device "Direct link to Regenerate artifact for a given device") ``` from nornir import InitNornir from nornir.core.plugins.inventory import InventoryPluginRegister from nornir_infrahub.plugins.inventory.infrahub import InfrahubInventory from nornir_infrahub.plugins.tasks import regenerate_host_artifact from nornir_utils.plugins.functions import print_result def main(): InventoryPluginRegister.register("InfrahubInventory", InfrahubInventory) nr = InitNornir(inventory=...) eos_devices = nr.filter(platform="eos") # regenerate an artifact for a host print_result(eos_devices.run(task=regenerate_host_artifact, artifact="startup-config")) return 0 if __name__ == "__main__": raise SystemExit(main()) ``` ## `generate_artifacts`[​](#generate_artifacts "Direct link to generate_artifacts") Generates an artifact for a given task. This function retrieves the artifact definition from the InfrahubNode and triggers its generation through the Infrahub SDK. ### Parameters[​](#parameters-1 "Direct link to Parameters") | Parameter | Type | Required | Default | Description | | ---------- | ------ | -------- | ------- | -------------------------------------------------------------------- | | `task` | `Task` | Yes | | The task instance containing host-related data. | | `artifact` | `str` | Yes | | The name of the artifact to generate. | | `timeout` | `int` | No | 10 | Retained for compatibility; ignored by the SDK path. Defaults to 10. | ### Returns[​](#returns-1 "Direct link to Returns") `Result` — An object representing the outcome of the operation, indicating success or failure. ### Raises[​](#raises-1 "Direct link to Raises") | Exception | Description | | ----------- | ------------------------------------------------------------------------------------ | | `Exception` | Propagates any error raised by the Infrahub SDK if the artifact cannot be generated. | ### Examples[​](#examples-1 "Direct link to Examples") #### Example generating artifacts[​](#example-generating-artifacts "Direct link to Example generating artifacts") ``` from nornir import InitNornir from nornir.core.plugins.inventory import InventoryPluginRegister from nornir_infrahub.plugins.inventory.infrahub import InfrahubInventory from nornir_infrahub.plugins.tasks import generate_artifacts def main(): InventoryPluginRegister.register("InfrahubInventory", InfrahubInventory) nr = InitNornir(inventory=...) # generate_artifacts, generates the artifact for all the targets in the Artifact definition # we only need to run this task once, per artifact definition run_once = nr.filter(name="jfk1-edge1") result = run_once.run(task=generate_artifacts, artifact="startup-config", timeout=20) ocfg_result = run_once.run(task=generate_artifacts, artifact="openconfig-interfaces", timeout=20) return 0 if __name__ == "__main__": raise SystemExit(main()) ``` ## `get_artifact`[​](#get_artifact "Direct link to get_artifact") Retrieves the specified artifact from the Infrahub storage. This function fetches an artifact node associated with the given artifact name or id and sends a request to retrieve its stored content. The response is returned as JSON or text, depending on the artifact's content type. ### Parameters[​](#parameters-2 "Direct link to Parameters") | Parameter | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | ----------------------------------------------- | | `task` | `Task` | Yes | | The task instance containing host-related data. | | `artifact` | `str` | No | | The name of the artifact to retrieve. | | `artifact_id` | `str` | No | | The id of the artifact to retrieve. | ### Returns[​](#returns-2 "Direct link to Returns") `Result` — An object containing the retrieved artifact data, its content type, and the success status of the operation. ### Raises[​](#raises-2 "Direct link to Raises") | Exception | Description | | -------------- | ------------------------------------------------------------------------------------ | | `RuntimeError` | If neither or both of `artifact` and `artifact_id` are provided. | | `Exception` | Propagates any error raised by the Infrahub SDK if the artifact cannot be retrieved. | ### Examples[​](#examples-2 "Direct link to Examples") #### Example getting artifacts from Infrahub[​](#example-getting-artifacts-from-infrahub "Direct link to Example getting artifacts from Infrahub") ``` from nornir import InitNornir from nornir.core.plugins.inventory import InventoryPluginRegister from nornir_infrahub.plugins.inventory.infrahub import InfrahubInventory from nornir_infrahub.plugins.tasks import get_artifact from nornir_utils.plugins.functions import print_result def main(): InventoryPluginRegister.register("InfrahubInventory", InfrahubInventory) nr = InitNornir(inventory=...) eos_devices = nr.filter(platform="eos") # retrieves the artifact for all the hosts in the inventory result = eos_devices.run(task=get_artifact, artifact="startup-config") print_result(result) return 0 if __name__ == "__main__": raise SystemExit(main()) ``` --- # CoreFileObject management tasks ## `upload_file_object`[​](#upload_file_object "Direct link to upload_file_object") Uploads a file to an Infrahub CoreFileObject object. Creates the object if it does not exist, or updates it if the file content has changed (SHA-1 checksum comparison). When the local content is identical to the one stored on the server the upload is skipped (idempotent). Provide either `file_path` (to upload a file from disk) or `content` with `file_name` (to upload raw bytes). ### Parameters[​](#parameters "Direct link to Parameters") | Parameter | Type | Required | Default | Description | | ----------- | ------------- | -------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `task` | `Task` | Yes | | The Nornir task instance containing host-related data. | | `kind` | `str` | Yes | | The schema kind that inherits from CoreFileObject. | | `file_path` | `str \| Path` | No | | Filesystem path to the file to upload. | | `content` | `bytes` | No | | Raw file content to upload. Requires `file_name`. | | `file_name` | `str` | No | | File name to associate with `content`. Ignored when `file_path` is used. | | `data` | `dict` | No | | Additional object fields. On create, this is forwarded to `client.create` (attributes and relationships). On update, only attribute keys are applied; passing a relationship or unknown key fails the task. | | `object_id` | `str` | No | | UUID of an existing object to update. | | `hfid` | `list[str]` | No | | HFID components identifying an existing object. | | `branch` | `str` | No | the client's default branch | Target Infrahub branch. Defaults to the client's default branch. | | `**kwargs` | `Any` | No | | Extra keyword arguments forwarded to `client.create` (e.g. `allow_upsert`, `timeout`). Applies only on the create path; the update path does not call `client.create`, so these are not used when updating an existing object. | ### Returns[​](#returns "Direct link to Returns") `Result` — A Nornir Result with `changed` indicating whether the object was created or updated, `object_id` with the UUID, and a descriptive `result` message. ### Raises[​](#raises "Direct link to Raises") | Exception | Description | | ------------ | ----------------------------------------------------------------------------- | | `ValueError` | If *kind* does not inherit from CoreFileObject or arguments are inconsistent. | ### Examples[​](#examples "Direct link to Examples") #### Upload a contract PDF to a CoreFileObject object[​](#upload-a-contract-pdf-to-a-corefileobject-object "Direct link to Upload a contract PDF to a CoreFileObject object") ``` from nornir_infrahub.plugins.tasks import upload_file_object result = nr.run( task=upload_file_object, kind="NetworkCircuitContract", file_path="/path/to/contract.pdf", data={"contract_start": "2026-01-01"}, ) ``` ## `download_file_object`[​](#download_file_object "Direct link to download_file_object") Downloads file content from an Infrahub CoreFileObject object. Returns the file content as base64-encoded binary data, with a UTF-8 text representation for text MIME types. Optionally saves the file to a local path. When `save_to` points to an existing file whose SHA-1 matches the server-side checksum, the download is skipped and `changed` is `False` (idempotent, similar to `nornir_utils.plugins.tasks.files.write_file`). Note: The downloaded content is held in memory and embedded as base64 in the Nornir `Result`. Nornir aggregates results across hosts, so downloading large files across many hosts can be heavy. For large payloads, prefer `save_to` and ignore the `binary`/`text` fields, or call the infrahub-sdk download API directly. ### Parameters[​](#parameters-1 "Direct link to Parameters") | Parameter | Type | Required | Default | Description | | ----------- | ------------- | -------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | `task` | `Task` | Yes | | The Nornir task instance containing host-related data. | | `kind` | `str` | Yes | | The schema kind that inherits from CoreFileObject. | | `object_id` | `str` | No | | UUID of the object to download from. | | `hfid` | `list[str]` | No | | HFID components identifying the object. | | `save_to` | `str \| Path` | No | | Local path where the downloaded file should be written. A directory receives the original file name; an explicit path is used as-is. | | `branch` | `str` | No | the client's default branch | Target Infrahub branch. Defaults to the client's default branch. | ### Returns[​](#returns-1 "Direct link to Returns") `Result` — A Nornir Result containing `binary` (base64 string), `text` (UTF-8 string or None), `file_name`, `file_type`, `file_size`, `checksum`, `object_id`, `save_to`, and `changed` (True only when a local file was created or overwritten). ### Raises[​](#raises-1 "Direct link to Raises") | Exception | Description | | ------------ | ----------------------------------------------- | | `ValueError` | If *kind* does not inherit from CoreFileObject. | ### Examples[​](#examples-1 "Direct link to Examples") #### Download a file from a CoreFileObject object[​](#download-a-file-from-a-corefileobject-object "Direct link to Download a file from a CoreFileObject object") ``` from nornir_infrahub.plugins.tasks import download_file_object result = nr.run( task=download_file_object, kind="NetworkCircuitContract", hfid=["contract-2026"], ) ``` --- # Inventory plugin ## `InfrahubInventory`[​](#infrahubinventory "Direct link to infrahubinventory") Nornir inventory plugin for integrating with `Opsmill - Infrahub` (). This plugin fetches inventory data from Infrahub, maps it to Nornir Hosts, and supports the creation of Nornir groups based on attributes or relations from Infrahub Nodes. ### Parameters[​](#parameters "Direct link to Parameters") | Parameter | Type | Required | Default | Description | | ----------------- | ------ | -------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `address` | `str` | No | "" | The Infrahub URL to connect to. Defaults to "". | | `branch` | `str` | No | "main" | The Infrahub branch to use. Defaults to "main". | | `host_node` | `dict` | Yes | | A dictionary defining the Infrahub Node kind that will be mapped to Nornir Hosts. Example: `{"kind": "InfraDevice"}` | | `schema_mappings` | `list` | Yes | | A list of mappings that define how Nornir Host properties correspond to attributes or relations from Infrahub Nodes. A mapping with `name: "name"` customizes the Nornir host name (default: the node's `name` attribute). Example: `[{"name": "hostname", "mapping": "primary_address.address"}, {"name": "name", "mapping": "hostname"}]`. | | `group_mappings` | `list` | Yes | | A list of Infrahub Node attributes or relations used to create Nornir groups. Example: `["site.name"]`. | | `defaults_file` | `str` | No | "defaults.yaml" | Path to the defaults YAML file. Defaults to "defaults.yaml". | | `group_file` | `str` | No | "group.yaml" | Path to the group YAML file. Defaults to "group.yaml". | ### Examples[​](#examples "Direct link to Examples") #### Basic usage of `InfrahubInventory` with Nornir[​](#basic-usage-of-infrahubinventory-with-nornir "Direct link to basic-usage-of-infrahubinventory-with-nornir") ``` from nornir import InitNornir from nornir.core.plugins.inventory import InventoryPluginRegister from nornir_infrahub.plugins.inventory.infrahub import InfrahubInventory def main(): # Register the custom InfrahubInventory plugin InventoryPluginRegister.register("InfrahubInventory", InfrahubInventory) # Initialize Nornir with InfrahubInventory as the inventory plugin nr = InitNornir( inventory={ "plugin": "InfrahubInventory", "options": { "address": "http://localhost:8000", # Infrahub API URL "token": "06438eb2-8019-4776-878c-0941b1f1d1ec", # Infrahub API token "host_node": {"kind": "InfraDevice"}, # Infrahub Node kind to map to Nornir Hosts "schema_mappings": [ {"name": "hostname", "mapping": "primary_address.address"}, {"name": "platform", "mapping": "platform.nornir_platform"}, ], # Mapping Nornir Host properties to Infrahub Node attributes "group_mappings": ["site.name"], # Create Nornir groups from Infrahub Node attributes "group_file": "dummy.yml", # Path to the group file }, } ) # Print Nornir inventory host and group names print(nr.inventory.hosts.keys()) print(nr.inventory.groups.keys()) return 0 if __name__ == "__main__": raise SystemExit(main()) ``` --- # Understanding Artifact Lifecycle Management This topic explores how Infrahub's artifact system works with Nornir to enable sophisticated configuration management workflows. Understanding artifact lifecycle management is crucial for implementing robust, auditable network automation. ## Introduction[​](#introduction "Direct link to Introduction") Artifacts in Infrahub are generated outputs based on your infrastructure data and templates. They represent the "rendered truth" of your infrastructure at any point in time. This document explains: * What artifacts are and why they matter * How artifacts flow through generation, storage, and deployment * The relationship between artifacts, templates, and infrastructure data * How version control applies to generated configurations ## What are artifacts?[​](#what-are-artifacts "Direct link to What are artifacts?") ### Definition[​](#definition "Direct link to Definition") An artifact is any generated content that represents infrastructure state: * Device configurations * Compliance reports * Documentation * API payloads * Validation results ### Key characteristics[​](#key-characteristics "Direct link to Key characteristics") Artifacts are: * **Generated**: Created from templates + data, not manually written * **Versioned**: Each generation creates a new version * **Immutable**: Once generated, artifacts don't change * **Traceable**: Linked to the exact data and templates used ### References[​](#references "Direct link to References") For detailed information about artifacts in Infrahub: * [Artifact Topic](https://docs.infrahub.app/topics/artifact) - How artifacts work in Infrahub * [Transformation Topic](https://docs.infrahub.app/topics/transformation) - How transformations generate artifacts * [Repository Configuration Reference](https://docs.infrahub.app/reference/dotinfrahub) - `.infrahub.yml` artifact definition syntax ## Artifact management with Nornir[​](#artifact-management-with-nornir "Direct link to Artifact management with Nornir") ### Task plugin functions[​](#task-plugin-functions "Direct link to Task plugin functions") The Nornir-Infrahub plugin provides three key functions: #### [generate\_artifacts()](/nornir/references/plugins/artifact_tasks.md#generate_artifacts)[​](#generate_artifacts "Direct link to generate_artifacts") Triggers generation for all targets in a definition: ``` # Generate configs for all edge devices nr.run(task=generate_artifacts, artifact="Startup Config for Edge devices") ``` This is typically used: * After template updates * On scheduled intervals * Before deployment windows #### [regenerate\_host\_artifact()](/nornir/references/plugins/artifact_tasks.md#regenerate_host_artifact)[​](#regenerate_host_artifact "Direct link to regenerate_host_artifact") Regenerates artifact for specific hosts: ``` # Regenerate after device-specific change changed_devices = nr.filter(name="atl1-edge1") changed_devices.run(task=regenerate_host_artifact, artifact="Startup Config for Edge devices") ``` Use cases: * After individual device updates * Testing template changes * Troubleshooting specific devices #### [get\_artifact()](/nornir/references/plugins/artifact_tasks.md#get_artifact)[​](#get_artifact "Direct link to get_artifact") Retrieves stored artifact content: ``` # Get current config for deployment result = nr.run(task=get_artifact, artifact="Startup Config for Edge devices") for host, task_result in result.items(): config = task_result.result # Deploy config to device ``` Use cases: * Deploying configurations to devices * Comparing current vs desired state * Feeding into validation pipelines ## Version control integration[​](#version-control-integration "Direct link to Version control integration") ### Branch-aware artifacts[​](#branch-aware-artifacts "Direct link to Branch-aware artifacts") Artifacts are generated per branch: ``` main branch: atl1-edge1 startup-config v3 (current) atl1-edge1 startup-config v2 atl1-edge1 startup-config v1 feature branch: atl1-edge1 startup-config v2 (testing changes) atl1-edge1 startup-config v1 ``` This enables: * Safe testing in feature branches * Comparison between branches * Controlled promotion to production ## Practical workflows[​](#practical-workflows "Direct link to Practical workflows") ### Configuration deployment[​](#configuration-deployment "Direct link to Configuration deployment") 1. **Generate**: Create new configs from current data 2. **Validate**: Check configs before deployment 3. **Deploy**: Push to devices 4. **Verify**: Confirm successful application ### Compliance auditing[​](#compliance-auditing "Direct link to Compliance auditing") 1. **Define**: Create compliance report template 2. **Generate**: Run against all devices 3. **Review**: Identify non-compliant devices 4. **Remediate**: Fix issues and regenerate ### Disaster recovery[​](#disaster-recovery "Direct link to Disaster recovery") 1. **Regular Generation**: Keep artifacts current 2. **Backup Storage**: Replicate artifact store 3. **Rapid Recovery**: Deploy last-known-good configs ## Advanced concepts[​](#advanced-concepts "Direct link to Advanced concepts") ### Conditional generation[​](#conditional-generation "Direct link to Conditional generation") Generate only when needed: ``` # Check if regeneration needed if device.last_modified > artifact.generated_at: nr.run(task=regenerate_host_artifact, artifact="Startup Config for Edge devices") ``` ## Further reading[​](#further-reading "Direct link to Further reading") * [Your First Automation Workflow](/nornir/guides/your-first-automation-workflow.md) - Implement artifact workflows with Nornir * [Artifact Management Plugin Reference](/nornir/references/plugins/artifact_tasks.md) - Full API documentation for artifact tasks --- # Infrahub Inventory Concepts This topic provides a deep understanding of how Infrahub's graph database model maps to Nornir's inventory concepts. Understanding these mappings is essential for designing effective automation workflows. ## Introduction[​](#introduction "Direct link to Introduction") The InfrahubInventory plugin bridges two different paradigms: * **Infrahub**: Graph database with nodes, relationships, and attributes * **Nornir**: Host-centric inventory with groups and connection parameters This document explores: * How graph concepts translate to inventory structures * The role of schema mappings in this translation * Dynamic group generation from relationships * Performance considerations and best practices ## Core mapping concepts[​](#core-mapping-concepts "Direct link to Core mapping concepts") ### Nodes to hosts[​](#nodes-to-hosts "Direct link to Nodes to hosts") In Infrahub, devices are nodes in the graph. The inventory plugin transforms these into Nornir hosts: ``` Infrahub Node Nornir Host ┌─────────────────────┐ ┌─────────────────────┐ │ InfraDevice │ ──▶ │ Host │ │ - id: abc123 │ │ - name: router1 │ │ - name: router1 │ │ - hostname: 10.1.1.1│ │ - primary_address │ │ - platform: ios │ │ └─▶ 10.1.1.1 │ │ - groups: [...] │ └─────────────────────┘ └─────────────────────┘ ``` ### Attributes to properties[​](#attributes-to-properties "Direct link to Attributes to properties") Node attributes become host properties through schema mappings: | Infrahub Attribute | Mapping Path | Nornir Property | | ------------------ | ------------------------- | --------------- | | Direct attribute | `name` | `host.name` | | Nested relation | `primary_address.address` | `host.hostname` | ## The inventory loading process[​](#the-inventory-loading-process "Direct link to The inventory loading process") ### Step 1: query construction[​](#step-1-query-construction "Direct link to Step 1: query construction") The plugin builds a GraphQL query based on your configuration: ``` query { InfraDevice(status__value: "active") { edges { node { id name { value } primary_address { node { address { value } } } site { node { name { value } } } member_of_groups { edges { node { name { value } } } } } } } } ``` ### Step 2: data transformation[​](#step-2-data-transformation "Direct link to Step 2: data transformation") Retrieved data undergoes transformation: 1. **Flatten nested structures**: Navigate relationships to extract values 2. **Apply type conversions**: Convert IP objects to strings 3. **Handle missing data**: Use defaults or skip mappings 4. **Preserve metadata**: Store full node data for tasks ### Step 3: group generation[​](#step-3-group-generation "Direct link to Step 3: group generation") Groups are created from: 1. **Infrahub group memberships**: Devices assigned to `CoreStandardGroup` in Infrahub 2. **Dynamic groups**: Generated from `group_mappings` configuration 3. **Hierarchical groups**: Groups can have parent groups config.yaml ``` group_mappings: - "site.name" - "role.name" ``` ``` # For a device where site.name = "chicago" and role.name = "spine" # Results in Nornir groups: host.groups = [ "core_router", # Infrahub group membership "site__chicago", # dynamic groups from group_mappings "role__spine" # dynamic groups from group_mappings ] ``` ## Advanced inventory concepts[​](#advanced-inventory-concepts "Direct link to Advanced inventory concepts") ### Filtering at source[​](#filtering-at-source "Direct link to Filtering at source") Reduce data transfer by filtering in Infrahub: ``` host_node: kind: "InfraDevice" filters: status__value: "active" site__name__values: ["chicago", "dallas"] role__value: "spine" ``` This translates to GraphQL filters, ensuring only relevant nodes are fetched. Use `__value` for single values and `__values` for lists. ### Include optimization[​](#include-optimization "Direct link to Include optimization") Minimize query complexity with selective includes: ``` host_node: kind: "InfraDevice" include: - "name" - "primary_address" - "platform" # Only fetch what you need ``` ### Relationship cardinality[​](#relationship-cardinality "Direct link to Relationship cardinality") Understanding cardinality is crucial: * **One-to-One**: Device → Primary Address (direct mapping) * **One-to-Many**: Device → Interfaces (requires special handling) * **Many-to-Many**: Devices ↔ Services (complex relationships) Currently, the plugin handles one-to-one and many-to-one relationships directly. ## Performance considerations[​](#performance-considerations "Direct link to Performance considerations") ### Query optimization[​](#query-optimization "Direct link to Query optimization") Large inventories require optimization: 1. **Pagination**: Automatic handling of large result sets 2. **Selective fetching**: Only request needed attributes 3. **Relationship depth**: Limit traversal depth ### Inventory loading[​](#inventory-loading "Direct link to Inventory loading") Nornir loads the inventory once during initialization: ``` # Inventory is fetched from Infrahub during initialization nr = InitNornir(config_file="config.yaml") # Subsequent operations use the loaded inventory nr.filter(platform="ios") # No new query nr.run(task=my_task) # Uses loaded inventory ``` ### Parallel processing[​](#parallel-processing "Direct link to Parallel processing") Inventory loading is optimized for parallel task execution: * Host data is independent * Groups are pre-computed * No shared state between hosts ## Dynamic inventory patterns[​](#dynamic-inventory-patterns "Direct link to Dynamic inventory patterns") ### Environment-based inventory[​](#environment-based-inventory "Direct link to Environment-based inventory") Use branches for environments: ``` def get_inventory(environment="production"): branch_map = { "production": "main", "staging": "staging", "development": "dev" } return InitNornir( inventory={ "plugin": "InfrahubInventory", "options": { "host_node": {"kind": "InfraDevice"}, "branch": branch_map.get(environment, "main") } } ) ``` ### Role-based filtering[​](#role-based-filtering "Direct link to Role-based filtering") Create specialized inventories: ``` # Core network devices only core_nr = InitNornir( inventory={ "plugin": "InfrahubInventory", "options": { "host_node": { "kind": "InfraDevice", "filters": { "role__values": ["core", "edge"] } } } } ) ``` ### Multi-platform support[​](#multi-platform-support "Direct link to Multi-platform support") Handle different device platforms: config.yaml ``` schema_mappings: - name: "hostname" mapping: "primary_address.address" - name: "platform" mapping: "platform.nornir_platform" # Groups by platform automatically group_mappings: - "platform.name" ``` ## Troubleshooting inventory issues[​](#troubleshooting-inventory-issues "Direct link to Troubleshooting inventory issues") ### Debug mode[​](#debug-mode "Direct link to Debug mode") Enable detailed logging: ``` import logging logging.basicConfig(level=logging.DEBUG) nr = InitNornir(config_file="config.yaml") # Watch for GraphQL queries and responses ``` ### Validation queries[​](#validation-queries "Direct link to Validation queries") Test your mappings with GraphQL using the Infrahub GraphQL explorer or SDK: ``` import asyncio from infrahub_sdk import InfrahubClient async def test_query(): client = InfrahubClient(address="http://localhost:8000") query = """ query { InfraDevice { edges { node { name { value } primary_address { node { address { value } } } } } } } """ result = await client.execute_graphql(query=query) print(result) asyncio.run(test_query()) ``` ### Common issues[​](#common-issues "Direct link to Common issues") 1. **Missing Hosts**: Check filters and node kind 2. **Empty Groups**: Verify relationship paths exist 3. **Mapping Errors**: Ensure attributes are populated 4. **Performance**: Reduce included attributes ## Integration with Nornir ecosystem[​](#integration-with-nornir-ecosystem "Direct link to Integration with Nornir ecosystem") ### Transform functions[​](#transform-functions "Direct link to Transform functions") Nornir provides a `transform_function` mechanism to enrich host data during inventory loading. Register a transform function and reference it by name: ``` from nornir import InitNornir from nornir.core.plugins.inventory import TransformFunctionRegister def enrich_host(host): """Transform function called for each host during inventory loading.""" # Add computed properties based on naming conventions host.data["is_core"] = "core" in host.name host.data["is_edge"] = "edge" in host.name # Derive datacenter from hostname prefix if "-" in host.name: host.data["datacenter"] = host.name.split("-")[0] # Register the transform function TransformFunctionRegister.register("enrich_host", enrich_host) # Use transform_function in InitNornir nr = InitNornir( inventory={ "plugin": "InfrahubInventory", "options": { "address": "http://localhost:8000", "host_node": {"kind": "InfraDevice"} }, "transform_function": "enrich_host" } ) # Filter using enriched data core_devices = nr.filter(filter_func=lambda h: h.data.get("is_core", False)) print(f"Found {len(core_devices.inventory.hosts)} core devices") ``` ### Using Infrahub node data in tasks[​](#using-infrahub-node-data-in-tasks "Direct link to Using Infrahub node data in tasks") Access the full Infrahub node object within tasks for attributes not mapped to Nornir properties: ``` from nornir.core.task import Task, Result def device_audit(task: Task) -> Result: """Audit device using both Nornir properties and Infrahub data.""" # Access standard Nornir host properties hostname = task.host.hostname platform = task.host.platform # Access the full Infrahub node for additional attributes infrahub_node = task.host.data.get("InfrahubNode") audit_info = { "name": task.host.name, "hostname": hostname, "platform": platform, } # Extract additional Infrahub attributes if available if infrahub_node: if hasattr(infrahub_node, "role") and infrahub_node.role: audit_info["role"] = infrahub_node.role.value return Result(host=task.host, result=audit_info) # Run the audit task results = nr.run(task=device_audit) ``` ### Custom inventory plugins[​](#custom-inventory-plugins "Direct link to Custom inventory plugins") Create a custom inventory class to apply transformations during loading: ``` from nornir_infrahub.plugins.inventory.infrahub import InfrahubInventory from nornir.core.plugins.inventory import InventoryPluginRegister class CustomInfrahubInventory(InfrahubInventory): """Custom inventory with platform name normalization.""" def load(self): inventory = super().load() # Normalize platform names for Nornir/NAPALM compatibility platform_map = { "juniper_junos": "junos", "arista_eos": "eos", "cisco_ios": "ios", } for host in inventory.hosts.values(): if host.platform in platform_map: host.platform = platform_map[host.platform] return inventory # Register and use the custom plugin InventoryPluginRegister.register("CustomInfrahubInventory", CustomInfrahubInventory) nr = InitNornir( inventory={ "plugin": "CustomInfrahubInventory", "options": { "address": "http://localhost:8000", "host_node": { "kind": "InfraDevice", "include": ["platform"] }, "schema_mappings": [ {"name": "platform", "mapping": "platform.nornir_platform"} ] } } ) ``` ## Further reading[​](#further-reading "Direct link to Further reading") * [Understanding Nornir-Infrahub Integration](/nornir/topics/understanding-nornir-infrahub-integration.md) - High-level concepts * [Configuring Schema Mappings](/nornir/guides/configuring-schema-mappings.md) - Practical mapping guide * [Infrahub Schema Documentation](https://docs.infrahub.app/topics/schema) - Schema design guide --- # Understanding the Nornir-Infrahub Integration This topic explains the core concepts behind the Nornir-Infrahub integration, how it works, and why it represents a paradigm shift in network automation. ## Introduction[​](#introduction "Direct link to Introduction") The Nornir-Infrahub integration fundamentally changes how network automation handles inventory and configuration management. Instead of maintaining static files or basic databases, this integration leverages Infrahub's graph database and version control capabilities to create a dynamic, versioned, and relationship-aware automation platform. This document answers key questions: * How does Infrahub's data model translate to Nornir's inventory system? * What makes this approach different from traditional inventory management? * How do version control concepts apply to network automation? * What new capabilities does this integration enable? ## Core concepts[​](#core-concepts "Direct link to Core concepts") ### The graph database advantage[​](#the-graph-database-advantage "Direct link to The graph database advantage") Traditional network inventory systems store data in flat files or relational databases. This creates challenges: * **Rigid relationships**: Difficult to model complex network topologies * **Limited context**: Device data exists in isolation * **No history**: Changes overwrite previous state Infrahub's graph database approach solves these issues: ``` ┌─────────────┐ belongs_to ┌──────────┐ │ Device │──────────────────▶│ Site │ └─────────────┘ └──────────┘ │ │ │ has_interface │ located_in ▼ ▼ ┌─────────────┐ ┌──────────┐ │ Interface │ │ Location │ └─────────────┘ └──────────┘ ``` This graph structure naturally represents network relationships, making it straightforward to: * Navigate from devices to their interfaces, sites, and roles * Query based on any relationship or attribute * Maintain referential integrity automatically ### Dynamic inventory generation[​](#dynamic-inventory-generation "Direct link to Dynamic inventory generation") The integration transforms Infrahub's graph data into Nornir's inventory structure in real-time: 1. **Query Infrahub**: Fetch nodes based on configured criteria 2. **Apply mappings**: Transform Infrahub attributes to Nornir properties 3. **Build relationships**: Create groups from Infrahub relationships 4. **Inject metadata**: Include full Infrahub node data for tasks This process happens transparently when you initialize Nornir, ensuring your inventory always reflects the current state in Infrahub. ### Version control for infrastructure[​](#version-control-for-infrastructure "Direct link to Version control for infrastructure") Infrahub brings Git-like version control to infrastructure data: #### Branches[​](#branches "Direct link to Branches") Just as code has development branches, your infrastructure can too: ``` # Work with production data nr_prod = InitNornir( inventory={ "plugin": "InfrahubInventory", "options": { "host_node": {"kind": "InfraDevice"}, "branch": "main" } } ) # Test changes in development nr_dev = InitNornir( inventory={ "plugin": "InfrahubInventory", "options": { "host_node": {"kind": "InfraDevice"}, "branch": "development" } } ) ``` #### Change tracking[​](#change-tracking "Direct link to Change tracking") Every modification in Infrahub is tracked: * Who made the change * When it was made * What was changed * Why (via commit messages) This audit trail is invaluable for compliance and troubleshooting. ## Architecture and design[​](#architecture-and-design "Direct link to Architecture and design") ### Component interaction[​](#component-interaction "Direct link to Component interaction") The integration consists of three main components: 1. **InfrahubInventory Plugin**: Bridges Infrahub and Nornir 2. **Schema Mappings**: Define how Infrahub data maps to Nornir 3. **Task Plugins**: Interact with Infrahub artifacts ``` ┌─────────────┐ HTTP/GraphQL ┌──────────────┐ │ Nornir │◀──────────────────▶│ Infrahub │ │ │ │ │ │ ┌─────────┐ │ │ ┌──────────┐ │ │ │Inventory│ │ │ │ Graph │ │ │ │ Plugin │ │ │ │Database │ │ │ └─────────┘ │ │ └──────────┘ │ │ │ │ │ │ ┌─────────┐ │ │ ┌──────────┐ │ │ │ Task │ │ │ │Artifacts │ │ │ │Plugins │ │ │ │ Storage │ │ │ └─────────┘ │ │ └──────────┘ │ └─────────────┘ └──────────────┘ ``` ### Data flow[​](#data-flow "Direct link to Data flow") 1. **Initialization**: Nornir requests inventory from the plugin 2. **Query**: Plugin queries Infrahub via GraphQL 3. **Transform**: Results are mapped to Nornir's data model 4. **Enrichment**: Additional metadata is preserved 5. **Execution**: Tasks run with access to full Infrahub context ## Further reading[​](#further-reading "Direct link to Further reading") * [Infrahub Inventory Concepts](/nornir/topics/infrahub-inventory-concepts.md) - Deep dive into inventory mechanics * [Artifact Lifecycle Management](/nornir/topics/artifact-lifecycle-management.md) - Understanding configuration artifacts * [Your First Automation Workflow](/nornir/guides/your-first-automation-workflow.md) - Practical implementation guide --- # Executing queries in a batch The Python SDK client allows you to group the execution of multiple queries in a batch. Using a batch to execute code concurrently helps reducing the amount of boilerplate code you would have to write. Additionally a batch allows you to control how many queries can be executed concurrently, preventing you from overwhelming the Infrahub API server. A batch is only available with the async version of the SDK client. ## Using batches[​](#using-batches "Direct link to Using batches") Using a batch is a 3 step process: 1. first, we instantiate a batch object 2. we can then add a tasks to the batch, a task can be any Python `Callable` that returns an `Awaitable`, like a coroutine 3. finally we execute the batch and process the results ``` import asyncio from infrahub_sdk import InfrahubClient async def main(): client = InfrahubClient() batch = await client.create_batch() for tag in ["red", "green", "blue", "yellow", "orange"]: batch.add(task=client.get, kind="BuiltinTag", name__value=tag) async for _, result in batch.execute(): print(result.name.value) if __name__ == "__main__": asyncio.run(main()) ``` Additional arguments passed in the `batch.add` method call, are being passed as arguments to the task when it gets executed. ## Controlling the amount of concurrent queries[​](#controlling-the-amount-of-concurrent-queries "Direct link to Controlling the amount of concurrent queries") By default a batch will execute 5 tasks concurrently, but this can be controlled with the `max_concurrent_execution` configuration variable for the SDK client. ``` import asyncio from infrahub_sdk import Config, InfrahubClient async def main(): client = InfrahubClient(Config(max_concurrent_execution=10)) batch = await client.create_batch() for tag in ["red", "green", "blue", "yellow", "orange"]: batch.add(task=client.get, kind="BuiltinTag", name__value=tag) async for _, result in batch.execute(): print(result.name.value) if __name__ == "__main__": asyncio.run(main()) ``` ## Handling exceptions in tasks[​](#handling-exceptions-in-tasks "Direct link to Handling exceptions in tasks") When a batch executes a task that raises an exception, it will re-raise the exception and this will need to be handled in the calling function. Other tasks that were executing or tasks that have not started yet will not be executed or complete. ``` import asyncio from infrahub_sdk import InfrahubClient async def will_raise(swallowed: bool): raise Exception() async def main(): client = InfrahubClient() batch = await client.create_batch() batch.add(task=client.get, kind="BuiltinTag", name__value="red") batch.add(task=will_raise, swallowed=True) batch.add(task=client.get, kind="BuiltinTag", name__value="green") async for _, result in batch.execute(): print(result.name.value) if __name__ == "__main__": asyncio.run(main()) ``` Alternatively we can choose to return the exception raised in a task. This has the advantage that we can still handle the exception in the calling function and that the other batch tasks will be executed. ``` import asyncio from infrahub_sdk import InfrahubClient async def will_raise(swallowed: bool): raise Exception() async def main(): client = InfrahubClient() batch = await client.create_batch(return_exceptions=True) batch.add(task=client.get, kind="BuiltinTag", name__value="red") batch.add(task=will_raise, swallowed=True) batch.add(task=client.get, kind="BuiltinTag", name__value="green" ) async for _, result in batch.execute(): if isinstance(result, Exception): print("this task has failed") print(result.name.value) if __name__ == "__main__": asyncio.run(main()) ``` ## Adding extra contexts for tasks[​](#adding-extra-contexts-for-tasks "Direct link to Adding extra contexts for tasks") A task can be provided with extra context, through the `node` argument. The goal is to be able to provide more context when a task has completed. This can be especially useful when a task does not return a useful value. Imagine a scenario where we want to create 100 tag objects. The `save` method that we have to call to save the object in the database does not return a useful result. Therefor we will pass the tag object as the node argument to the task, so we can inform the user when the task completes. ``` import asyncio from infrahub_sdk import InfrahubClient async def main(): client = InfrahubClient() batch = await client.create_batch() for idx in range(100): tag = await client.create(kind="BuiltinTag", name=f"tag-{idx}") batch.add(task=tag.save, node=tag, allow_upsert=True) async for node, result in batch.execute(): print(f"{node.name.value} was created in Infrahub succesfully") if __name__ == "__main__": asyncio.run(main()) ``` --- # Branch management The Python SDK provides multiple methods to manage the branches in an Infrahub instance. ## Get a single branch[​](#get-a-single-branch "Direct link to Get a single branch") * Async * Sync ``` from infrahub_sdk import InfrahubClient client = InfrahubClient() branch = await client.branch.get(branch_name="main") ``` ``` from infrahub_sdk import InfrahubClientSync client = InfrahubClientSync() branch = client.branch.get(branch_name="main") ``` ## Get all the branches[​](#get-all-the-branches "Direct link to Get all the branches") * Async * Sync ``` from asyncio import run as aiorun from rich import print as rprint from infrahub_sdk import InfrahubClient async def main(): client = InfrahubClient(address="http://localhost:8000") branches = await client.branch.all() rprint(branches) if __name__ == "__main__": aiorun(main()) ``` ``` from rich import print as rprint from infrahub_sdk import InfrahubClientSync def main(): client = InfrahubClientSync(address="http://localhost:8000") branches = client.branch.all() rprint(branches) if __name__ == "__main__": main() ``` ## Create a branch[​](#create-a-branch "Direct link to Create a branch") * Async * Sync ``` from asyncio import run as aiorun from infrahub_sdk import InfrahubClient async def main(): client = InfrahubClient(address="http://localhost:8000") await client.branch.create(branch_name="new-branch", description="description", sync_with_git=False) print("New branch created") if __name__ == "__main__": aiorun(main()) ``` ``` from infrahub_sdk import InfrahubClientSync def main(): client = InfrahubClientSync(address="http://localhost:8000") client.branch.create(branch_name="new-branch2", description="description", sync_with_git=False) print("New branch created") if __name__ == "__main__": main() ``` ## Rebase a branch[​](#rebase-a-branch "Direct link to Rebase a branch") * Async * Sync ``` from asyncio import run as aiorun from infrahub_sdk import InfrahubClient async def main(): client = InfrahubClient(address="http://localhost:8000") await client.branch.rebase(branch_name="new-branch") if __name__ == "__main__": aiorun(main()) ``` ``` from infrahub_sdk import InfrahubClientSync def main(): client = InfrahubClientSync(address="http://localhost:8000") client.branch.rebase(branch_name="new-branch") if __name__ == "__main__": main() ``` ## Merge a branch[​](#merge-a-branch "Direct link to Merge a branch") * Async * Sync ``` from asyncio import run as aiorun from infrahub_sdk import InfrahubClient async def main(): client = InfrahubClient(address="http://localhost:8000") await client.branch.merge(branch_name="new-branch") if __name__ == "__main__": aiorun(main()) ``` ``` from infrahub_sdk import InfrahubClientSync def main(): client = InfrahubClientSync(address="http://localhost:8000") client.branch.merge(branch_name="new-branch") if __name__ == "__main__": main() ``` ## Delete a branch[​](#delete-a-branch "Direct link to Delete a branch") * Async * Sync ``` from infrahub_sdk import InfrahubClient client = InfrahubClient() await client.branch.delete(branch_name="new-branch") ``` ``` from infrahub_sdk import InfrahubClientSync client = InfrahubClientSync() client.branch.delete(branch_name="new-branch") ``` ## Generating a diff for a branch[​](#generating-a-diff-for-a-branch "Direct link to Generating a diff for a branch") * Async * Sync ``` from infrahub_sdk import InfrahubClient client = await InfrahubClient() diff = await client.branch.diff_data(branch_name="new-branch") ``` ``` from infrahub_sdk import InfrahubClientSync client = InfrahubClientSync() diff = client.branch.diff_data(branch_name="new-branch") ``` --- # How to create and configure an Infrahub client This guide shows you how to create and configure an Infrahub client using the Python SDK. You'll learn how to set up authentication, configure proxy settings, and customize client behavior for your infrastructure automation workflows. At the end of this guide, you'll have a fully configured client ready to interact with your Infrahub instance. TL;DR; If you prefer to jump right in, check out the ["Hello World" example](#hello-world-example) at the end of this guide for a quick reference implementation. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Python 3.9 or higher * Infrahub SDK installed (`pip install infrahub-sdk` or `uv add infrahub-sdk`) * Access to an Infrahub instance (local or remote) * Valid credentials (API token or username/password) if authentication is required ## Step 1: Create a client instance[​](#step-1-create-a-client-instance "Direct link to Step 1: Create a client instance") Asynchronous vs. Synchronous Clients The SDK offers both asynchronous and synchronous client implementations. Choose the one that best fits your application architecture: * **Asynchronous client** (`InfrahubClient`): Ideal for modern async applications using Python's `async`/`await` syntax * **Synchronous client** (`InfrahubClientSync`): Better for traditional synchronous workflows or scripts - Async - Sync Create an `InfrahubClient` for async/await workflows: ``` from infrahub_sdk import InfrahubClient # Connect to local Infrahub instance client = InfrahubClient() ``` Create an `InfrahubClientSync` for traditional synchronous code: ``` from infrahub_sdk import InfrahubClientSync # Connect to local Infrahub instance client = InfrahubClientSync() ``` ## Step 2: Configure authentication and address[​](#step-2-configure-authentication-and-address "Direct link to Step 2: Configure authentication and address") Next, configure the address of your Infrahub instance and set up authentication credentials. Infrahub supports two authentication methods: API tokens and username/password. Configuration Methods You can configure the client in two ways: 1. **Environment variables**: Ideal for sensitive information 2. **`Config` object**: Better for explicit configuration in code You can also combine both methods, where the `Config` object takes precedence over environment variables. You can find the full list of configuration options in the [SDK configuration reference](/python-sdk/reference/config.md). ### Using API tokens[​](#using-api-tokens "Direct link to Using API tokens") API tokens provide secure, long-lived authentication and are the recommended method for most use cases, especially for automation scripts and non-interactive applications. * Async * Sync - Code - Environment We can set the address and API token directly in the `Config` object: ``` from infrahub_sdk import Config, InfrahubClient client = InfrahubClient(config=Config(address="http://localhost:8000", api_token="token")) ``` We need to export the address and API token as an environment variable: ``` export INFRAHUB_ADDRESS="http://localhost:8000" export INFRAHUB_API_TOKEN="token" ``` ``` from infrahub_sdk import InfrahubClient client = InfrahubClient() # token and address are read from the INFRAHUB_API_TOKEN and INFRAHUB_ADDRESS environment variables ``` * Code * Environment We can set the address and API token directly in the `Config` object: ``` from infrahub_sdk import Config, InfrahubClientSync client = InfrahubClientSync(config=Config(address="http://localhost:8000", api_token="token")) ``` We need to export the address and API token as an environment variable: ``` export INFRAHUB_ADDRESS="http://localhost:8000" export INFRAHUB_API_TOKEN="token" ``` ``` from infrahub_sdk import InfrahubClientSync client = InfrahubClientSync() # token and address are read from the INFRAHUB_API_TOKEN and INFRAHUB_ADDRESS environment variables ``` ### Using username/password[​](#using-usernamepassword "Direct link to Using username/password") For interactive authentication, use username and password. The SDK automatically handles JWT token generation and refresh. * Async * Sync - Code - Environment We can set the address, username and password directly in the `Config` object: ``` from infrahub_sdk import Config, InfrahubClient client = InfrahubClient(config=Config(address="http://localhost:8000", username="admin", password="infrahub")) ``` We need to export the address, username and password as environment variables: ``` export INFRAHUB_ADDRESS="http://localhost:8000" export INFRAHUB_USERNAME="admin" export INFRAHUB_PASSWORD="infrahub" ``` ``` from infrahub_sdk import InfrahubClient client = InfrahubClient() # username and password are read from the INFRAHUB_USERNAME and INFRAHUB_PASSWORD environment variables ``` * Code * Environment We can set the address, username and password directly in the `Config` object: ``` from infrahub_sdk import Config, InfrahubClientSync client = InfrahubClientSync(config=Config(address="http://localhost:8000", username="admin", password="infrahub")) ``` We need to export the address, username and password as environment variables: ``` export INFRAHUB_ADDRESS="http://localhost:8000" export INFRAHUB_USERNAME="admin" export INFRAHUB_PASSWORD="infrahub" ``` ``` from infrahub_sdk import InfrahubClientSync client = InfrahubClientSync() # username and password are read from the INFRAHUB_USERNAME and INFRAHUB_PASSWORD environment variables ``` success Your client is now configured and ready to use! ## Step 3: Set the default branch (optional)[​](#step-3-set-the-default-branch-optional "Direct link to Step 3: Set the default branch (optional)") Configure your client to work with a specific Infrahub branch instead of the default `main` branch. This is particularly useful when working on feature branches or experimental changes, as it eliminates the need to specify the branch name in every method call. * Code * Environment ``` from infrahub_sdk import InfrahubClient, Config config = Config(address="http://localhost:8000", api_token="token", default_branch="other-branch") client_other_branch = InfrahubClient(config=config) tag_other_branch = await client_other_branch.get(kind="BuiltinTag", name__value="RED") tag_main_branch = await client_other_branch.get(kind="BuiltinTag", name__value="RED", branch="main") ``` ``` export INFRAHUB_ADDRESS="http://localhost:8000" export INFRAHUB_API_TOKEN="token" export INFRAHUB_DEFAULT_BRANCH="other-branch" ``` ``` from infrahub_sdk import InfrahubClient client_other_branch = InfrahubClient() tag_other_branch = await client_other_branch.get(kind="BuiltinTag", name__value="RED") tag_main_branch = await client_other_branch.get(kind="BuiltinTag", name__value="RED", branch="main") ``` Branch Configuration Your client is now configured to use the specified default branch instead of `main`. Remember that you can always override this setting by explicitly passing the `branch` argument to any method call. ## Hello world example[​](#hello-world-example "Direct link to Hello world example") Let's create a "Hello World" example to verify your client configuration works correctly. This example will connect to your Infrahub instance and query the available accounts. 1. Create a new file called `hello_world.py`: ``` touch hello_world.py ``` File Naming Avoid naming your script `test.py` as this name could conflict with Python's testing modules. 2. Add the following code to `hello_world.py`: * Async * Sync ``` import asyncio from infrahub_sdk import Config, InfrahubClient async def hello_world(): client = InfrahubClient(config=Config(address="http://localhost:8000")) # Try to query accounts to validate connection try: accounts = await client.all(kind="CoreAccount") print(f"Successfully connected to Infrahub! Found {len(accounts)} account(s)") except Exception as e: print(f"Something went wrong: {e}") asyncio.run(hello_world()) ``` ``` from infrahub_sdk import Config, InfrahubClientSync # Test connection and authentication def hello_world(): client = InfrahubClientSync(config=Config(address="http://localhost:8000")) # Try to query accounts to validate connection try: accounts = client.all(kind="CoreAccount") print(f"Successfully connected to Infrahub! Found {len(accounts)} account(s)") except Exception as e: print(f"Something went wrong: {e}") hello_world() ``` 3. Export the necessary environment variables: ``` export INFRAHUB_API_TOKEN="06438eb2-8019-4776-878c-0941b1f1d1ec" # This is the default token ``` 4. Run the script: ``` python hello_world.py ``` If everything is configured correctly, you should see: success `Successfully connected to Infrahub! Found 1 account(s)` ## Advanced use cases[​](#advanced-use-cases "Direct link to Advanced use cases") ### Enable debug logging[​](#enable-debug-logging "Direct link to Enable debug logging") When developing or troubleshooting, you can enable GraphQL query logging to see exactly what requests are being sent to Infrahub: * Code * Environment ``` from infrahub_sdk import Config, InfrahubClient config = Config(echo_graphql_queries=True) client = InfrahubClient(config=config) ``` ``` export INFRAHUB_ECHO_GRAPHQL_QUERIES=true ``` ``` from infrahub_sdk import InfrahubClient client = InfrahubClient() # debug flag state is read from the INFRAHUB_ECHO_GRAPHQL_QUERIES environment variables ``` ### Configure proxy settings[​](#configure-proxy-settings "Direct link to Configure proxy settings") When operating in environments with restricted network access or behind corporate firewalls, you can configure the client to route requests through HTTP/HTTPS proxies. #### Single proxy for all requests[​](#single-proxy-for-all-requests "Direct link to Single proxy for all requests") To route all traffic through the same proxy server, use the `INFRAHUB_PROXY` environment variable or the `proxy` configuration parameter: * Async * Sync ``` from infrahub_sdk import Config, InfrahubClient config = Config(proxy="http://proxy.example.com:8080") client = InfrahubClient(config=config) # Or using environment variable # export INFRAHUB_PROXY=http://proxy.example.com:8080 client = InfrahubClient() ``` ``` from infrahub_sdk import Config, InfrahubClientSync config = Config(proxy="http://proxy.example.com:8080") client = InfrahubClientSync(config=config) # Or using environment variable # export INFRAHUB_PROXY=http://proxy.example.com:8080 client = InfrahubClientSync() ``` #### Separate proxies for HTTP and HTTPS[​](#separate-proxies-for-http-and-https "Direct link to Separate proxies for HTTP and HTTPS") In some network environments, you might need to route HTTP and HTTPS traffic through different proxy servers. Use the `INFRAHUB_PROXY_MOUNTS_HTTP` and `INFRAHUB_PROXY_MOUNTS_HTTPS` environment variables for this purpose: ``` export INFRAHUB_PROXY_MOUNTS_HTTP=http://http-proxy.example.com:8080 export INFRAHUB_PROXY_MOUNTS_HTTPS=http://https-proxy.example.com:8080 ``` * Async * Sync ``` from infrahub_sdk import InfrahubClient # Proxy configuration is read from environment variables client = InfrahubClient() ``` ``` from infrahub_sdk import InfrahubClientSync # Proxy configuration is read from environment variables client = InfrahubClientSync() ``` Using both `proxy` and `proxy_mounts` The `proxy` and `proxy_mounts` configurations are mutually exclusive and cannot be used together. Specifying both will cause a `ValueError` to be raised when the client is initialized. ## Next steps[​](#next-steps "Direct link to Next steps") Now that you have a fully configured Infrahub client, you're ready to start working with your infrastructure data. Here's what you can explore next: * **[Query objects and data](/python-sdk/guides/query_data.md)**: Learn how to retrieve objects and data from your Infrahub instance * **[Create, update and delete objects](/python-sdk/guides/create_update_delete.md)**: Manage your infrastructure data programmatically * **[Work with branches](/python-sdk/guides/branches.md)**: Use Infrahub's branch-based workflow to manage infrastructure changes * **[Batch operations](/python-sdk/guides/batch.md)**: Optimize performance with batch operations for multiple objects ## Related resources[​](#related-resources "Direct link to Related resources") * **[SDK Configuration Reference](/python-sdk/reference/config.md)**: Complete list of all configuration options * **[Installation Guide](/python-sdk/guides/installation.md)**: Detailed instructions for installing the SDK * **[Python Typing Guide](/python-sdk/guides/python-typing.md)**: Improve your development experience with type hints * **[Tracking Guide](/python-sdk/guides/tracking.md)**: Learn how to monitor and trace SDK operations --- # Create, update and deleting nodes We will be using the following schema in this guide: ``` --- version: "1.0" nodes: - name: Device namespace: Test attributes: - name: name kind: Text unique: true optional: false relationships: - name: tags cardinality: many kind: Attribute peer: BuiltinTag optional: true - name: site cardinality: one kind: Attribute peer: TestSite optional: false - name: interfaces cardinality: many kind: Component peer: TestInterface optional: false - name: Site namespace: Test attributes: - name: name kind: Text unique: true optional: false relationships: - name: device cardinality: many kind: Attribute peer: TestDevice optional: true - name: Interface namespace: Test attributes: - name: name kind: Text unique: true optional: false relationships: - name: device cardinality: one kind: Parent peer: TestDevice optional: true ``` ## Creating a node[​](#creating-a-node "Direct link to Creating a node") A node can be created using the `create` method. The `create` method will first construct a `InfrahubNode` object in memory. This `InfrahubNode` object will then need to be saved into Infrahub using the `save` method. The attributes and relationships of the `InfrahubNode` you want to create can be passed as arguments to the `create` method, or you can pass them using a dictionary. * Async * Sync ``` device = await client.create(kind="TestDevice", name="atl1-edge1") await device.save() ``` ``` device = client.create(kind="TestDevice", name="atl1-edge1") device.save() ``` * Async * Sync ``` data = {"name": "atl1-edge1"} device = await client.create(kind="TestDevice", data=data) await device.save() ``` ``` data = {"name": "atl1-edge1"} device = client.create(kind="TestDevice", data=data) device.save() ``` ### Creating a node with a relation of cardinality one[​](#creating-a-node-with-a-relation-of-cardinality-one "Direct link to Creating a node with a relation of cardinality one") * Async * Sync ``` site = await client.get(kind="TestSite", name__value="atl1") device = await client.create(kind="TestDevice", name="atl1-edge1", site=site) await device.save() ``` ``` site = client.get(kind="TestSite", name__value="atl1") device = client.create(kind="TestDevice", name="atl1-edge1", site=site) device.save() ``` ### Creating a node with a relation of cardinality many[​](#creating-a-node-with-a-relation-of-cardinality-many "Direct link to Creating a node with a relation of cardinality many") * Async * Sync ``` interfaces = await client.filters(kind="TestInterface", name__values=["Ethernet1", "Ethernet2"]) device = await client.create(kind="TestDevice", name="atl1-edge1", interfaces=interfaces) await device.save() ``` ``` interfaces = client.filters(kind="TestInterface", name__values=["Ethernet1", "Ethernet2"]) device = client.create(kind="TestDevice", name="atl1-edge1", interfaces=interfaces) device.save() ``` ## Updating a node[​](#updating-a-node "Direct link to Updating a node") To update a node from Infrahub using the SDK, we first have to retrieve the node using one of the query methods, then update the attributes and/or relations of the node and `save` the node. * Async * Sync ``` interface = await client.get(kind="TestInterface", name__value="Ethernet1") interface.name.value = "Ethernet3" await interface.save() ``` ``` interface = client.get(kind="TestInterface", name__value="Ethernet1") interface.name.value = "Ethernet3" interface.save() ``` ### Updating a relation of cardinality one on a node[​](#updating-a-relation-of-cardinality-one-on-a-node "Direct link to Updating a relation of cardinality one on a node") * Async * Sync ``` site = await client.get(kind="TestSite", name__value="atl2") device = await client.get(kind="TestDevice", name__value="atl1-edge1") device.site = site await device.save() ``` ``` site = client.get(kind="TestSite", name__value="atl2") device = client.get(kind="TestDevice", name__value="atl1-edge1") device.site = site device.save() ``` ### Updating a relation of cardinality many on a node[​](#updating-a-relation-of-cardinality-many-on-a-node "Direct link to Updating a relation of cardinality many on a node") Adding a single relation: * Async * Sync ``` interface = await client.get(kind="TestInterface", name__value="Ethernet1") device = await client.get(kind="TestDevice", name__value="atl1-edge1") device.interfaces.add(interface) await device.save() ``` ``` interface = client.get(kind="TestInterface", name__value="Ethernet1") device = client.get(kind="TestDevice", name__value="atl1-edge1") device.interfaces.add(interface) device.save() ``` Adding multiple relations: * Async * Sync ``` interfaces = await client.filters(kind="TestInterface", name__values=["Ethernet1", "Ethernet2"]) device = await client.get(kind="TestDevice", name__value="atl1-edge1") device.interfaces.extend(interfaces) await device.save() ``` ``` interfaces = client.filters(kind="TestInterface", name__values=["Ethernet1", "Ethernet2"]) device = client.get(kind="TestDevice", name__value="atl1-edge1") device.interfaces.extend(interfaces) device.save() ``` ## Deleting a node[​](#deleting-a-node "Direct link to Deleting a node") To delete a node from Infrahub using the SDK, we first have to retrieve the node using one of the query methods. The `delete` method can then be used on the retrieved `InfrahubNode` object. * Async * Sync ``` device = await client.get(kind="TestDevice", name__value="atl1-edge1") await device.delete() ``` ``` device = client.get(kind="TestDevice", name__value="atl1-edge1") device.delete() ``` ## Upserting a node[​](#upserting-a-node "Direct link to Upserting a node") The word upserting is formed out of the combination of updating and inserting. It refers to the action of creating a node if it does not yet exist, or to update the existing node if it already exists. We first have to create the node in memory, then use the `save` method on the node with the `allow_upsert` argument set to `True`. * Async * Sync ``` site = await client.get(kind="TestSite", name="atl1") device = await client.create(kind="TestDevice", name="atl1-edge1") device.site = site await device.save(allow_upsert=True) ``` ``` site = client.get(kind="TestSite", name="atl1") device = client.create(kind="TestDevice", name="atl1-edge1") device.site = site device.save(allow_upsert=True) ``` --- # Traversing the graph Graph traversal lets you discover how nodes are connected without knowing the relationship path in advance. The SDK exposes two client methods: * `traverse_paths` — find the shortest path(s) between a **source** and a **destination** node. * `reachable_nodes` — find every node of given **kinds** that is reachable from a **source** node, with the path used to reach each one. note Graph traversal requires **Infrahub 1.10 or later**. Calling either method against an older server raises `VersionNotSupportedError`. ## Finding the path between two nodes[​](#finding-the-path-between-two-nodes "Direct link to Finding the path between two nodes") Pass the source and destination as node UUID strings **or** `InfrahubNode` instances (the SDK reads the node's id for you). Paths are returned shortest-first. Each path is a list of `hops`, and each hop exposes the `node` visited and the `relationship` traversed to reach it (the first hop's `relationship` is `None`). ``` # both of these work — and you can mix them device = await client.get(kind="InterfacePhysical", id=src_id) await client.traverse_paths(device, dst_id) # node + id await client.traverse_paths(src_id, dst_id) # id + id ``` * Async * Sync ``` result = await client.traverse_paths( source="1891a122-8875-bae7-3866-10658751d7cc", destination="1891a12b-27e5-fe3e-386c-1065983045b0", max_depth=8, ) print(f"{result.count} path(s) found") for path in result.paths: for hop in path.hops: arrow = f" --[{hop.relationship.from_rel}]--> " if hop.relationship else "" print(f"{arrow}{hop.node.display_label}", end="") print() ``` ``` result = client.traverse_paths( source="1891a122-8875-bae7-3866-10658751d7cc", destination="1891a12b-27e5-fe3e-386c-1065983045b0", max_depth=8, ) print(f"{result.count} path(s) found") ``` ### Constraining the traversal[​](#constraining-the-traversal "Direct link to Constraining the traversal") All limits and filters are optional; when omitted, the server applies its own defaults. By default the server excludes IP namespaces — they act as hubs and are rarely useful for path tracing — along with internal namespaces such as `Builtin`, `Internal`, and `Core`. The excluded kinds are reported back in `result.excluded_kinds`, and any of them can be brought back into the traversal with `included_kinds`. The kind filters (`kind_filter`, `excluded_kinds`, `included_kinds`, and `target_kinds` on `reachable_nodes`) accept either kind-name strings or generated protocol classes — mix them freely: ``` from infrahub_sdk.protocols import DcimCable, InterfacePhysical result = await client.traverse_paths( source=src, destination=dst, kind_filter=[DcimCable, InterfacePhysical, "DcimFrontPatchPanelInterface"], ) ``` note `relationship_filter` matches the schema **relationship identifier** (the canonical name shared by both sides of a relationship, for example `dcimconnector__dcimendpoint`) — **not** the per-side `from_rel` / `to_rel` names shown in the result. Find identifiers via the schema: `(await client.schema.get(kind="InterfacePhysical")).relationships[i].identifier`. `shortest_paths_only` controls how many paths are considered through each intermediate node. When `True` (the server default), only the shortest path through each intermediate object is returned; when `False`, every loopless path up to `max_paths` is returned (exhaustive mode). * Async * Sync ``` result = await client.traverse_paths( source=src, destination=dst, max_depth=10, max_paths=5, kind_filter=["DcimCable", "InterfacePhysical"], # only traverse through these kinds relationship_filter=["dcimconnector__dcimendpoint"], # schema relationship identifier included_kinds=["IpamIPPrefix"], # re-include a default-excluded kind shortest_paths_only=False, # return all loopless paths, not just the shortest ) ``` ``` result = client.traverse_paths( source=src, destination=dst, max_depth=10, max_paths=5, kind_filter=["DcimCable", "InterfacePhysical"], relationship_filter=["dcimconnector__dcimendpoint"], included_kinds=["IpamIPPrefix"], shortest_paths_only=False, ) ``` ## Checking that a path exists[​](#checking-that-a-path-exists "Direct link to Checking that a path exists") A common use in an Infrahub check is "is A still connected to B?". `path_exists` answers that in one call — it requests a single path (the cheapest way to know) and returns a boolean. It takes the same source/destination and filter arguments as `traverse_paths`. * Async * Sync ``` connected = await client.path_exists( device_a, device_b, max_depth=8, kind_filter=["DcimCable", "InterfacePhysical"], ) if not connected: self.log_error(message=f"No path between {device_a.display_label} and {device_b.display_label}") ``` ``` connected = client.path_exists(device_a, device_b, max_depth=8) if not connected: self.log_error(message="Expected path is missing") ``` ## Discovering reachable nodes[​](#discovering-reachable-nodes "Direct link to Discovering reachable nodes") Use `reachable_nodes` for impact or dependency analysis: "what nodes of these kinds can I reach from here?" Each entry includes the reachable `node`, the `depth` at which it was found, and the full `path` from the source. * Async * Sync ``` result = await client.reachable_nodes( source=device_id, target_kinds=["DcimCable", "InfraCircuit"], max_depth=5, max_results=100, shortest_paths_only=True, ) for dep in result.dependencies: print(f"{dep.node.kind:20} {dep.node.display_label} (depth {dep.depth})") ``` ``` result = client.reachable_nodes( source=device_id, target_kinds=["DcimCable", "InfraCircuit"], max_depth=5, max_results=100, shortest_paths_only=True, ) ``` ## Working with the results[​](#working-with-the-results "Direct link to Working with the results") Results are typed objects, not raw dictionaries. A returned `PathNode` is a lightweight identity (`id`, `kind`, `label`, `display_label`, `hfid`) — it does not carry the node's attributes or relationships. To get the full node, call `.fetch()` on any `PathNode`. It resolves the node through the same client (and branch) that produced the traversal and adds it to the client store, so fetching the same id again is served from the store. * Async * Sync ``` for hop in result.paths[0].hops: print(hop.node.display_label) # identity, no request full = await hop.node.fetch() # resolve the full node print(full.name.value) ``` ``` for hop in result.paths[0].hops: print(hop.node.display_label) # identity, no request full = hop.node.fetch() # resolve the full node print(full.name.value) ``` note `.fetch()` issues one request per node. For large results where you need many full nodes, prefer fetching only the ones you actually need, or batch them with `client.get` inside an `InfrahubBatch`. ### Detecting truncated results[​](#detecting-truncated-results "Direct link to Detecting truncated results") The server caps the number of results. Note that `result.count` is the number of *paths* returned, not the number of distinct nodes — when several paths lead to the same node, one node is reported multiple times. To check whether `max_results` (the cap on distinct target nodes) was reached, count the distinct nodes by their UUID: ``` result = await client.reachable_nodes(source=src, target_kinds=["InfraDevice"], max_results=50) reached = {dep.node.id for dep in result.dependencies} if len(reached) >= 50: print("Result may be truncated — increase max_results to see more.") ``` For `traverse_paths`, the `PathTraversalResult` reports truncation directly via `result.truncated_at_depth`. It is `None` when the search completed within budget; otherwise it is the depth at which the server ran out of budget. In that case the returned paths are complete only for depths *less than* that value, and deeper paths may exist. ``` result = await client.traverse_paths(source=src, destination=dst) if result.truncated_at_depth is not None: print(f"Search ran out of budget at depth {result.truncated_at_depth}; deeper paths may exist.") ``` ## Common check patterns[​](#common-check-patterns "Direct link to Common check patterns") These are the recurring questions graph traversal answers in an Infrahub check. The examples are async; the sync client mirrors them without `await`. **Connectivity required** — A must reach B: ``` if not await client.path_exists(a, b): self.log_error(message="A is no longer connected to B") ``` **Isolation / segmentation** — A must *not* reach B (the same primitive, negated): ``` if await client.path_exists(a, b): self.log_error(message="A and B must remain isolated") ``` **Path must avoid a kind** — no path may pass through a given kind: ``` if not await client.path_exists(a, b, excluded_kinds=["LabReservedThing"]): self.log_error(message="No compliant path that avoids lab-reserved nodes") ``` **Path must traverse a kind** — every A→B path must pass through, for example, a firewall: ``` result = await client.traverse_paths(a, b) for path in result.paths: if not any(hop.node.kind == "SecurityFirewall" for hop in path.hops): self.log_error(message="Found a path that bypasses the firewall") ``` **Within N hops** — A and B must be no more than 3 hops apart: ``` result = await client.traverse_paths(a, b, max_depth=3) if not result.paths: self.log_error(message="A and B are more than 3 hops apart (or not connected)") ``` **Reach all required targets** — several required services must each be reachable from a device: ``` for service_id in dns_and_ntp_ids: if not await client.path_exists(device, service_id): self.log_error(message=f"{device.display_label} cannot reach required service {service_id}") ``` The patterns above use `path_exists` because they ask about a *specific* destination. When the check is about a *kind* of node rather than a specific one, use `reachable_nodes` instead. **Reach at least one node of a kind** — a device must be able to reach some DNS server: ``` result = await client.reachable_nodes(device, target_kinds=["NetworkDnsServer"], max_results=1) if result.count == 0: self.log_error(message=f"{device.display_label} cannot reach any DNS server") ``` **Redundancy by kind** — at least two NTP servers must be reachable: ``` result = await client.reachable_nodes(device, target_kinds=["NetworkNtpServer"]) if result.count < 2: self.log_error(message=f"{device.display_label} reaches only {result.count} NTP server(s); expected at least 2") ``` **Forbidden reach (blast radius / segmentation)** — a device must not reach any sensitive asset: ``` result = await client.reachable_nodes(device, target_kinds=["SecuritySensitiveAsset"], max_results=1) if result.count: self.log_error(message=f"{device.display_label} can reach a sensitive asset it should be isolated from") ``` **Report dependencies** — enumerate what a node depends on, with how far away each is: ``` result = await client.reachable_nodes(device, target_kinds=["InfraCircuit", "DcimCable"]) for dep in result.dependencies: self.log_info(message=f"depends on {dep.node.display_label} ({dep.node.kind}) at depth {dep.depth}") ``` ## Handling older servers[​](#handling-older-servers "Direct link to Handling older servers") ``` from infrahub_sdk.exceptions import VersionNotSupportedError try: result = await client.traverse_paths(source=src, destination=dst) except VersionNotSupportedError as exc: print(exc) # Graph path traversal requires Infrahub 1.10 or later. ``` note An empty result is **not** an error: when no path or no reachable node exists within the limits, `count` is `0` and the list is empty. A request for a node that does not exist raises a `GraphQLError`. --- # Installing infrahub-sdk The Infrahub SDK for Python is available on [PyPI](https://pypi.org/project/infrahub-sdk/) and can be installed using the pip package installer. It is recommended to install the SDK into a virtual environment. ``` python3 -m venv .venv source .venv/bin/activate pip install infrahub-sdk ``` ## Installing optional extras[​](#installing-optional-extras "Direct link to Installing optional extras") Extras can be installed as part of the Python SDK and are not installed by default. ### ctl[​](#ctl "Direct link to ctl") The `ctl` extra provides the `infrahubctl` command, which allows you to interact with an Infrahub instance. ``` pip install 'infrahub-sdk[ctl]' ``` ### tests[​](#tests "Direct link to tests") The `tests` extra provides all the components for the testing framework of Transforms, Queries and Checks. ``` pip install 'infrahub-sdk[tests]' ``` ### all[​](#all "Direct link to all") Installs `infrahub-sdk` together with all the extras. ``` pip install 'infrahub-sdk[all]' ``` --- # Using the object-storage The Python SDK can be used to interface with Infrahub's [object-storage](/artifact-file-storage/overview.md). ## Storing string objects to the object-storage[​](#storing-string-objects-to-the-object-storage "Direct link to Storing string objects to the object-storage") We can use the SDK to store string objects into the object-storage. ``` import json from infrahub_sdk import InfrahubClientSync client = InfrahubClientSync() data = { "key": "value", "list": [ "item1", "item2", "item3", ] } response = client.object_store.upload(content=json.dumps(data)) print(response) ``` ## Retrieving objects from the object-storage[​](#retrieving-objects-from-the-object-storage "Direct link to Retrieving objects from the object-storage") Retrieving objects from the object-storage can be done using the object's identifier. In this step we will be using the identifier that we got as a response to the previous step. ``` import json from infrahub_sdk import InfrahubClientSync client = InfrahubClientSync() identifier = "17d19a20-70f6-3e44-3341-c5124065cda9" response = client.object_store.get(identifier="17d19a20-70f6-3e44-3341-c5124065cda9") print(json.loads(response)) ``` ## Storing a file to the object-storage[​](#storing-a-file-to-the-object-storage "Direct link to Storing a file to the object-storage") You can store text files in the object-storage. We have to read the contents of the file into a string object, which we can then store in the object-storage. ``` import json from pathlib import Path from infrahub_sdk import InfrahubClientSync data = { "key": "value", "list": [ "item1", "item2", "item3", ] } json_file = Path("/tmp/file.json") json_file.write_text(json.dumps(data)) client = InfrahubClientSync() identifier = "17d19a20-70f6-3e44-3341-c5124065cda9" response = client.object_store.upload(content=json_file.read_text()) print(response) ``` --- # Overview This guide explains how to use Python's type system effectively with the Infrahub SDK, focusing on the use of Protocols for type-safe development. What is Python Typing Python typing allows you to specify the expected data types of variables, function arguments, and return values to improve code clarity and catch bugs early. ``` # Basic type hints def percentage(num1: int, num2: int) -> float: return (num1 / num2) * 100 ``` ## Leveraging Python protocols[​](#leveraging-python-protocols "Direct link to Leveraging Python protocols") The Python SDK for Infrahub has been designed to automatically work with any schemas loaded into Infrahub. Internally, the Python SDK generates dynamic Python representations of your schemas. While this approach improves code readability, it presents challenges with type checking because each object has a different signature based on your schema. ### Without protocols[​](#without-protocols "Direct link to Without protocols") In the example below, type checkers like Mypy will typically complain about `blue_tag.description.value` because `description` is a dynamic parameter generated by the SDK. ``` # Type checker cannot verify the existence of 'description' blue_tag = client.get("BuiltinTag", name__value="blue") # blue_tag is of type InfrahubNode or InfrahubNodeSync blue_tag.description.value = "The blue tag" # Mypy: error: "InfrahubNode" has no attribute "description" blue_tag.save() ``` ### With protocols[​](#with-protocols "Direct link to With protocols") To provide strict type checking while maintaining platform extensibility, the Python SDK integrates with Python Protocols. For all core and internal models, the protocols are included in the SDK under `infrahub_sdk.protocols`. Whenever you need to specify the kind of object you're working with as a string, you can use the corresponding protocol instead. ``` from infrahub_sdk.protocols import BuiltinTag # Type checker can now verify all attributes blue_tag = client.get(BuiltinTag, name__value="blue") # blue_tag is of type BuiltinTag blue_tag.description.value = "The blue tag" # No type errors blue_tag.save() ``` Python Protocols Python Protocols, introduced in PEP 544, define a set of method and property signatures that a class must implement to be considered a match, enabling structural subtyping (also known as "duck typing" with static checks). They allow you to specify behavior without requiring inheritance, making code more flexible and type-safe. More information about Python Protocols can be found [here](https://typing.python.org/en/latest/spec/protocol.html) ## Generating custom protocols based on your schema[​](#generating-custom-protocols-based-on-your-schema "Direct link to Generating custom protocols based on your schema") You can generate Python Protocols for your own models using the `infrahubctl protocols` command. This supports both synchronous and asynchronous Python code. It's possible to provide the schema from a local directory or from an existing Infrahub Instance. * Existing Infrahub Instance * Local Directory ``` export INFRAHUB_ADDRESS=https://infrahub.example.com infrahubctl protocols --out lib/protocols.py --sync ``` ``` infrahubctl protocols --schemas schemas/tag.schema.yml --out lib/protocols.py ``` > When using a local directory, Protocols for Profiles and Object Templates won't be generated. ## Using custom protocols[​](#using-custom-protocols "Direct link to Using custom protocols") After generation, you can import and use your custom protocols as describe below. ``` from lib.protocols import MyOwnObject # Use your custom protocol my_object = client.get(MyOwnObject, name__value="example") ``` > if you don't have your own Python module, it's possible to use relative path by having the `protocols.py` in the same directory as your script/transform/generator ## Generating Pydantic models from GraphQL queries[​](#generating-pydantic-models-from-graphql-queries "Direct link to Generating Pydantic models from GraphQL queries") When working with GraphQL queries, you can generate type-safe Pydantic models that correspond to your query return types. This provides excellent type safety and IDE support for your GraphQL operations. ### Why use generated return types?[​](#why-use-generated-return-types "Direct link to Why use generated return types?") Generated Pydantic models from GraphQL queries offer several important benefits: * **Type Safety**: Catch type errors during development time instead of at runtime * **IDE Support**: Get autocomplete, type hints, and better IntelliSense in your IDE * **Documentation**: Generated models serve as living documentation of your GraphQL API * **Validation**: Automatic validation of query responses against the expected schema ### Generating return types[​](#generating-return-types "Direct link to Generating return types") Use the `infrahubctl graphql generate-return-types` command to create Pydantic models from your GraphQL queries: ``` # Generate models for queries in a directory infrahubctl graphql generate-return-types queries/ # Generate models for specific query files infrahubctl graphql generate-return-types queries/get_tags.gql ``` > You can also export the GraphQL schema first using the `infrahubctl graphql export-schema` command. ### Example workflow[​](#example-workflow "Direct link to Example workflow") 1. **Create your GraphQL queries** in `.gql` files preferably in a directory (for example, `queries/`): ``` # queries/get_tags.gql query GetAllTags { BuiltinTag { edges { node { __typename name { value } } } } } ``` 2. **Export the GraphQL schema**: ``` infrahubctl graphql export-schema ``` 3. **Generate the Pydantic models**: ``` infrahubctl graphql generate-return-types queries/ ``` Query names Ensure each of your GraphQL queries has a unique name, as the generated Python files will be named based on these query names. Two queries with the same name will land in the same file, leading to potential overrides. 4. **Use the generated models** in your Python code ``` from .queries.get_tags import GetAllTagsQuery response = await client.execute_graphql(query=MY_QUERY) data = GetAllTagsQuery(**response) ``` --- # Querying data in Infrahub We can query data in 3 ways using the SDK: * querying a single node of a given kind, based on some filters, using the `get` method * querying multiple nodes of a given kind, based on some filters, using the `filters` method * querying all the nodes of a given kind, using the `all` method * querying with a GraphQL query, using the `execute_graphql` method ## Query filters[​](#query-filters "Direct link to Query filters") The `get` and `filters` query methods allow you to use filters. Filters specify on which attribute(s) or attributes of a relationship(s) the resulting node(s) should match. The available filters depend on the kind of Node you want to query and are dynamically generated from the schema. ### Discovering available filters[​](#discovering-available-filters "Direct link to Discovering available filters") The easiest way to discover the available filters for a kind of node is by opening the GraphQLi Explorer in the GraphQL sandbox. Under each GraphQL query you will find the available filters. ### Attribute filters[​](#attribute-filters "Direct link to Attribute filters") For every attribute the following filters will be generated (replace attribute with the name of the attribute): * `attribute__value`: filter for a single attribute value * `attribute__values`: (list) filter for multiple attribute values * `attribute__is_visible`: (boolean) filter for the `is_visible` property of an attribute * `attribute__is_protected`: (boolean) filter for the `is_protected` property of an attribute * `attribute__source__id`: filter for the `source` property of an attribute * `attribute__owner__id`: filter for the `owner` property of an attribute ### Relationship filters[​](#relationship-filters "Direct link to Relationship filters") For every attribute of a relationship of the node, the same filters will be generated. The name of the filter will be prefixed with the relationship name followed by 2 underscores `relationship__attribute__value`. ### Using filters[​](#using-filters "Direct link to Using filters") Filters can be passed as arguments to the `get` or `filters` method. * Async * Sync ``` tag = await client.get(kind="BuiltinTag", name__value="RED") ``` ``` tag = client.get(kind="BuiltinTag", name__value="RED") ``` ### Using multiple filters[​](#using-multiple-filters "Direct link to Using multiple filters") When you pass multiple filters as argument to the `get` or `filters` method, they will combined in a logical AND operation. The resulting nodes of your query will match on all the filters in that case. * Async * Sync ``` tags = await client.filters(kind="BuiltinTag", name__values=["RED", "BLUE"], name__is_protected=True) ``` ``` tags = client.filters(kind="BuiltinTag", name__values=["RED", "BLUE"], name__is_protected=True) ``` ## Querying a single node[​](#querying-a-single-node "Direct link to Querying a single node") You can query Infrahub for a single node of a particular kind, by using the `get` method and using 1 or more filters. * Async * Sync ``` tag = await client.get(kind="BuiltinTag", name__value="RED") ``` ``` tag = client.get(kind="BuiltinTag", name__value="RED") ``` ## Querying multiple nodes[​](#querying-multiple-nodes "Direct link to Querying multiple nodes") You can query Infrahub for multiple nodes of a particular kind, by using the `filters` method and using 1 or more filters. * Async * Sync ``` tags = client.filters(kind="BuiltinTag", name__values=["RED", "BLUE"]) ``` ``` tags = client.filters(kind="BuiltinTag", name__values=["RED", "BLUE"]) ``` ## Querying all nodes[​](#querying-all-nodes "Direct link to Querying all nodes") You can query Infrahub for all nodes of a particular kind, by using the `all` method. * Async * Sync ``` tags = await client.all(kind="BuiltinTag") ``` ``` tags = client.all(kind="BuiltinTag") ``` ## Querying with a GraphQL query[​](#querying-with-a-graphql-query "Direct link to Querying with a GraphQL query") In some scenarios it might be more convenient to query Infrahub using a GraphQL query, rather than using the builtin mechanisms in the SDK. An example might be finding all the devices connected to a given circuit. This would require us to execute multiple queries using the nodes provided by the SDK. However with a GraphQL query this can be achieved using only a single query. The downside of using this method, is that it will not construct Python objects for the resulting data. Instead the SDK will return a Python dictionary containing the deserialized JSON data returned by the GraphQL API. * Async * Sync ``` query = """query { BuiltinTag(name__values: ["RED", "BLUE"]) { edges { node { name { value } } } } }""" data = await client.execute_graphql(query=query) for tag in data["BuiltinTag"]["edges"]: print(tag["node"]["name"]["value"]) ``` ``` query = """query { BuiltinTag(name__values: ["RED", "BLUE"]) { edges { node { name { value } } } } }""" data = client.execute_graphql(query=query) for tag in data["BuiltinTag"]["edges"]: print(tag["node"]["name"]["value"]) ``` ## Attributes and relationships[​](#attributes-and-relationships "Direct link to Attributes and relationships") By default, the result of a query will include attributes, relationships of cardinality one and relationships of kind Attribute or Parent. Relationships that are included in a query will be automatically initialized which means the ID, type and display name of the peers will be included in the query. But the related node itself will not be included. To explore this in a bit more details, we are going to assume the following schema has been loaded into Infrahub ``` --- version: "1.0" nodes: - name: Device namespace: Test attributes: - name: name kind: Text unique: true optional: false relationships: - name: tags cardinality: many kind: Attribute peer: BuiltinTag optional: true - name: site cardinality: one kind: Attribute peer: TestSite optional: false - name: interfaces cardinality: many kind: Component peer: TestInterface optional: false - name: Site namespace: Test attributes: - name: name kind: Text unique: true optional: false relationships: - name: device cardinality: many kind: Attribute peer: TestDevice optional: true - name: Interface namespace: Test attributes: - name: name kind: Text unique: true optional: false relationships: - name: device cardinality: one kind: Parent peer: TestDevice optional: true ``` ### Attributes[​](#attributes "Direct link to Attributes") Attributes are included by default. * Async * Sync ``` device = await client.get(kind="BuiltinTag", name__value="atl1-edge1") print(device.name.value) ``` ``` device = client.get(kind="BuiltinTag", name__value="atl1-edge1") print(device.name.value) ``` ### Relationships of cardinality one[​](#relationships-of-cardinality-one "Direct link to Relationships of cardinality one") Relationships of cardinality one are included by default and will be initialized. * Async * Sync ``` device = await client.get(kind="TestDevice", name__value="atl1-edge1") print(device.site.initialized) # True print(device.site.id, device.site.display_label, device.site.typename) print(device.site.peer) # the related node is not included ``` ``` device = client.get(kind="TestDevice", name__value="atl1-edge1") print(device.site.initialized) # True print(device.site.id, device.site.display_label, device.site.typename) print(device.site.peer) # the related node is not included ``` Relationships of kind `Attribute`, `Parent` are included by default and will be initialized. * Async * Sync ``` device = await client.get(kind="TestDevice", name__value="atl1-edge1") print(device.tags.initialized) # True print(device.tags.peers) for tag in device.tags.peers: print(tag.id, tag.display_label, tag.typename) ``` ``` device = client.get(kind="TestDevice", name__value="atl1-edge1") print(device.tags.initialized) # True print(device.tags.peers) for tag in device.tags.peers: print(tag.id, tag.display_label, tag.typename) ``` ### Relationships of cardinality many are not included by default[​](#relationships-of-cardinality-many-are-not-included-by-default "Direct link to Relationships of cardinality many are not included by default") Relationships of cardinality many are not included by default. * Async * Sync ``` device = await client.get(kind="TestDevice", name__value="atl1-edge1") print(device.interfaces.initialized) # False print(device.interfaces.peers) # empty list [] ``` ``` device = client.get(kind="TestDevice", name__value="atl1-edge1") print(device.interfaces.initialized) # False print(device.interfaces.peers) # empty list [] ``` ### Including attributes and relationships[​](#including-attributes-and-relationships "Direct link to Including attributes and relationships") You can include attributes and relationships that are not retrieved as part of a query by default. The included relationships will be initialized and the related nodes (peers) will be initialized. * Async * Sync ``` device = await client.get(kind="TestDevice", name__value="atl1-edge1", include=["interfaces"]) print(device.interfaces.initialized) # True print(device.interfaces.peers) for interface in device.interfaces.peers: print(interface.id, interface.display_label, interface.typename) ``` ``` device = client.get(kind="TestDevice", name__value="atl1-edge1", include=["interfaces"]) print(device.interfaces.initialized) # True print(device.interfaces.peers) for interface in device.interfaces.peers: print(interface.id, interface.display_label, interface.typename) ``` ### Excluding attribute and relationships[​](#excluding-attribute-and-relationships "Direct link to Excluding attribute and relationships") You can exclude attributes and relationships that are retrieved with a query by default. This can be useful if you need to optimize or speed up a particular query. * Async * Sync ``` device = await client.get(kind="TestDevice", exclude=["site"]) print(device.site) # None ``` ``` device = client.get(kind="TestDevice", exclude=["site"]) print(device.site) # None ``` ### Fetching relationships manually[​](#fetching-relationships-manually "Direct link to Fetching relationships manually") The `fetch` method can be used to retrieve relationships, initialize them; and retrieving the related nodes manually. The `fetch` method can also be used on relationship that were previously initialized. * Async * Sync ``` device = await client.get(kind="TestDevice", name__value="atl1-edge1") print(device.interfaces.initialized) # False await device.interfaces.fetch() print(device.interfaces.initialized) # True for interface in device.interfaces.peers: print(interface.id, interface.display_label, interface.typename, interface.peer.name.value) ``` ``` device = client.get(kind="TestDevice", name__value="atl1-edge1") print(device.interfaces.initialized) # False device.interfaces.fetch() print(device.interfaces.initialized) # True for interface in device.interfaces.peers: print(interface.id, interface.display_label, interface.typename, interface.peer.name.value) ``` * Async * Sync ``` device = await client.get(kind="TestDevice", name__value="atl1-edge1", include=["interfaces"]) print(device.interfaces.initialized) # True await device.interfaces.fetch() for interface in device.interfaces.peers: print(interface.id, interface.display_label, interface.typename, interface.peer.name.value) ``` ``` device = client.get(kind="TestDevice", name__value="atl1-edge1", include=["interfaces"]) print(device.interfaces.initialized) # True device.interfaces.fetch() for interface in device.interfaces.peers: print(interface.id, interface.display_label, interface.typename, interface.peer.name.value) ``` ### Prefetch relationships[​](#prefetch-relationships "Direct link to Prefetch relationships") Related nodes of a relationship can be retrieved, using the `prefetch_relationships` argument for the different `query` methods. But this requires the usage of the internal client store . More information can be found in the [Using the client store](/python-sdk/guides/store.md) guide. ## Query a node(s) in the past[​](#query-a-nodes-in-the-past "Direct link to Query a node(s) in the past") To query the state of a Node in the past, you can pass the `at` argument to all the query methods. The at argument accepts a `str`, `DateTime` or `Timestamp` object as value. Values of type `str` will be parsed using the [Pendulum](https://pendulum.eustace.io/) library. * Async * Sync ``` device = await client.get(kind="TestDevice", name__value="atl1-edge1", at="10:10:10") ``` ``` device = client.get(kind="TestDevice", name__value="atl1-edge1", at="10:10:10") ``` ### Properties of attributes and relationships[​](#properties-of-attributes-and-relationships "Direct link to Properties of attributes and relationships") By default, the [meta data or properties](/objects/metadata.md) of attributes and relationships are not included. We can include these properties using the `property` argument of the SDK client's `all`, `filters` or `get` method. * Async * Sync ``` device = await client.get(kind="TestDevice", name__value="atl1-edge1", property=True) print(device.name.is_protected) print(device.name.source.display_label) ``` ``` device = client.get(kind="TestDevice", name__value="atl1-edge1", property=True) print(device.name.protected) print(device.name.source.display_label) ``` ## Node metadata[​](#node-metadata "Direct link to Node metadata") Node metadata provides information about when a node was created or last updated, and by whom. This includes timestamps and references to the accounts that made the changes. ### Including node metadata in queries[​](#including-node-metadata-in-queries "Direct link to Including node metadata in queries") By default, node metadata is not included in query results. You can include it using the `include_metadata` argument of the SDK client's `all`, `filters`, or `get` method. * Async * Sync ``` device = await client.get(kind="TestDevice", name__value="atl1-edge1", include_metadata=True) ``` ``` device = client.get(kind="TestDevice", name__value="atl1-edge1", include_metadata=True) ``` ### Accessing node metadata[​](#accessing-node-metadata "Direct link to Accessing node metadata") Once metadata is included in the query, you can access it using the `get_node_metadata()` method. The metadata object contains the following fields: * `created_at`: Timestamp when the node was created * `created_by`: Reference to the account that created the node * `updated_at`: Timestamp when the node was last updated * `updated_by`: Reference to the account that last updated the node - Async - Sync ``` device = await client.get(kind="TestDevice", name__value="atl1-edge1", include_metadata=True) # Get the metadata object metadata = device.get_node_metadata() # Access creation metadata print(metadata.created_at) # e.g., "2024-01-15T10:30:00Z" print(metadata.created_by.display_label) # e.g., "admin" # Access update metadata print(metadata.updated_at) # e.g., "2024-01-20T14:45:00Z" print(metadata.updated_by.display_label) # e.g., "admin" ``` ``` device = client.get(kind="TestDevice", name__value="atl1-edge1", include_metadata=True) # Get the metadata object metadata = device.get_node_metadata() # Access creation metadata print(metadata.created_at) # e.g., "2024-01-15T10:30:00Z" print(metadata.created_by.display_label) # e.g., "admin" # Access update metadata print(metadata.updated_at) # e.g., "2024-01-20T14:45:00Z" print(metadata.updated_by.display_label) # e.g., "admin" ``` The `created_by` and `updated_by` fields are `NodeProperty` objects that include: * `id`: The unique identifier of the account * `display_label`: A human-readable label for the account * `typename`: The GraphQL type name of the account ## Relationship metadata[​](#relationship-metadata "Direct link to Relationship metadata") When querying with `include_metadata=True`, you can also access metadata about relationship edges themselves. This tells you when a specific relationship (the connection between two nodes) was last modified and by whom. ### Accessing relationship metadata[​](#accessing-relationship-metadata "Direct link to Accessing relationship metadata") Use the `get_relationship_metadata()` method on a related node to access the relationship edge metadata. This is different from node metadata - it describes when the relationship itself was created or modified, not the connected node. * Async * Sync ``` device = await client.get(kind="TestDevice", name__value="atl1-edge1", include_metadata=True) # For a cardinality-one relationship rel_metadata = device.site.get_relationship_metadata() if rel_metadata: print(rel_metadata.updated_at) # e.g., "2024-01-17T08:00:00Z" print(rel_metadata.updated_by.display_label) # e.g., "admin" # For a cardinality-many relationship for tag in device.tags.peers: rel_metadata = tag.get_relationship_metadata() if rel_metadata: print(f"Tag relationship updated at: {rel_metadata.updated_at}") print(f"Updated by: {rel_metadata.updated_by.display_label}") ``` ``` device = client.get(kind="TestDevice", name__value="atl1-edge1", include_metadata=True) # For a cardinality-one relationship rel_metadata = device.site.get_relationship_metadata() if rel_metadata: print(rel_metadata.updated_at) # e.g., "2024-01-17T08:00:00Z" print(rel_metadata.updated_by.display_label) # e.g., "admin" # For a cardinality-many relationship for tag in device.tags.peers: rel_metadata = tag.get_relationship_metadata() if rel_metadata: print(f"Tag relationship updated at: {rel_metadata.updated_at}") print(f"Updated by: {rel_metadata.updated_by.display_label}") ``` The `RelationshipMetadata` object contains: * `updated_at`: Timestamp when the relationship was last updated * `updated_by`: Reference to the account that last updated the relationship (a `NodeProperty` object with `id`, `display_label`, and `typename`) note Relationship metadata tracks changes to the relationship edge itself (for example, when the relationship was created or when its properties were modified), not changes to the connected nodes. For node-level metadata, use `get_node_metadata()` on the node itself. ## Ordering query results[​](#ordering-query-results "Direct link to Ordering query results") You can control the order in which query results are returned using the `order` argument. Results can be ordered by attributes, related-node attributes, or object metadata such as creation and update timestamps. ### Ordering by fields[​](#ordering-by-fields "Direct link to Ordering by fields") Use the `Order` and `OrderByEntry` classes along with `OrderDirection` to order results by one or more fields. Each `OrderByEntry` takes a `field` and an optional `direction` (defaulting to `OrderDirection.ASC`). The entries are applied in order, so the first entry is the primary sort key. A `field` can reference: * an attribute, e.g. `name__value` * a related-node attribute, e.g. `owner__name__value` * node metadata, e.g. `node_metadata__created_at` or `node_metadata__updated_at` - Async - Sync ``` from infrahub_sdk.enums import OrderDirection from infrahub_sdk.types import Order, OrderByEntry # Get devices ordered by name (ascending is the default) devices = await client.all( kind="TestDevice", order=Order(by=[OrderByEntry(field="name__value")]) ) # Get devices ordered by creation time (oldest first) devices = await client.all( kind="TestDevice", order=Order(by=[OrderByEntry(field="node_metadata__created_at", direction=OrderDirection.ASC)]) ) # Get devices ordered by last update time (most recent first) devices = await client.all( kind="TestDevice", order=Order(by=[OrderByEntry(field="node_metadata__updated_at", direction=OrderDirection.DESC)]) ) # Order by name ascending, then by creation time descending devices = await client.all( kind="TestDevice", order=Order(by=[ OrderByEntry(field="name__value", direction=OrderDirection.ASC), OrderByEntry(field="node_metadata__created_at", direction=OrderDirection.DESC), ]) ) ``` ``` from infrahub_sdk.enums import OrderDirection from infrahub_sdk.types import Order, OrderByEntry # Get devices ordered by name (ascending is the default) devices = client.all( kind="TestDevice", order=Order(by=[OrderByEntry(field="name__value")]) ) # Get devices ordered by creation time (oldest first) devices = client.all( kind="TestDevice", order=Order(by=[OrderByEntry(field="node_metadata__created_at", direction=OrderDirection.ASC)]) ) # Get devices ordered by last update time (most recent first) devices = client.all( kind="TestDevice", order=Order(by=[OrderByEntry(field="node_metadata__updated_at", direction=OrderDirection.DESC)]) ) # Order by name ascending, then by creation time descending devices = client.all( kind="TestDevice", order=Order(by=[ OrderByEntry(field="name__value", direction=OrderDirection.ASC), OrderByEntry(field="node_metadata__created_at", direction=OrderDirection.DESC), ]) ) ``` The available order directions are: * `OrderDirection.ASC`: Ascending order (oldest/smallest first) * `OrderDirection.DESC`: Descending order (newest/largest first) note The `node_metadata` argument on `Order` is deprecated in favor of the `by` form shown above. It cannot be combined with `by` in the same `Order`. ### Disabling default ordering[​](#disabling-default-ordering "Direct link to Disabling default ordering") For performance optimization, you can disable the default ordering behavior entirely: * Async * Sync ``` from infrahub_sdk.types import Order # Disable ordering to improve query performance devices = await client.all(kind="TestDevice", order=Order(disable=True)) ``` ``` from infrahub_sdk.types import Order # Disable ordering to improve query performance devices = client.all(kind="TestDevice", order=Order(disable=True)) ``` ### Combining metadata and ordering[​](#combining-metadata-and-ordering "Direct link to Combining metadata and ordering") You can include metadata and order results in the same query: * Async * Sync ``` from infrahub_sdk.enums import OrderDirection from infrahub_sdk.types import NodeMetaOrder, Order # Get the 10 most recently updated devices with their metadata devices = await client.filters( kind="TestDevice", limit=10, include_metadata=True, order=Order(node_metadata=NodeMetaOrder(updated_at=OrderDirection.DESC)) ) for device in devices: metadata = device.get_node_metadata() print(f"{device.name.value} - Last updated: {metadata.updated_at}") ``` ``` from infrahub_sdk.enums import OrderDirection from infrahub_sdk.types import NodeMetaOrder, Order # Get the 10 most recently updated devices with their metadata devices = client.filters( kind="TestDevice", limit=10, include_metadata=True, order=Order(node_metadata=NodeMetaOrder(updated_at=OrderDirection.DESC)) ) for device in devices: metadata = device.get_node_metadata() print(f"{device.name.value} - Last updated: {metadata.updated_at}") ``` ## Query a node(s) in a different branch[​](#query-a-nodes-in-a-different-branch "Direct link to Query a node(s) in a different branch") If you want to query a node(s) in a different branch than the default branch with which the SDK client was initiated, then you can use the `branch` argument of the query methods. * Async * Sync ``` device = await client.get(kind="TestDevice", name__value="atl1-edge1", branch="refresh-site-atl1") ``` ``` device = client.get(kind="TestDevice", name__value="atl1-edge1", branch="refresh-site-atl1") ``` ## Query for a large amount of nodes[​](#query-for-a-large-amount-of-nodes "Direct link to Query for a large amount of nodes") When you have a query that matches a large amount of nodes, it could have an implication on the performance of the query. The SDK tries to optimize this process by using pagination, which happens transparently. However, even when using pagination, such queries could take a long time to complete. Using the `parallel` argument, we can enable concurrent retrieval of pages of data for large queries, which can significantly improve the resolution time of the query. The `parallel` argument is available for the SDK client's `filters` and `all` method. * Async * Sync ``` device = await client.all(kind="TestDevice", parallel=True) ``` ``` device = client.get(kind="TestDevice", parallel=True) ``` --- # Using Resource Managers The goal of this guide is to show you how to create a resource pool using the Python SDK, and how you can allocated resources with them. note The guide makes the assumption that we start with an Infrahub instance that doesn't have any data or schema loaded. ## Loading a schema[​](#loading-a-schema "Direct link to Loading a schema") Save the following schema in a file on your local system. The location or filename are not that important, but in this guide will be using `/tmp/schema.yml` ``` --- version: "1.0" nodes: - name: IPPrefix namespace: Ipam include_in_menu: false inherit_from: - "BuiltinIPPrefix" description: "IPv4 or IPv6 network" label: "IP Prefix" - name: IPAddress namespace: Ipam include_in_menu: false inherit_from: - "BuiltinIPAddress" description: "IP Address" label: "IP Address" - name: Device namespace: Infra description: "A Device" icon: "mdi:server" label: "Device" attributes: - name: name kind: Text label: Name optional: false relationships: - name: primary_ip label: "Primary IP Address" peer: IpamIPAddress optional: false kind: Attribute cardinality: one ``` Load the schema with the `infrahubctl` command. ❯ infrahubctl schema load /tmp/schema.yml ``` schema '/tmp/schema.yml' loaded successfully 1 schema processed in 6.846 seconds. ``` ## Creating an IP Prefix object[​](#creating-an-ip-prefix-object "Direct link to Creating an IP Prefix object") Next we will be creating an IP Prefix object, which the resource manager will use as a resource to allocate resources from. * Async * Sync ``` from infrahub_sdk import InfrahubClient client = await InfrahubClient() prefix = await client.create(kind="IpamIPPrefix", prefix="10.0.0.0/24", member_type="address") await prefix.save() ``` ``` from infrahub_sdk import InfrahubClientSync client = InfrahubClient() prefix = client.create(kind="IpamIPPrefix", prefix="10.0.0.0/24", member_type="address") prefix.save() ``` ## Creating a resource manager[​](#creating-a-resource-manager "Direct link to Creating a resource manager") We can now create a resource manager of kind `CoreIPAddressPool`. The kind of the resource manager determines the kind of resource the manager will allocate. We will create a `CoreIPaddressPool` with the following properties: * Name: My IP address pool * Default Address Type: `IpamIPAddress` (the kind of the IP address node defined in our schema) * Default Prefix Size: 32 * Resources: 10.0.0.0/24 * IP Namespace: Namespace > Default - Async - Sync ``` pool = await client.create( kind="CoreIPAddressPool", name="My IP address pool", default_address_type="IpamIPAddress", default_prefix_size=32, resources=[prefix], is_pool=True, ip_namespace={"id": "default"} ) await pool.save() ``` ``` pool = client.create( kind="CoreIPAddressPool", name="My IP address pool", default_address_type="IpamIPAddress", default_prefix_size=32, resources=[prefix], is_pool=True, ip_namespace={"id": "default"} ) pool.save() ``` ## Allocating a resource out of the pool[​](#allocating-a-resource-out-of-the-pool "Direct link to Allocating a resource out of the pool") We can now start allocating resources out of the `CoreIPAddressPool` we created. We can use the resource manager to allocate resources out of a pool in 2 different ways: 1. Directly allocate a resource out of a pool. This is typically used when you need to allocate a resource that has no relation to other nodes. For example, allocating an IP address out of a pool that will be assigned to something that is not stored in Infrahub. 2. Allocate a resource out of a pool to a relationship of a node. For example, create a device and allocate an IP address out of a pool and assign it to the device ### Direct allocation of a resource[​](#direct-allocation-of-a-resource "Direct link to Direct allocation of a resource") The Python SDK provides 2 methods that perform direct allocations: * `allocate_next_ip_address` to allocate resources out of `CoreIPAddressPool` pools * `allocate_next_ip_prefix` to allocated resources out of `CoreIPPrefixPool` pools We will use `allocate_next_ip_address` to allocated an IP address out of the `CoreIPAddressPool` we created. * Async * Sync ``` ip_address = await client.allocate_next_ip_address( resource_pool=pool, data={"description": "my first allocated ip"} ) ``` ``` ip_address = client.allocate_next_ip_address( resource_pool=pool, data={"description": "my first allocated ip"} ) ``` We provided a data argument to method. This data argument is used to pass attributes and relationships that we want to set on the allocated resource. In this case we are setting to the description of the allocated IP Address. You can allocate resources in an idempotent way by passing an identifier argument to the allocation method. This identifier links the resource pool with the allocated resource allowing us to create idempotent allocation behavior. This is crucial when you want to allocate resources in an idempotent way using [generators](https://docs.infrahub.app/topics/generator). In this example we are executing the `allocate_next_ip_address` method 2 times, using the same identifier. * Async * Sync ``` ip_address2 = await client.allocate_next_ip_address(resource_pool=pool, identifier="my-allocated-ip") ip_address3 = await client.allocate_next_ip_address(resource_pool=pool, identifier="my-allocated-ip") assert ip_address2.id == ip_address3.id assert ip_address2.address.value == ip_address3.address.value ``` ``` ip_address2 = client.allocate_next_ip_address(resource_pool=pool, identifier="my-allocated-ip") ip_address3 = client.allocate_next_ip_address(resource_pool=pool, identifier="my-allocated-ip") assert ip_address2.id == ip_address3.id assert ip_address2.address.value == ip_address3.address.value ``` ### Allocating resources to a relationship of a node[​](#allocating-resources-to-a-relationship-of-a-node "Direct link to Allocating resources to a relationship of a node") Another way we can use resource managers is in situations where we create a node that has a relationship and we want to use a resource manager to allocate a new resource for that relationship. For example, we want to create a new device (or server) and assign an IP address to the device out of a pool. The Python SDK allows you to set the relationship of a Node to the resource pool that you want to allocate from. The resource will be allocated when you `save` the node. * Async * Sync ``` device = await client.create(kind="InfraDevice", name="dev-123", primary_ip=pool) await device.save() ``` ``` device = client.create(kind="InfraDevice", name="dev-123", primary_ip=pool) device.save() ``` When you allocated to a relationship of a node, the resource allocation happens in an idempotent way. An identifier is automatically assigned to the resource allocation in this case. ## Branch agnostic resource allocation[​](#branch-agnostic-resource-allocation "Direct link to Branch agnostic resource allocation") Resource managers have to allocate resources in a branch agnostic way. For example if we allocate a resource in a branch, then that resource should also be allocated in the main branch, even if the resource object does not yet exist in the main branch. Without this behavior we could allocate the resource multiple times, which should not be possible. Create a branch named `test` * Async * Sync ``` await client.branch.create("test") ``` ``` client.branch.create("test") ``` Allocate a new IP address in the `test` branch. * Async * Sync ``` ip_address4 = await client.allocate_next_ip_address( resource_pool=pool, branch="test" ) ``` ``` ip_address4 = client.allocate_next_ip_address( resource_pool=pool, branch="test" ) ``` Currently there is no dedicated functionality in the Python SDK to do this, so for now we have to use a GraphQL query. ``` query { InfrahubResourcePoolAllocated(pool_id: "", resource_id: "") { edges { node { display_label branch } } } IpamIPAddress { edges { node { display_label } } } } ``` Notice that we have one IP address allocated by the Resource manager in the test branch. The query in the main branch shows us this allocation, indicating that it has been allocated and the resource cannot be allocated again. However, the IP address does not exist itself within the main branch. ## CoreNumberPool and attribute allocation[​](#corenumberpool-and-attribute-allocation "Direct link to CoreNumberPool and attribute allocation") `CoreNumberPool` allocates integer values (such as VLAN IDs or AS numbers) directly to node attributes. The pool assigns the integer value at the moment the node is created on the server. ``` vlan = await client.create( kind="InfraVLAN", name="VLAN-100", vlan_id={"from_pool": {"id": pool_id}}, ) await vlan.save() ``` ### Limitation: `allow_upsert=True` with a pool-sourced HFID attribute[​](#limitation-allow_upserttrue-with-a-pool-sourced-hfid-attribute "Direct link to limitation-allow_upserttrue-with-a-pool-sourced-hfid-attribute") `CoreNumberPool` assigns the integer value at server creation time, and a new value on each creation. When a node's human-friendly identifier (HFID) includes a pool-sourced attribute, the HFID is never stable, so an upsert can never match an existing node by it. Rather than updating, each `save(allow_upsert=True)` would create another node. The SDK blocks this up front to avoid silently duplicating data. warning Calling `save(allow_upsert=True)` on a node whose HFID contains a `CoreNumberPool`-sourced attribute raises `ValidationError` before any network call is made. ``` # Schema has human_friendly_id: ["vlan_id__value"] vlan = await client.create( kind="InfraVLAN", name="VLAN-100", vlan_id={"from_pool": {"id": pool_id}}, ) # This raises ValidationError - vlan_id is pool-sourced and in the HFID, so the upsert can't be idempotent await vlan.save(allow_upsert=True) ``` **Alternatives:** * **Look up and reuse** - find the existing node by a stable attribute or relationship and reuse it, otherwise create it once. This is the idempotent pattern to use in generators: ``` existing = await client.filters(kind="InfraVLAN", service__ids=[service.id]) vlan = existing[0] if existing else await client.create( kind="InfraVLAN", name="VLAN-100", vlan_id={"from_pool": {"id": pool_id}}, ) await vlan.save() # creates on first run, pool assigns vlan_id; reuses afterwards ``` * **Explicit id** - if you already know the node's UUID, set `node.id` before saving. The upsert will use the UUID directly and skip HFID lookup: ``` vlan.id = "known-uuid" await vlan.save(allow_upsert=True) # matches on the id directly, no HFID lookup needed ``` * **Deterministic identifier** - if possible, design your schema so the HFID uses a non-pool attribute (for example, a human-assigned `name`) and keep `vlan_id` out of the HFID. --- # Using the client store The client in the SDK contains a store that is used to store objects in a local cache. The store is mainly used for the internal working of the SDK. It is used to create relations between objects that might not yet exist in the database, or to store relations for objects that were retrieved from the database, amongst other things. The store stores objects that we are retrieving from Infrahub using the different query methods. This allows to not have to keep references to objects throughout scripts, or avoids situations where we have to re-execute queries. Objects are stored in the following scenario: * The resulting objects from using the SDK client's `get`, `filters` or `all` methods - Async - Sync ``` tag = await client.get(kind="BuiltinTag", name__value="RED") tag_in_store = client.store.get(key=tag.id) ``` ``` tag = client.get(kind="BuiltinTag", name__value="RED") tag_in_store = client.store.get(key=tag.id) ``` * The resulting related objects for objects retrieved using the SDK client's query methods, when we use the `prefetch_relationships` argument. - Async - Sync ``` device = await client.get(kind="InfraDevice", name__value="atl1-edge1", prefetch_relationships=True) site = client.store.get(key=device.site.id) ``` ``` device = client.get(kind="InfraDevice", name__value="atl1-edge1", prefetch_relationships=True) site = client.store.get(key=device.site.id) ``` * The related objects of a object's relationship when the `fetch` method is used - Async - Sync ``` device = await client.get(kind="InfraDevice", name__value="atl1-edge1") await device.site.fetch() site = client.store.get(key=device.site.id) ``` ``` device = client.get(kind="InfraDevice", name__value="atl1-edge1") device.site.fetch() site = client.store.get(key=device.site.id) ``` * Objects that get created using the SDK - Async - Sync ``` tag = await client.create("BuiltinTag", name="BLACK") await tag.save() tag_in_store = client.store.get(key=tag.id) ``` ``` tag = client.create("BuiltinTag", name="BLACK") tag.save() tag_in_store = client.store.get(key=tag.id) ``` ## Retrieving objects from the store[​](#retrieving-objects-from-the-store "Direct link to Retrieving objects from the store") You can retrieve objects from the store using their `id` or `hfid`. When using the `hfid`, we also have to provide the `kind` of the object that we want to retrieve. * Async * Sync ``` tag = await client.get("BuiltinTag", name__value="BLACK") tag_in_store = client.store.get(key=tag.id) tag == tag_in_store tag = await client.get("BuiltinTag", name__value="BLACK") tag_in_store = client.store.get(key=tag.hfid, kind="BuiltinTag") tag == tag_in_store ``` ``` tag = client.get("BuiltinTag", name__value="BLACK") tag_in_store = client.store.get(key=tag.id) tag == tag_in_store tag = client.get("BuiltinTag", name__value="BLACK") tag_in_store = client.store.get(key=tag.hfid, kind="BuiltinTag") tag == tag_in_store ``` ## Manually storing objects in the store[​](#manually-storing-objects-in-the-store "Direct link to Manually storing objects in the store") You can store objects in the store manually using the `set` method. This has the advantage that you can choose a key that you want to use to reference the object in the store, besides the `id` or `hfid`. For example, we could use the name attribute value of the node as the key. * Async * Sync ``` tag = await client.get(kind="BuiltinTag", name__value="RED", populate_store=False) client.store.set(key=tag.name.value, node=tag) tag_in_store = client.store.get(key=tag.name.value) tag_in_store = client.store.get(key=tag.id) tag_in_store = client.store.get(key=tag.hfid, kind="BuiltinTag") ``` ``` tag = client.get(kind="BuiltinTag", name__value="RED", populate_store=False) client.store.set(key=tag.name.value, node=tag) tag_in_store = client.store.get(key=tag.name.value) tag_in_store = client.store.get(key=tag.id) tag_in_store = client.store.get(key=tag.hfid, kind="BuiltinTag") ``` ## Disable storing objects in the store using the different query methods[​](#disable-storing-objects-in-the-store-using-the-different-query-methods "Direct link to Disable storing objects in the store using the different query methods") In some scenarios it might not be desirable to automatically store the retrieved objects in the store, when using the SDK client's different query methods. In this case you can set the `populate_store` argument to `False`. * Async * Sync ``` tag = await client.get(kind="BuiltinTag", name__value="RED", populate_store=False) ``` ``` tag = client.get(kind="BuiltinTag", name__value="RED", populate_store=False) ``` --- # Using the client tracking mode The Python SDK provides a feature known as *Tracking Mode*. This mode allows for the aggregation and tracking of operations performed during a session, enhancing efficiency and data management. ## Overview[​](#overview "Direct link to Overview") Tracking mode is designed for scenarios where multiple operations or mutations are performed in sequence, and you want to consolidate or track these operations for efficiency or auditing purposes. It is ideal for scenarios requiring precise control and repeatability, such as idempotent scripting. Under the hood, tracking mode leverages a `CoreStandardGroup` object to aggregate and track these operations. For a detailed exploration of tracking mode applications and how to use it for idempotent scripting, visit the [tracking topic](/python-sdk/topics/tracking.md). ## Automatic tracking[​](#automatic-tracking "Direct link to Automatic tracking") The Infrahub Python SDK offers a streamlined process for managing the tracking session lifecycle using the context manager. This method is available for both asynchronous and synchronous clients, ensuring all operations within the context are efficiently tracked without manual intervention. ### Utilizing the context manager[​](#utilizing-the-context-manager "Direct link to Utilizing the context manager") With the `start_tracking` method, you initiate tracking mode as you enter the context and automatically conclude the tracking session as you exit. This guarantees that all operations performed within the context are tracked under the specified session, simplifying tracking management. * Async * Sync ``` # Auto-manage tracking session with async context manager async with client.start_tracking(identifier="my_tracking_session", params=params, delete_unused_nodes=True) as session: # Tracked operations node = await session.create(kind="MyNodeKind", data={"name": "Example"}) await node.save() # Optionally, add related Nodes and Groups to the context await session.group_context.add_related_nodes([another_node.id]) await session.group_context.add_related_groups([group.id]) ``` ``` # Auto-manage tracking session with sync context manager with client.start_tracking(identifier="my_tracking_session", params=params, delete_unused_nodes=True) as session: # Tracked operations node = session.create(kind="MyNodeKind", data={"name": "Example"}) node.save() # Optionally, add related Nodes and Groups to the context session.group_context.add_related_nodes([another_node.id]) session.group_context.add_related_groups([group.id]) ``` info The context manager feature elegantly handles the start and conclusion of tracking sessions, making your code cleaner and less prone to errors related to manual tracking management. ## Manual tracking[​](#manual-tracking "Direct link to Manual tracking") Manual tracking involves explicitly starting, managing, and concluding sessions, offering fine-grained control over the tracking process. ### Setting up[​](#setting-up "Direct link to Setting up") First, ensure the Python SDK is installed and configured to communicate with your Infrahub instance. Then, let's start by setting up our client and enabling tracking mode. Once tracking mode is enabled, it's important to note that only the objects that are being saved (created or updated) will be automatically tracked under the specified session identifier. Objects that are merely queried and not modified or saved will not be added to the tracking group. This behavior ensures that the tracking group specifically reflects changes made during the session, providing a clear audit trail of modifications. * Async * Sync ``` from infrahub_sdk.client import InfrahubClient client = InfrahubClient(address="http://localhost:8000") await client.start_tracking(identifier="my_tracking_session") node = await client.create(kind="MyNodeKind", data={"name": "Example"}) await node.save() ``` ``` from infrahub_sdk.client_sync import InfrahubClientSync client = InfrahubClientSync(address="http://localhost:8000") client.start_tracking(identifier="my_tracking_session") node = client.create(kind="MyNodeKind", data={"name": "Example"}) node.save() ``` #### Tracking parameters[​](#tracking-parameters "Direct link to Tracking parameters") When enabling tracking mode with the `start_tracking` method, you can customize the tracking session with the following parameters: | Parameter | Description | | ------------------------- | --------------------------------------------------------------------------------------------------------------------------- | | **identifier** | Unique string to identify the session, used for correlating operations and logs. Defaults to "python-sdk" if not specified. | | **params** | Optional dictionary for extra context, enabling fine-grained control over tracking. | | **delete\_unused\_nodes** | Boolean indicating if nodes not referenced should be automatically deleted, helping maintain a clean state. | | **group\_type** | Type of group object for tracking, default is `CoreStandardGroup`, customizable for specific grouping logic. | These parameters provide flexibility, enabling detailed auditing, efficient data management, and support for idempotent operations. #### Advanced tracking with parameters[​](#advanced-tracking-with-parameters "Direct link to Advanced tracking with parameters") In addition to the basic tracking mode functionalities, the Infrahub Python SDK allows for more granular control over the tracking sessions through parameters. These parameters can be beneficial for categorizing, filtering, or adding metadata to the groups created or updated during the tracking session. When starting a tracking session with `start_tracking,` you can pass a dictionary of parameters to define further the context and characteristics of the group being tracked. This allows for dynamic grouping based on runtime data, user inputs, or other operational metrics. * Async * Sync ``` params = { "data_source": "external", "analysis_type": "ipam_data" } await client.start_tracking( identifier="weekly_analysis", params=params, delete_unused_nodes=True ) ``` ``` params = { "data_source": "external", "analysis_type": "ipam_data" } client.start_tracking( identifier="weekly_analysis", params=params, delete_unused_nodes=True ) ``` ### Ending a tracking session and updating tracking information[​](#ending-a-tracking-session-and-updating-tracking-information "Direct link to Ending a tracking session and updating tracking information") This step involves creating or updating (using upsert) the `CoreStandardGroup` used to store all the Nodes and Groups used during execution. * Async * Sync ``` from infrahub_sdk.client import InfrahubClient client = InfrahubClient(address="http://localhost:8000") await client.start_tracking(identifier="my_tracking_session") node = await client.create(kind="MyNodeKind", data={"name": "Example"}) await node.save() # Update tracking information for async client await client.group_context.update_group() ``` ``` from infrahub_sdk.client_sync import InfrahubClientSync client = InfrahubClientSync(address="http://localhost:8000") client.start_tracking(identifier="my_tracking_session") node = client.create(kind="MyNodeKind", data={"name": "Example"}) node.save() # Update tracking information for sync client client.group_context.update_group() ``` ## Retrieving and manipulating groups[​](#retrieving-and-manipulating-groups "Direct link to Retrieving and manipulating groups") After setting up a tracking session with specific parameters, you may need to retrieve the group associated with this session for further manipulation. This involves fetching the group, accessing its members, and executing specific logic based on the member types. Here's how to retrieve a group based on an identifier and parameters and interact with its members: * Async * Sync ``` # Set the context properties to match the tracking session await client.set_context_properties(identifier="my_tracking_session", params=params) # Retrieve the group associated with the specified identifier and parameters group = await client.group_context.get_group(store_peers=True) # Check if the group exists if group: # Access previous members of the group, if any if client.group_context.previous_members: for member in client.group_context.previous_members: # Fetch the object from the store based on the member's type and ID obj = client.store.get(kind=member._typename, key=member.id) # Perform operations based on the member's type pass ``` ``` # Set the context properties to match the tracking session client.set_context_properties(identifier="my_tracking_session", params=params) # Retrieve the group associated with the specified identifier and parameters group = client.group_context.get_group(store_peers=True) # Check if the group exists if group: # Access previous members of the group, if any if client.group_context.previous_members: for member in client.group_context.previous_members: # Fetch the object from the store based on the member's type and ID obj = client.store.get(kind=member._typename, key=member.id) # Perform operations based on the member's type pass ``` --- # Infrahub Python SDK The Infrahub Python SDK is a client library for interacting with [Infrahub](https://docs.infrahub.app/) programmatically. It handles authentication, query construction, and data serialization so you can work with infrastructure data using native Python objects instead of raw API calls. ## What you can do with it[​](#what-you-can-do-with-it "Direct link to What you can do with it") The SDK covers three main use cases: * **Automate inside Infrahub** — Write [transforms](https://docs.infrahub.app/topics/transformation), [generators](https://docs.infrahub.app/topics/generator), and [checks](https://docs.infrahub.app/topics/check) that run as part of Infrahub's pipeline. * **Integrate with external systems** — Query and sync data between Infrahub and your existing tools. * **Build custom applications** — Use Infrahub as a data backend for your own Python projects. Under the hood, tools like [`infrahubctl`](/infrahubctl/infrahubctl.md) and the [Infrahub Ansible collection](https://docs.infrahub.app/ansible) both use this SDK. ## A quick look[​](#a-quick-look "Direct link to A quick look") * Async * Sync ``` import asyncio from infrahub_sdk import InfrahubClient async def main(): client = InfrahubClient() # Create a new tag and save it tag = await client.create( kind="BuiltinTag", name="staging", description="Resources in the staging environment", ) await tag.save() # Query all tags tags = await client.all(kind="BuiltinTag") for tag in tags: print(f"{tag.name.value} — {tag.description.value}") asyncio.run(main()) ``` ``` from infrahub_sdk import InfrahubClientSync client = InfrahubClientSync() # Create a new tag and save it tag = client.create( kind="BuiltinTag", name="staging", description="Resources in the staging environment", ) tag.save() # Query all tags tags = client.all(kind="BuiltinTag") for tag in tags: print(f"{tag.name.value} — {tag.description.value}") ``` Both async and sync clients expose the same API. Choose async for applications that benefit from concurrent I/O (transforms, large-scale sync scripts) and sync for straightforward scripting. ## When to use the SDK instead of direct API calls[​](#when-to-use-the-sdk-instead-of-direct-api-calls "Direct link to When to use the SDK instead of direct API calls") | Concern | Raw API | SDK | | -------------- | ------------------------------------ | -------------------------------------------------- | | Authentication | Manual token management, JWT refresh | Handled automatically | | Querying | Build GraphQL queries by hand | `client.get()`, `client.all()`, `client.filters()` | | Mutations | Construct and POST GraphQL mutations | `node.save()`, `node.delete()` | | Concurrency | Roll your own async batching | Built-in `batch` with concurrency control | | Typing | Maintain type definitions manually | Schema-driven type export using protocols | tip The SDK removes the boilerplate so you can focus on the logic that matters to your infrastructure. ## Key capabilities[​](#key-capabilities "Direct link to Key capabilities") * **CRUD operations** — Create, read, update, and delete any node type defined in your schema. * **Batch execution** — Group multiple queries into a batch with configurable concurrency limits. * **Tracking** — Tag operations with identifiers for auditing and idempotent updates. * **Store** — Cache retrieved nodes locally to reduce redundant queries. * **GraphQL escape hatch** — Run arbitrary GraphQL queries when the high-level API doesn't cover your use case. ## Video walkthrough[​](#video-walkthrough "Direct link to Video walkthrough") ## Installation[​](#installation "Direct link to Installation") * uv * pip ``` uv add infrahub-sdk ``` Extras are available for additional functionality: ``` uv add 'infrahub-sdk[ctl]' # Adds the infrahubctl CLI uv add 'infrahub-sdk[tests]' # Adds the testing framework for transforms and checks uv add 'infrahub-sdk[all]' # Everything ``` ``` pip install infrahub-sdk ``` Extras are available for additional functionality: ``` pip install 'infrahub-sdk[ctl]' # Adds the infrahubctl CLI pip install 'infrahub-sdk[tests]' # Adds the testing framework for transforms and checks pip install 'infrahub-sdk[all]' # Everything ``` [Installation guide/python-sdk/guides/installation](/python-sdk/guides/installation.md) ## Next steps[​](#next-steps "Direct link to Next steps") * **[Hello world example](/python-sdk/guides/client.md#hello-world-example)** — Your first client connection and query. * **[Create and configure a client](/python-sdk/guides/client.md)** — Set up authentication, proxy settings, and client options. * **[Query data](/python-sdk/guides/query_data.md)** — Retrieve nodes with filters and GraphQL. * **[Create, update, and delete nodes](/python-sdk/guides/create_update_delete.md)** — Manage infrastructure data programmatically. * **[Work with branches](/python-sdk/guides/branches.md)** — Use Infrahub's branch workflow from Python. * **[Batch operations](/python-sdk/guides/batch.md)** — Optimize performance for bulk operations. * **[Client configuration reference](/python-sdk/reference/config.md)** — All available configuration options. * **[Compatibility matrix](/python-sdk/reference/compatibility.md)** * **[Python SDK Release Notes](https://github.com/opsmill/infrahub-sdk-python/releases)** --- # Compatibility matrix This page documents which versions of the Infrahub Python SDK are compatible with each version of Infrahub. ## Version mapping[​](#version-mapping "Direct link to Version mapping") Each Infrahub release pins a specific SDK version. Using the matching SDK version ensures full compatibility. Newer patch releases of the SDK within the same minor version are generally safe to use. | Infrahub | Required SDK | Release date | | -------- | ------------ | -------------- | | 1.9.x | >= 1.20.0 | April 2026 | | 1.8.x | >= 1.19.0 | March 2026 | | 1.7.x | >= 1.18.1 | January 2026 | | 1.6.x | >= 1.16.0 | December 2025 | | 1.5.x | >= 1.15.0 | November 2025 | | 1.4.x | >= 1.13.5 | August 2025 | | 1.3.x | >= 1.13.0 | June 2025 | | 1.2.x | >= 1.8.0 | March 2025 | | 1.1.x | >= 1.3.0 | December 2024 | | 1.0.x | >= 1.0.0 | October 2024 | | 0.16.x | >= 0.13.1 | September 2024 | ## Detailed release mapping[​](#detailed-release-mapping "Direct link to Detailed release mapping") The table below shows the exact SDK version pinned to each Infrahub release. | Infrahub | SDK version | Infrahub release date | | -------- | ----------- | --------------------- | | 1.9.3 | 1.20.0 | 2026-05-05 | | 1.9.2 | 1.20.0 | 2026-04-30 | | 1.9.1 | 1.20.0 | 2026-04-29 | | 1.9.0 | 1.20.0 | 2026-04-24 | | 1.8.6 | 1.19.0 | 2026-04-21 | | 1.8.5 | 1.19.0 | 2026-04-17 | | 1.8.4 | 1.19.0 | 2026-04-02 | | 1.8.3 | 1.19.0 | 2026-03-31 | | 1.8.2 | 1.19.0 | 2026-03-25 | | 1.8.1 | 1.19.0 | 2026-03-19 | | 1.8.0 | 1.19.0 | 2026-03-16 | | 1.7.7 | 1.18.1 | 2026-03-12 | | 1.7.6 | 1.18.1 | 2026-02-25 | | 1.7.5 | 1.18.1 | 2026-02-24 | | 1.7.4 | 1.18.1 | 2026-02-03 | | 1.7.3 | 1.18.1 | 2026-01-28 | | 1.7.2 | 1.18.1 | 2026-01-27 | | 1.7.1 | 1.18.1 | 2026-01-12 | | 1.7.0 | 1.18.1 | 2026-01-09 | | 1.6.3 | 1.17.0 | 2026-01-07 | | 1.6.2 | 1.17.0 | 2025-12-22 | | 1.6.1 | 1.17.0 | 2025-12-11 | | 1.6.0 | 1.16.0 | 2025-12-01 | | 1.5.5 | 1.15.1 | 2025-12-22 | | 1.5.4 | 1.15.1 | 2025-12-16 | | 1.5.3 | 1.15.1 | 2025-11-24 | | 1.5.2 | 1.15.1 | 2025-11-18 | | 1.5.1 | 1.15.1 | 2025-11-13 | | 1.5.0 | 1.15.0 | 2025-11-10 | | 1.4.13 | 1.13.5 | 2025-11-06 | | 1.4.12 | 1.13.5 | 2025-10-23 | | 1.4.11 | 1.13.5 | 2025-10-17 | | 1.4.10 | 1.13.5 | 2025-10-01 | | 1.4.9 | 1.13.5 | 2025-09-26 | | 1.4.8 | 1.13.5 | 2025-09-23 | | 1.4.7 | 1.13.5 | 2025-09-16 | | 1.4.6 | 1.13.5 | 2025-09-10 | | 1.4.5 | 1.14.0 | 2025-09-08 | | 1.4.4 | 1.13.5 | 2025-09-03 | | 1.4.3 | 1.13.5 | 2025-08-29 | | 1.4.2 | 1.13.5 | 2025-08-28 | | 1.4.1 | 1.13.5 | 2025-08-27 | | 1.4.0 | 1.13.5 | 2025-08-26 | | 1.3.9 | 1.13.5 | 2025-09-08 | | 1.3.8 | 1.13.5 | 2025-08-26 | | 1.3.7 | 1.13.5 | 2025-08-14 | | 1.3.6 | 1.13.5 | 2025-08-11 | | 1.3.5 | 1.13.5 | 2025-08-05 | | 1.3.4 | 1.13.5 | 2025-07-24 | | 1.3.3 | 1.13.3 | 2025-07-15 | | 1.3.2 | 1.13.3 | 2025-06-30 | | 1.3.1 | 1.13.2 | 2025-06-27 | | 1.3.0 | 1.13.0 | 2025-06-12 | | 1.2.12 | 1.12.1 | 2025-06-03 | | 1.2.11 | 1.12.1 | 2025-05-23 | | 1.2.10 | 1.12.1 | 2025-05-14 | | 1.2.9 | 1.12.0 | 2025-05-07 | | 1.2.8 | 1.12.0 | 2025-05-01 | | 1.2.7 | 1.11.1 | 2025-04-28 | | 1.2.6 | 1.10.2 | 2025-04-18 | | 1.2.5 | 1.10.2 | 2025-04-12 | | 1.2.4 | 1.10.1 | 2025-04-04 | | 1.2.3 | 1.10.0 | 2025-04-01 | | 1.2.2 | 1.9.2 | 2025-03-28 | | 1.2.1 | 1.9.1 | 2025-03-26 | | 1.2.0 | 1.8.0 | 2025-03-21 | | 1.1.10 | 1.7.2 | 2025-04-01 | | 1.1.9 | 1.7.2 | 2025-03-17 | | 1.1.8 | 1.7.2 | 2025-03-08 | | 1.1.7 | 1.7.0 | 2025-02-18 | | 1.1.6 | 1.7.1 | 2025-01-31 | | 1.1.5 | 1.7.0 | 2025-01-24 | | 1.1.4 | 1.6.1 | 2025-01-17 | | 1.1.3 | 1.6.0 | 2025-01-16 | | 1.1.2 | 1.5.0 | 2025-01-09 | | 1.1.1 | 1.4.0 | 2025-01-06 | | 1.1.0 | 1.3.0 | 2024-12-30 | | 1.0.10 | 1.1.0 | 2024-12-20 | | 1.0.9 | 1.1.0 | 2024-12-13 | | 1.0.8 | 1.1.0 | 2024-12-03 | | 1.0.7 | 1.0.1 | 2024-11-20 | | 1.0.6 | 1.0.1 | 2024-11-18 | | 1.0.5 | 1.0.1 | 2024-11-15 | | 1.0.4 | 1.0.1 | 2024-11-13 | | 1.0.3 | 1.0.0 | 2024-11-08 | | 1.0.2 | 1.0.0 | 2024-11-06 | | 1.0.1 | 1.0.0 | 2024-10-31 | | 0.16.4 | 0.14.0 | 2024-10-17 | | 0.16.3 | 0.14.0 | 2024-10-10 | | 0.16.2 | 0.13.1 | 2024-10-01 | | 0.16.1 | 0.13.1 | 2024-09-25 | ## Python version support[​](#python-version-support "Direct link to Python version support") | SDK version | Python versions | | --------------- | ---------------------------- | | >= 1.17.0 | 3.10, 3.11, 3.12, 3.13, 3.14 | | 1.16.0 | 3.10, 3.11, 3.12, 3.13 | | 1.13.0 - 1.15.x | 3.9, 3.10, 3.11, 3.12, 3.13 | note The Infrahub server requires Python 3.12 or later. The SDK supports older Python versions so it can be used from environments that do not run the server itself. ## Feature-specific requirements[​](#feature-specific-requirements "Direct link to Feature-specific requirements") Some SDK features require a minimum Infrahub version: | Feature | Minimum SDK | Minimum Infrahub | | ------------------------- | ----------- | ---------------- | | infrahubctl branch report | 1.19.0 | 1.7 | | FileObject support | 1.19.0 | 1.8 | | NumberPool support | 1.13.0 | 1.3 | ## General guidance[​](#general-guidance "Direct link to General guidance") * **Use the SDK version that matches your Infrahub release.** The version mapping table above shows which SDK version was tested and shipped with each Infrahub release. * **Patch upgrades within a minor SDK version are safe.** For example, if your Infrahub version pins SDK 1.18.0, upgrading to 1.18.1 is safe. * **Upgrading to a newer minor SDK version may work** but is not guaranteed. New SDK features may depend on server-side changes that are not present in older Infrahub versions. --- # Python SDK Configuration The Python SDK (Async or Sync) client can be configured using an instance of the `Config` class. * Async * Sync ``` from infrahub_sdk import Config, InfrahubClient config = Config(address="http://infrahub:8080", api_token="123-xyz-invalid-token") client = InfrahubClient(config=config) ``` ``` from infrahub_sdk import Config, InfrahubClientSync config = Config(address="http://infrahub:8080", api_token="123-xyz-invalid-token") client = InfrahubClientSync(config=config) ``` The following settings can be defined in the `Config` class ## address[​](#address "Direct link to address") **Description**: The URL to use when connecting to Infrahub.
**Type**: `string`
**Default value**:
**Environment variable**: `INFRAHUB_ADDRESS`
## api\_token[​](#api_token "Direct link to api_token") **Description**: API token for authentication against Infrahub.
**Type**: `string`
**Environment variable**: `INFRAHUB_API_TOKEN`
## echo\_graphql\_queries[​](#echo_graphql_queries "Direct link to echo_graphql_queries") **Description**: If set the GraphQL query and variables will be echoed to the screen
**Type**: `boolean`
**Default value**: False
**Environment variable**: `INFRAHUB_ECHO_GRAPHQL_QUERIES`
## username[​](#username "Direct link to username") **Description**: Username for accessing Infrahub
**Type**: `string`
**Environment variable**: `INFRAHUB_USERNAME`
## password[​](#password "Direct link to password") **Description**: Password for accessing Infrahub
**Type**: `string`
**Environment variable**: `INFRAHUB_PASSWORD`
## default\_branch[​](#default_branch "Direct link to default_branch") **Description**: Default branch to target if not specified for each request.
**Type**: `string`
**Default value**: main
**Environment variable**: `INFRAHUB_DEFAULT_BRANCH`
## default\_branch\_from\_git[​](#default_branch_from_git "Direct link to default_branch_from_git") **Description**: Indicates if the default Infrahub branch to target should come from the active branch in the local Git repository.
**Type**: `boolean`
**Default value**: False
**Environment variable**: `INFRAHUB_DEFAULT_BRANCH_FROM_GIT`
## identifier[​](#identifier "Direct link to identifier") **Description**: Tracker identifier
**Type**: `string`
**Environment variable**: `INFRAHUB_IDENTIFIER`
## insert\_tracker[​](#insert_tracker "Direct link to insert_tracker") **Description**: Insert a tracker on queries to the server
**Type**: `boolean`
**Default value**: False
**Environment variable**: `INFRAHUB_INSERT_TRACKER`
## max\_concurrent\_execution[​](#max_concurrent_execution "Direct link to max_concurrent_execution") **Description**: Max concurrent execution in batch mode
**Type**: `integer`
**Default value**: 5
**Environment variable**: `INFRAHUB_MAX_CONCURRENT_EXECUTION`
## mode[​](#mode "Direct link to mode") **Description**: Default mode for the client
**Type**: `object`
**Environment variable**: `INFRAHUB_MODE`
## pagination\_size[​](#pagination_size "Direct link to pagination_size") **Description**: Page size for queries to the server
**Type**: `integer`
**Default value**: 50
**Environment variable**: `INFRAHUB_PAGINATION_SIZE`
## retry\_delay[​](#retry_delay "Direct link to retry_delay") **Description**: Number of seconds to wait until attempting a retry.
**Type**: `integer`
**Default value**: 5
**Environment variable**: `INFRAHUB_RETRY_DELAY`
## retry\_on\_failure[​](#retry_on_failure "Direct link to retry_on_failure") **Description**: Retry operation in case of failure
**Type**: `boolean`
**Default value**: False
**Environment variable**: `INFRAHUB_RETRY_ON_FAILURE`
## max\_retry\_duration[​](#max_retry_duration "Direct link to max_retry_duration") **Description**: Maximum duration until we stop attempting to retry if enabled.
**Type**: `integer`
**Default value**: 300
**Environment variable**: `INFRAHUB_MAX_RETRY_DURATION`
## schema\_converge\_timeout[​](#schema_converge_timeout "Direct link to schema_converge_timeout") **Description**: Number of seconds to wait for schema to have converged
**Type**: `integer`
**Default value**: 60
**Environment variable**: `INFRAHUB_SCHEMA_CONVERGE_TIMEOUT`
## timeout[​](#timeout "Direct link to timeout") **Description**: Default connection timeout in seconds
**Type**: `integer`
**Default value**: 60
**Environment variable**: `INFRAHUB_TIMEOUT`
## transport[​](#transport "Direct link to transport") **Description**: Set an alternate transport using a predefined option
**Type**: `object`
**Environment variable**: `INFRAHUB_TRANSPORT`
## proxy[​](#proxy "Direct link to proxy") **Description**: Proxy address
**Type**: `string`
**Environment variable**: `INFRAHUB_PROXY`
## proxy\_mounts[​](#proxy_mounts "Direct link to proxy_mounts") **Description**: Proxy mounts configuration
**Type**: `object`
**Environment variable**: `INFRAHUB_PROXY_MOUNTS`
## marketplace\_url[​](#marketplace_url "Direct link to marketplace_url") **Description**: Base URL for the Infrahub Marketplace.
**Type**: `string`
**Default value**:
**Environment variable**: `INFRAHUB_MARKETPLACE_URL`
## update\_group\_context[​](#update_group_context "Direct link to update_group_context") **Description**: Update GraphQL query groups
**Type**: `boolean`
**Default value**: False
**Environment variable**: `INFRAHUB_UPDATE_GROUP_CONTEXT`
## tls\_insecure[​](#tls_insecure "Direct link to tls_insecure") **Description**:
Indicates if TLS certificates are verified. Enabling this option will disable: CA verification, expiry date verification, hostname verification). Can be useful to test with self-signed certificates.
**Type**: `boolean`
**Default value**: False
**Environment variable**: `INFRAHUB_TLS_INSECURE`
## tls\_ca\_file[​](#tls_ca_file "Direct link to tls_ca_file") **Description**: File path to CA cert or bundle in PEM format
**Type**: `string`
**Environment variable**: `INFRAHUB_TLS_CA_FILE`
## recorder[​](#recorder "Direct link to recorder") **Property**: recorder
**Description**: Select builtin recorder for later replay.
**Type**: `RecorderType`
**Default value**: RecorderType.NONE
## custom\_recorder[​](#custom_recorder "Direct link to custom_recorder") **Property**: custom\_recorder
**Description**: Provides a way to record responses from the Infrahub API
**Type**: `Recorder` (protocol)
**Default value**: NoRecorder.default
## requester[​](#requester "Direct link to requester") **Property**: requester
**Type**: `AsyncRequester`
**Default value**: None
## sync\_requester[​](#sync_requester "Direct link to sync_requester") **Property**: sync\_requester
**Type**: `SyncRequester`
**Default value**: None
--- # Python SDK Templating Filters can be used when defining [computed attributes](https://docs.infrahub.app/guides/computed-attributes) or [Jinja2 Transforms](https://docs.infrahub.app/guides/jinja2-transform) within Infrahub. ## Execution contexts[​](#execution-contexts "Direct link to Execution contexts") Filters are restricted based on the execution context in which a template is rendered: * **CORE** — Computed attributes evaluated inside the Infrahub API server. Only fully trusted filters (no I/O, no side effects) are allowed. * **WORKER** — Jinja2 transforms executed on Prefect background workers. Trusted filters and Infrahub client-dependent filters are allowed. * **LOCAL** — Local CLI rendering and unrestricted usage. All filters are allowed. The `validate()` method on `Jinja2Template` accepts an optional `context` parameter to enforce these restrictions: ``` from infrahub_sdk.template import Jinja2Template from infrahub_sdk.template.filters import ExecutionContext template = Jinja2Template(template="{{ sid | artifact_content }}") # Raises JinjaTemplateOperationViolationError — blocked in CORE template.validate(context=ExecutionContext.CORE) # Passes — allowed in WORKER template.validate(context=ExecutionContext.WORKER) ``` For backward compatibility, `validate(restricted=True)` maps to `CORE` and `validate(restricted=False)` maps to `LOCAL`. ## Builtin Jinja2 filters[​](#builtin-jinja2-filters "Direct link to Builtin Jinja2 filters") The following filters are [shipped with Jinja2](https://jinja.palletsprojects.com/en/stable/templates/#list-of-builtin-filters) and enabled within Infrahub. | Name | CORE | WORKER | LOCAL | | -------------- | ---- | ------ | ----- | | abs | ✅ | ✅ | ✅ | | attr | ❌ | ✅ | ✅ | | batch | ❌ | ✅ | ✅ | | capitalize | ✅ | ✅ | ✅ | | center | ✅ | ✅ | ✅ | | count | ✅ | ✅ | ✅ | | d | ✅ | ✅ | ✅ | | default | ✅ | ✅ | ✅ | | dictsort | ❌ | ✅ | ✅ | | e | ✅ | ✅ | ✅ | | escape | ✅ | ✅ | ✅ | | filesizeformat | ✅ | ✅ | ✅ | | first | ✅ | ✅ | ✅ | | float | ✅ | ✅ | ✅ | | forceescape | ✅ | ✅ | ✅ | | format | ✅ | ✅ | ✅ | | groupby | ❌ | ✅ | ✅ | | indent | ✅ | ✅ | ✅ | | int | ✅ | ✅ | ✅ | | items | ❌ | ✅ | ✅ | | join | ✅ | ✅ | ✅ | | last | ✅ | ✅ | ✅ | | length | ✅ | ✅ | ✅ | | list | ✅ | ✅ | ✅ | | lower | ✅ | ✅ | ✅ | | map | ❌ | ✅ | ✅ | | max | ✅ | ✅ | ✅ | | min | ✅ | ✅ | ✅ | | pprint | ❌ | ✅ | ✅ | | random | ❌ | ✅ | ✅ | | reject | ❌ | ✅ | ✅ | | rejectattr | ❌ | ✅ | ✅ | | replace | ✅ | ✅ | ✅ | | reverse | ✅ | ✅ | ✅ | | round | ✅ | ✅ | ✅ | | safe | ❌ | ✅ | ✅ | | select | ❌ | ✅ | ✅ | | selectattr | ❌ | ✅ | ✅ | | slice | ✅ | ✅ | ✅ | | sort | ❌ | ✅ | ✅ | | string | ✅ | ✅ | ✅ | | striptags | ✅ | ✅ | ✅ | | sum | ✅ | ✅ | ✅ | | title | ✅ | ✅ | ✅ | | tojson | ❌ | ✅ | ✅ | | trim | ✅ | ✅ | ✅ | | truncate | ✅ | ✅ | ✅ | | unique | ❌ | ✅ | ✅ | | upper | ✅ | ✅ | ✅ | | urlencode | ✅ | ✅ | ✅ | | urlize | ❌ | ✅ | ✅ | | wordcount | ✅ | ✅ | ✅ | | wordwrap | ✅ | ✅ | ✅ | | xmlattr | ❌ | ✅ | ✅ | ## Netutils filters[​](#netutils-filters "Direct link to Netutils filters") The following Jinja2 filters from [Netutils](https://netutils.readthedocs.io) are included within Infrahub. | Name | CORE | WORKER | LOCAL | | ---------------------------------- | ---- | ------ | ----- | | abbreviated\_interface\_name | ✅ | ✅ | ✅ | | abbreviated\_interface\_name\_list | ✅ | ✅ | ✅ | | asn\_to\_int | ✅ | ✅ | ✅ | | bits\_to\_name | ✅ | ✅ | ✅ | | bytes\_to\_name | ✅ | ✅ | ✅ | | canonical\_interface\_name | ✅ | ✅ | ✅ | | canonical\_interface\_name\_list | ✅ | ✅ | ✅ | | cidr\_to\_netmask | ✅ | ✅ | ✅ | | cidr\_to\_netmaskv6 | ✅ | ✅ | ✅ | | clean\_config | ✅ | ✅ | ✅ | | compare\_version\_loose | ✅ | ✅ | ✅ | | compare\_version\_strict | ✅ | ✅ | ✅ | | config\_compliance | ✅ | ✅ | ✅ | | config\_section\_not\_parsed | ✅ | ✅ | ✅ | | delimiter\_change | ✅ | ✅ | ✅ | | diff\_network\_config | ✅ | ✅ | ✅ | | feature\_compliance | ✅ | ✅ | ✅ | | find\_unordered\_cfg\_lines | ✅ | ✅ | ✅ | | fqdn\_to\_ip | ❌ | ❌ | ✅ | | get\_all\_host | ❌ | ❌ | ✅ | | get\_broadcast\_address | ✅ | ✅ | ✅ | | get\_first\_usable | ✅ | ✅ | ✅ | | get\_ips\_sorted | ✅ | ✅ | ✅ | | get\_nist\_urls | ✅ | ✅ | ✅ | | get\_nist\_vendor\_platform\_urls | ✅ | ✅ | ✅ | | get\_oui | ✅ | ✅ | ✅ | | get\_peer\_ip | ✅ | ✅ | ✅ | | get\_range\_ips | ✅ | ✅ | ✅ | | get\_upgrade\_path | ✅ | ✅ | ✅ | | get\_usable\_range | ✅ | ✅ | ✅ | | hash\_data | ✅ | ✅ | ✅ | | int\_to\_asdot | ✅ | ✅ | ✅ | | interface\_range\_compress | ✅ | ✅ | ✅ | | interface\_range\_expansion | ✅ | ✅ | ✅ | | ip\_addition | ✅ | ✅ | ✅ | | ip\_subtract | ✅ | ✅ | ✅ | | ip\_to\_bin | ✅ | ✅ | ✅ | | ip\_to\_hex | ✅ | ✅ | ✅ | | ipaddress\_address | ✅ | ✅ | ✅ | | ipaddress\_interface | ✅ | ✅ | ✅ | | ipaddress\_network | ✅ | ✅ | ✅ | | is\_classful | ✅ | ✅ | ✅ | | is\_fqdn\_resolvable | ❌ | ❌ | ✅ | | is\_ip | ✅ | ✅ | ✅ | | is\_ip\_range | ✅ | ✅ | ✅ | | is\_ip\_within | ✅ | ✅ | ✅ | | is\_netmask | ✅ | ✅ | ✅ | | is\_network | ✅ | ✅ | ✅ | | is\_reversible\_wildcardmask | ✅ | ✅ | ✅ | | is\_valid\_mac | ✅ | ✅ | ✅ | | longest\_prefix\_match | ✅ | ✅ | ✅ | | mac\_normalize | ✅ | ✅ | ✅ | | mac\_to\_format | ✅ | ✅ | ✅ | | mac\_to\_int | ✅ | ✅ | ✅ | | mac\_type | ✅ | ✅ | ✅ | | name\_to\_bits | ✅ | ✅ | ✅ | | name\_to\_bytes | ✅ | ✅ | ✅ | | name\_to\_name | ✅ | ✅ | ✅ | | netmask\_to\_cidr | ✅ | ✅ | ✅ | | netmask\_to\_wildcardmask | ✅ | ✅ | ✅ | | normalise\_delimiter\_caret\_c | ✅ | ✅ | ✅ | | paloalto\_panos\_brace\_to\_set | ✅ | ✅ | ✅ | | paloalto\_panos\_clean\_newlines | ✅ | ✅ | ✅ | | regex\_findall | ❌ | ❌ | ✅ | | regex\_match | ❌ | ❌ | ✅ | | regex\_search | ❌ | ❌ | ✅ | | regex\_split | ❌ | ❌ | ✅ | | regex\_sub | ❌ | ❌ | ✅ | | sanitize\_config | ✅ | ✅ | ✅ | | section\_config | ✅ | ✅ | ✅ | | sort\_interface\_list | ✅ | ✅ | ✅ | | split\_interface | ✅ | ✅ | ✅ | | uptime\_seconds\_to\_string | ✅ | ✅ | ✅ | | uptime\_string\_to\_seconds | ✅ | ✅ | ✅ | | version\_metadata | ✅ | ✅ | ✅ | | vlanconfig\_to\_list | ✅ | ✅ | ✅ | | vlanlist\_to\_config | ✅ | ✅ | ✅ | | wildcardmask\_to\_netmask | ✅ | ✅ | ✅ | ## Infrahub filters[​](#infrahub-filters "Direct link to Infrahub filters") These filters are provided by the Infrahub SDK for artifact and file object content composition. | Name | CORE | WORKER | LOCAL | | ----------------------------- | ---- | ------ | ----- | | `artifact_content` | ❌ | ✅ | ❌ | | `file_object_content` | ❌ | ✅ | ❌ | | `file_object_content_by_hfid` | ❌ | ✅ | ❌ | | `file_object_content_by_id` | ❌ | ✅ | ❌ | | `from_json` | ✅ | ✅ | ✅ | | `from_yaml` | ✅ | ✅ | ✅ | ### Usage examples[​](#usage-examples "Direct link to Usage examples") **Inline artifact content by `storage_id`:** ``` {{ artifact.node.storage_id.value | artifact_content }} ``` **Inline file object content:** ``` {# By storage_id #} {{ file_object.storage_id.value | file_object_content }} {# By node UUID #} {{ file_object.id | file_object_content_by_id }} {# By Human-Friendly ID #} {{ hfid_components | file_object_content_by_hfid(kind="NetworkCircuitContract") }} ``` **Parse structured content with chaining:** ``` {# JSON artifact → access parsed fields #} {% set config = artifact.node.storage_id.value | artifact_content | from_json %} interface {{ config.interface_name }} ip address {{ config.ip_address }} {# YAML artifact → iterate parsed data #} {% set config = artifact.node.storage_id.value | artifact_content | from_yaml %} {% for route in config.static_routes %} ip route {{ route.prefix }} {{ route.next_hop }} {% endfor %} ``` Client-dependent filters (`artifact_content`, `file_object_content`, `file_object_content_by_id`, `file_object_content_by_hfid`) require an `InfrahubClient` to be passed to `Jinja2Template`: ``` from infrahub_sdk.template import Jinja2Template # At construction time template = Jinja2Template(template=my_template, client=client) # Or via deferred injection template = Jinja2Template(template=my_template) template.set_client(client) ``` ## Known issues[​](#known-issues "Direct link to Known issues") ### Unable to combine the map and sort filters ()[​](#unable-to-combine-the-map-and-sort-filters-httpsgithubcompalletsjinjaissues2081 "Direct link to unable-to-combine-the-map-and-sort-filters-httpsgithubcompalletsjinjaissues2081") When using the `map` filter with the `sort` filter, you may encounter the following error: ``` TypeError: 'async_generator' object is not iterable ``` **As a workaround you can use the `list` filter between `map` and `sort` filter.** --- # `infrahub_sdk.client` ## Classes[​](#classes "Direct link to Classes") ### `InfrahubClient`[​](#infrahubclient "Direct link to infrahubclient") GraphQL Client to interact with Infrahub. **Methods:** #### `get`[​](#get "Direct link to get") ``` get(self, kind: type[SchemaType], raise_when_missing: Literal[False], at: Timestamp | None = ..., branch: str | None = ..., timeout: int | None = ..., id: str | None = ..., hfid: list[str] | None = ..., include: list[str] | None = ..., exclude: list[str] | None = ..., populate_store: bool = ..., fragment: bool = ..., prefetch_relationships: bool = ..., property: bool = ..., include_metadata: bool = ..., query_name: str | None = ..., **kwargs: Any) -> SchemaType | None ``` Show 6 other overloads #### `get`[​](#get-1 "Direct link to get-1") ``` get(self, kind: type[SchemaType], raise_when_missing: Literal[True], at: Timestamp | None = ..., branch: str | None = ..., timeout: int | None = ..., id: str | None = ..., hfid: list[str] | None = ..., include: list[str] | None = ..., exclude: list[str] | None = ..., populate_store: bool = ..., fragment: bool = ..., prefetch_relationships: bool = ..., property: bool = ..., include_metadata: bool = ..., query_name: str | None = ..., **kwargs: Any) -> SchemaType ``` #### `get`[​](#get-2 "Direct link to get-2") ``` get(self, kind: type[SchemaType], raise_when_missing: bool = ..., at: Timestamp | None = ..., branch: str | None = ..., timeout: int | None = ..., id: str | None = ..., hfid: list[str] | None = ..., include: list[str] | None = ..., exclude: list[str] | None = ..., populate_store: bool = ..., fragment: bool = ..., prefetch_relationships: bool = ..., property: bool = ..., include_metadata: bool = ..., query_name: str | None = ..., **kwargs: Any) -> SchemaType ``` #### `get`[​](#get-3 "Direct link to get-3") ``` get(self, kind: str, raise_when_missing: Literal[False], at: Timestamp | None = ..., branch: str | None = ..., timeout: int | None = ..., id: str | None = ..., hfid: list[str] | None = ..., include: list[str] | None = ..., exclude: list[str] | None = ..., populate_store: bool = ..., fragment: bool = ..., prefetch_relationships: bool = ..., property: bool = ..., include_metadata: bool = ..., query_name: str | None = ..., **kwargs: Any) -> InfrahubNode | None ``` #### `get`[​](#get-4 "Direct link to get-4") ``` get(self, kind: str, raise_when_missing: Literal[True], at: Timestamp | None = ..., branch: str | None = ..., timeout: int | None = ..., id: str | None = ..., hfid: list[str] | None = ..., include: list[str] | None = ..., exclude: list[str] | None = ..., populate_store: bool = ..., fragment: bool = ..., prefetch_relationships: bool = ..., property: bool = ..., include_metadata: bool = ..., query_name: str | None = ..., **kwargs: Any) -> InfrahubNode ``` #### `get`[​](#get-5 "Direct link to get-5") ``` get(self, kind: str, raise_when_missing: bool = ..., at: Timestamp | None = ..., branch: str | None = ..., timeout: int | None = ..., id: str | None = ..., hfid: list[str] | None = ..., include: list[str] | None = ..., exclude: list[str] | None = ..., populate_store: bool = ..., fragment: bool = ..., prefetch_relationships: bool = ..., property: bool = ..., include_metadata: bool = ..., query_name: str | None = ..., **kwargs: Any) -> InfrahubNode ``` #### `get`[​](#get-6 "Direct link to get-6") ``` get(self, kind: str | type[SchemaType], raise_when_missing: bool = True, at: Timestamp | None = None, branch: str | None = None, timeout: int | None = None, id: str | None = None, hfid: list[str] | None = None, include: list[str] | None = None, exclude: list[str] | None = None, populate_store: bool = True, fragment: bool = False, prefetch_relationships: bool = False, property: bool = False, include_metadata: bool = False, query_name: str | None = None, **kwargs: Any) -> InfrahubNode | SchemaType | None ``` #### `delete`[​](#delete "Direct link to delete") ``` delete(self, kind: str | type[SchemaType], id: str, branch: str | None = None) -> None ``` #### `create`[​](#create "Direct link to create") ``` create(self, kind: str | type[SchemaType], data: dict | None = None, branch: str | None = None, timeout: int | None = None, **kwargs: Any) -> InfrahubNode | SchemaType ``` Show 2 other overloads #### `create`[​](#create-1 "Direct link to create-1") ``` create(self, kind: str, data: dict | None = ..., branch: str | None = ..., **kwargs: Any) -> InfrahubNode ``` #### `create`[​](#create-2 "Direct link to create-2") ``` create(self, kind: type[SchemaType], data: dict | None = ..., branch: str | None = ..., **kwargs: Any) -> SchemaType ``` #### `get_version`[​](#get_version "Direct link to get_version") ``` get_version(self) -> str ``` Return the Infrahub version. #### `get_user`[​](#get_user "Direct link to get_user") ``` get_user(self) -> dict ``` Return user information. #### `get_user_permissions`[​](#get_user_permissions "Direct link to get_user_permissions") ``` get_user_permissions(self) -> dict ``` Return user permissions. #### `count`[​](#count "Direct link to count") ``` count(self, kind: str | type[SchemaType], at: Timestamp | None = None, branch: str | None = None, timeout: int | None = None, partial_match: bool = False, query_name: str | None = None, **kwargs: Any) -> int ``` Return the number of nodes of a given kind. #### `traverse_paths`[​](#traverse_paths "Direct link to traverse_paths") ``` traverse_paths(self, source: str | InfrahubNode, destination: str | InfrahubNode) -> PathTraversalResult ``` Find the shortest path(s) between two nodes in the graph. Kind filters (`kind_filter`, `excluded_kinds`, `included_kinds`) accept kind-name strings and/or generated protocol classes. `relationship_filter` matches schema relationship identifiers (for example `dcimconnector__dcimendpoint`), not the per-side names shown in the result. Requires Infrahub 1.10 or later. **Args:** * `source`: Node to start from, as a UUID string or an `InfrahubNode` instance. * `destination`: Node to reach, as a UUID string or an `InfrahubNode` instance. * `max_depth`: Maximum number of relationship hops to explore. * `max_paths`: Maximum number of paths to return. * `kind_filter`: Only traverse through nodes of these kinds. * `relationship_filter`: Only traverse through these schema relationship identifiers. * `excluded_namespaces`: Schema namespaces to exclude from traversal. * `excluded_kinds`: Node kinds to exclude from traversal. * `included_kinds`: Node kinds to re-include when otherwise excluded by default. * `shortest_paths_only`: When True (the server default), only return the shortest path(s); when False, return all loopless paths (exhaustive mode). * `branch`: Name of the branch to query from. Defaults to default\_branch. * `at`: Time of the query. Defaults to now. * `timeout`: Overrides the default GraphQL timeout, in seconds. **Raises:** * `VersionNotSupportedError`: If the server does not support graph traversal (pre-1.10). * `GraphQLError`: When the GraphQL response contains errors (e.g. unknown node). #### `path_exists`[​](#path_exists "Direct link to path_exists") ``` path_exists(self, source: str | InfrahubNode, destination: str | InfrahubNode) -> bool ``` Return whether at least one path connects `source` to `destination`. Convenience wrapper around :meth:`traverse_paths` for checks: it requests a single path (the cheapest way to answer "is there a path?") and returns `True` if one was found. Accepts the same source/destination and filter arguments as `traverse_paths`. Requires Infrahub 1.10 or later. **Args:** * `source`: Node to start from, as a UUID string or an `InfrahubNode` instance. * `destination`: Node to reach, as a UUID string or an `InfrahubNode` instance. * `max_depth`: Maximum number of relationship hops to explore. * `kind_filter`: Only traverse through nodes of these kinds. * `relationship_filter`: Only traverse through these schema relationship identifiers. * `excluded_namespaces`: Schema namespaces to exclude from traversal. * `excluded_kinds`: Node kinds to exclude from traversal. * `included_kinds`: Node kinds to re-include when otherwise excluded by default. * `branch`: Name of the branch to query from. Defaults to default\_branch. * `at`: Time of the query. Defaults to now. * `timeout`: Overrides the default GraphQL timeout, in seconds. **Raises:** * `VersionNotSupportedError`: If the server does not support graph traversal (pre-1.10). * `GraphQLError`: When the GraphQL response contains errors (e.g. unknown node). #### `reachable_nodes`[​](#reachable_nodes "Direct link to reachable_nodes") ``` reachable_nodes(self, source: str | InfrahubNode, target_kinds: list[str | type[SchemaType]]) -> ReachableNodesResult ``` Find all nodes of the given kinds reachable from a source node. `target_kinds` accepts kind-name strings and/or generated protocol classes. Requires Infrahub 1.10 or later. **Args:** * `source`: Node to start from, as a UUID string or an `InfrahubNode` instance. * `target_kinds`: Kinds of nodes to look for, as kind-name strings or protocol classes. * `max_depth`: Maximum number of relationship hops to explore. * `max_results`: Maximum number of reachable nodes to return. * `max_paths`: Maximum number of paths to compute per reachable node. * `shortest_paths_only`: When True, only return the shortest path(s) to each node. * `branch`: Name of the branch to query from. Defaults to default\_branch. * `at`: Time of the query. Defaults to now. * `timeout`: Overrides the default GraphQL timeout, in seconds. **Raises:** * `VersionNotSupportedError`: If the server does not support graph traversal (pre-1.10). * `GraphQLError`: When the GraphQL response contains errors (e.g. unknown node). #### `all`[​](#all "Direct link to all") ``` all(self, kind: type[SchemaType], at: Timestamp | None = ..., branch: str | None = ..., timeout: int | None = ..., populate_store: bool = ..., offset: int | None = ..., limit: int | None = ..., include: list[str] | None = ..., exclude: list[str] | None = ..., fragment: bool = ..., prefetch_relationships: bool = ..., property: bool = ..., parallel: bool = ..., order: Order | None = ..., include_metadata: bool = ..., query_name: str | None = ...) -> list[SchemaType] ``` Show 2 other overloads #### `all`[​](#all-1 "Direct link to all-1") ``` all(self, kind: str, at: Timestamp | None = ..., branch: str | None = ..., timeout: int | None = ..., populate_store: bool = ..., offset: int | None = ..., limit: int | None = ..., include: list[str] | None = ..., exclude: list[str] | None = ..., fragment: bool = ..., prefetch_relationships: bool = ..., property: bool = ..., parallel: bool = ..., order: Order | None = ..., include_metadata: bool = ..., query_name: str | None = ...) -> list[InfrahubNode] ``` #### `all`[​](#all-2 "Direct link to all-2") ``` all(self, kind: str | type[SchemaType], at: Timestamp | None = None, branch: str | None = None, timeout: int | None = None, populate_store: bool = True, offset: int | None = None, limit: int | None = None, include: list[str] | None = None, exclude: list[str] | None = None, fragment: bool = False, prefetch_relationships: bool = False, property: bool = False, parallel: bool = False, order: Order | None = None, include_metadata: bool = False, query_name: str | None = None) -> list[InfrahubNode] | list[SchemaType] ``` Retrieve all nodes of a given kind. **Args:** * `kind`: kind of the nodes to query * `at`: Time of the query. Defaults to Now. * `branch`: Name of the branch to query from. Defaults to default\_branch. * `populate_store`: Flag to indicate whether to populate the store with the retrieved nodes. * `timeout`: Overrides default timeout used when querying the GraphQL API. Specified in seconds. * `offset`: The offset for pagination. * `limit`: The limit for pagination. * `include`: List of attributes or relationships to include in the query. * `exclude`: List of attributes or relationships to exclude from the query. * `fragment`: Flag to use GraphQL fragments for generic schemas. * `prefetch_relationships`: Flag to indicate whether to pre-fetch related node data. * `parallel`: Whether to use parallel processing for the query. * `order`: Ordering related options. Setting `disable=True` enhances performances. * `include_metadata`: If True, includes node\_metadata and relationship\_metadata in the query. * `query_name`: If provided is used as the GraphQL operation name else All\_\ is used. **Returns:** * list\[InfrahubNode]: List of Nodes #### `filters`[​](#filters "Direct link to filters") ``` filters(self, kind: type[SchemaType], at: Timestamp | None = ..., branch: str | None = ..., timeout: int | None = ..., populate_store: bool = ..., offset: int | None = ..., limit: int | None = ..., include: list[str] | None = ..., exclude: list[str] | None = ..., fragment: bool = ..., prefetch_relationships: bool = ..., partial_match: bool = ..., property: bool = ..., parallel: bool = ..., order: Order | None = ..., include_metadata: bool = ..., query_name: str | None = ..., **kwargs: Any) -> list[SchemaType] ``` Show 2 other overloads #### `filters`[​](#filters-1 "Direct link to filters-1") ``` filters(self, kind: str, at: Timestamp | None = ..., branch: str | None = ..., timeout: int | None = ..., populate_store: bool = ..., offset: int | None = ..., limit: int | None = ..., include: list[str] | None = ..., exclude: list[str] | None = ..., fragment: bool = ..., prefetch_relationships: bool = ..., partial_match: bool = ..., property: bool = ..., parallel: bool = ..., order: Order | None = ..., include_metadata: bool = ..., query_name: str | None = ..., **kwargs: Any) -> list[InfrahubNode] ``` #### `filters`[​](#filters-2 "Direct link to filters-2") ``` filters(self, kind: str | type[SchemaType], at: Timestamp | None = None, branch: str | None = None, timeout: int | None = None, populate_store: bool = True, offset: int | None = None, limit: int | None = None, include: list[str] | None = None, exclude: list[str] | None = None, fragment: bool = False, prefetch_relationships: bool = False, partial_match: bool = False, property: bool = False, parallel: bool = False, order: Order | None = None, include_metadata: bool = False, query_name: str | None = None, **kwargs: Any) -> list[InfrahubNode] | list[SchemaType] ``` Retrieve nodes of a given kind based on provided filters. **Args:** * `kind`: kind of the nodes to query * `at`: Time of the query. Defaults to Now. * `branch`: Name of the branch to query from. Defaults to default\_branch. * `timeout`: Overrides default timeout used when querying the GraphQL API. Specified in seconds. * `populate_store`: Flag to indicate whether to populate the store with the retrieved nodes. * `offset`: The offset for pagination. * `limit`: The limit for pagination. * `include`: List of attributes or relationships to include in the query. * `exclude`: List of attributes or relationships to exclude from the query. * `fragment`: Flag to use GraphQL fragments for generic schemas. * `prefetch_relationships`: Flag to indicate whether to pre-fetch related node data. * `partial_match`: Allow partial match of filter criteria for the query. * `parallel`: Whether to use parallel processing for the query. * `order`: Ordering related options. Setting `disable=True` enhances performances. * `include_metadata`: If True, includes node\_metadata and relationship\_metadata in the query. * `query_name`: If provided is used as the GraphQL operation name else Filters\_\ is used. * `**kwargs`: Additional filter criteria for the query. **Returns:** * list\[InfrahubNodeSync]: List of Nodes that match the given filters. #### `clone`[​](#clone "Direct link to clone") ``` clone(self, branch: str | None = None) -> InfrahubClient ``` Return a cloned version of the client using the same configuration. #### `execute_graphql`[​](#execute_graphql "Direct link to execute_graphql") ``` execute_graphql(self, query: str, variables: dict | None = None, branch_name: str | None = None, at: str | Timestamp | None = None, timeout: int | None = None, tracker: str | None = None, operation_name: str | None = None) -> dict ``` Execute a GraphQL query (or mutation). If retry\_on\_failure is True, the query will retry until the server becomes reachable. **Args:** * `query`: GraphQL Query to execute, can be a query or a mutation * `variables`: Variables to pass along with the GraphQL query. Defaults to None. * `branch_name`: Name of the branch on which the query will be executed. Defaults to None. * `at`: Time when the query should be executed. Defaults to None. * `timeout`: Timeout in second for the query. Defaults to None. * `operation_name`: GraphQL operation name, sent as `operationName` in the request payload so tracing/observability tools can identify the operation. Defaults to None. **Returns:** * The GraphQL data payload (response\["data"]). **Raises:** * `GraphQLError`: When the GraphQL response contains errors. * `ServerNotReachableError`: If the server is not reachable after exhausting retries. * `AuthenticationError`: If the server returns a 401 or 403 response. * `URLNotFoundError`: If the server returns a 404 response. * `Error`: If the response is unexpectedly missing. #### `refresh_login`[​](#refresh_login "Direct link to refresh_login") ``` refresh_login(self) -> None ``` #### `login`[​](#login "Direct link to login") ``` login(self, refresh: bool = False) -> None ``` #### `query_gql_query`[​](#query_gql_query "Direct link to query_gql_query") ``` query_gql_query(self, name: str, variables: dict | None = None, update_group: bool = False, subscribers: list[str] | None = None, params: dict | None = None, branch_name: str | None = None, at: str | None = None, timeout: int | None = None, tracker: str | None = None) -> dict ``` #### `create_diff`[​](#create_diff "Direct link to create_diff") ``` create_diff(self, branch: str, name: str, from_time: datetime, to_time: datetime, wait_until_completion: bool = True) -> bool | str ``` #### `get_diff_summary`[​](#get_diff_summary "Direct link to get_diff_summary") ``` get_diff_summary(self, branch: str, name: str | None = None, from_time: datetime | None = None, to_time: datetime | None = None, timeout: int | None = None, tracker: str | None = None) -> list[NodeDiff] ``` #### `get_diff_tree`[​](#get_diff_tree "Direct link to get_diff_tree") ``` get_diff_tree(self, branch: str, name: str | None = None, from_time: datetime | None = None, to_time: datetime | None = None, timeout: int | None = None, tracker: str | None = None) -> DiffTreeData | None ``` Get complete diff tree with metadata and nodes. Returns None if no diff exists. **Raises:** * `ValueError`: If `from_time` is later than `to_time`. #### `allocate_next_ip_address`[​](#allocate_next_ip_address "Direct link to allocate_next_ip_address") ``` allocate_next_ip_address(self, resource_pool: CoreNode, kind: type[SchemaType], identifier: str | None = ..., prefix_length: int | None = ..., address_type: str | None = ..., data: dict[str, Any] | None = ..., branch: str | None = ..., timeout: int | None = ..., tracker: str | None = ...) -> SchemaType | None ``` Show 2 other overloads #### `allocate_next_ip_address`[​](#allocate_next_ip_address-1 "Direct link to allocate_next_ip_address-1") ``` allocate_next_ip_address(self, resource_pool: CoreNode, kind: None = ..., identifier: str | None = ..., prefix_length: int | None = ..., address_type: str | None = ..., data: dict[str, Any] | None = ..., branch: str | None = ..., timeout: int | None = ..., tracker: str | None = ...) -> CoreNode | None ``` #### `allocate_next_ip_address`[​](#allocate_next_ip_address-2 "Direct link to allocate_next_ip_address-2") ``` allocate_next_ip_address(self, resource_pool: CoreNode, kind: type[SchemaType] | None = None, identifier: str | None = None, prefix_length: int | None = None, address_type: str | None = None, data: dict[str, Any] | None = None, branch: str | None = None, timeout: int | None = None, tracker: str | None = None) -> CoreNode | SchemaType | None ``` Allocate a new IP address by using the provided resource pool. **Args:** * `resource_pool`: Node corresponding to the pool to allocate resources from. * `identifier`: Value to perform idempotent allocation, the same resource will be returned for a given identifier. * `prefix_length`: Length of the prefix to set on the address to allocate. * `address_type`: Kind of the address to allocate. * `data`: A key/value map to use to set attributes values on the allocated address. * `branch`: Name of the branch to allocate from. Defaults to default\_branch. * `timeout`: Flag to indicate whether to populate the store with the retrieved nodes. * `tracker`: The offset for pagination. **Returns:** * Node corresponding to the allocated resource. **Raises:** * `ValueError`: If `resource_pool` is not a `CoreIPAddressPool`. #### `allocate_next_ip_prefix`[​](#allocate_next_ip_prefix "Direct link to allocate_next_ip_prefix") ``` allocate_next_ip_prefix(self, resource_pool: CoreNode, kind: type[SchemaType], identifier: str | None = ..., prefix_length: int | None = ..., member_type: str | None = ..., prefix_type: str | None = ..., data: dict[str, Any] | None = ..., branch: str | None = ..., timeout: int | None = ..., tracker: str | None = ...) -> SchemaType | None ``` Show 2 other overloads #### `allocate_next_ip_prefix`[​](#allocate_next_ip_prefix-1 "Direct link to allocate_next_ip_prefix-1") ``` allocate_next_ip_prefix(self, resource_pool: CoreNode, kind: None = ..., identifier: str | None = ..., prefix_length: int | None = ..., member_type: str | None = ..., prefix_type: str | None = ..., data: dict[str, Any] | None = ..., branch: str | None = ..., timeout: int | None = ..., tracker: str | None = ...) -> CoreNode | None ``` #### `allocate_next_ip_prefix`[​](#allocate_next_ip_prefix-2 "Direct link to allocate_next_ip_prefix-2") ``` allocate_next_ip_prefix(self, resource_pool: CoreNode, kind: type[SchemaType] | None = None, identifier: str | None = None, prefix_length: int | None = None, member_type: str | None = None, prefix_type: str | None = None, data: dict[str, Any] | None = None, branch: str | None = None, timeout: int | None = None, tracker: str | None = None) -> CoreNode | SchemaType | None ``` Allocate a new IP prefix by using the provided resource pool. **Args:** * `resource_pool`: Node corresponding to the pool to allocate resources from. * `identifier`: Value to perform idempotent allocation, the same resource will be returned for a given identifier. * `prefix_length`: Length of the prefix to allocate. * `member_type`: Member type of the prefix to allocate. * `prefix_type`: Kind of the prefix to allocate. * `data`: A key/value map to use to set attributes values on the allocated prefix. * `branch`: Name of the branch to allocate from. Defaults to default\_branch. * `timeout`: Flag to indicate whether to populate the store with the retrieved nodes. * `tracker`: The offset for pagination. **Returns:** * Node corresponding to the allocated resource. **Raises:** * `ValueError`: If `resource_pool` is not a `CoreIPPrefixPool`. #### `create_batch`[​](#create_batch "Direct link to create_batch") ``` create_batch(self, return_exceptions: bool = False) -> InfrahubBatch ``` #### `get_list_repositories`[​](#get_list_repositories "Direct link to get_list_repositories") ``` get_list_repositories(self, branches: dict[str, BranchData] | None = None, kind: str = 'CoreGenericRepository') -> dict[str, RepositoryData] ``` #### `repository_update_commit`[​](#repository_update_commit "Direct link to repository_update_commit") ``` repository_update_commit(self, branch_name: str, repository_id: str, commit: str, is_read_only: bool = False) -> bool ``` #### `convert_object_type`[​](#convert_object_type "Direct link to convert_object_type") ``` convert_object_type(self, node_id: str, target_kind: str, branch: str | None = None, fields_mapping: dict[str, ConversionFieldInput] | None = None) -> InfrahubNode ``` Convert a given node to another kind on a given branch. `fields_mapping` keys are target fields names and its values indicate how to fill in these fields. Any mandatory field not having an equivalent field in the source kind should be specified in this mapping. See for more information. ### `InfrahubClientSync`[​](#infrahubclientsync "Direct link to infrahubclientsync") **Methods:** #### `get`[​](#get-7 "Direct link to get-7") ``` get(self, kind: type[SchemaTypeSync], raise_when_missing: Literal[False], at: Timestamp | None = ..., branch: str | None = ..., timeout: int | None = ..., id: str | None = ..., hfid: list[str] | None = ..., include: list[str] | None = ..., exclude: list[str] | None = ..., populate_store: bool = ..., fragment: bool = ..., prefetch_relationships: bool = ..., property: bool = ..., include_metadata: bool = ..., query_name: str | None = ..., **kwargs: Any) -> SchemaTypeSync | None ``` Show 6 other overloads #### `get`[​](#get-8 "Direct link to get-8") ``` get(self, kind: type[SchemaTypeSync], raise_when_missing: Literal[True], at: Timestamp | None = ..., branch: str | None = ..., timeout: int | None = ..., id: str | None = ..., hfid: list[str] | None = ..., include: list[str] | None = ..., exclude: list[str] | None = ..., populate_store: bool = ..., fragment: bool = ..., prefetch_relationships: bool = ..., property: bool = ..., include_metadata: bool = ..., query_name: str | None = ..., **kwargs: Any) -> SchemaTypeSync ``` #### `get`[​](#get-9 "Direct link to get-9") ``` get(self, kind: type[SchemaTypeSync], raise_when_missing: bool = ..., at: Timestamp | None = ..., branch: str | None = ..., timeout: int | None = ..., id: str | None = ..., hfid: list[str] | None = ..., include: list[str] | None = ..., exclude: list[str] | None = ..., populate_store: bool = ..., fragment: bool = ..., prefetch_relationships: bool = ..., property: bool = ..., include_metadata: bool = ..., query_name: str | None = ..., **kwargs: Any) -> SchemaTypeSync ``` #### `get`[​](#get-10 "Direct link to get-10") ``` get(self, kind: str, raise_when_missing: Literal[False], at: Timestamp | None = ..., branch: str | None = ..., timeout: int | None = ..., id: str | None = ..., hfid: list[str] | None = ..., include: list[str] | None = ..., exclude: list[str] | None = ..., populate_store: bool = ..., fragment: bool = ..., prefetch_relationships: bool = ..., property: bool = ..., include_metadata: bool = ..., query_name: str | None = ..., **kwargs: Any) -> InfrahubNodeSync | None ``` #### `get`[​](#get-11 "Direct link to get-11") ``` get(self, kind: str, raise_when_missing: Literal[True], at: Timestamp | None = ..., branch: str | None = ..., timeout: int | None = ..., id: str | None = ..., hfid: list[str] | None = ..., include: list[str] | None = ..., exclude: list[str] | None = ..., populate_store: bool = ..., fragment: bool = ..., prefetch_relationships: bool = ..., property: bool = ..., include_metadata: bool = ..., query_name: str | None = ..., **kwargs: Any) -> InfrahubNodeSync ``` #### `get`[​](#get-12 "Direct link to get-12") ``` get(self, kind: str, raise_when_missing: bool = ..., at: Timestamp | None = ..., branch: str | None = ..., timeout: int | None = ..., id: str | None = ..., hfid: list[str] | None = ..., include: list[str] | None = ..., exclude: list[str] | None = ..., populate_store: bool = ..., fragment: bool = ..., prefetch_relationships: bool = ..., property: bool = ..., include_metadata: bool = ..., query_name: str | None = ..., **kwargs: Any) -> InfrahubNodeSync ``` #### `get`[​](#get-13 "Direct link to get-13") ``` get(self, kind: str | type[SchemaTypeSync], raise_when_missing: bool = True, at: Timestamp | None = None, branch: str | None = None, timeout: int | None = None, id: str | None = None, hfid: list[str] | None = None, include: list[str] | None = None, exclude: list[str] | None = None, populate_store: bool = True, fragment: bool = False, prefetch_relationships: bool = False, property: bool = False, include_metadata: bool = False, query_name: str | None = None, **kwargs: Any) -> InfrahubNodeSync | SchemaTypeSync | None ``` #### `delete`[​](#delete-1 "Direct link to delete-1") ``` delete(self, kind: str | type[SchemaTypeSync], id: str, branch: str | None = None) -> None ``` #### `create`[​](#create-3 "Direct link to create-3") ``` create(self, kind: str | type[SchemaTypeSync], data: dict | None = None, branch: str | None = None, timeout: int | None = None, **kwargs: Any) -> InfrahubNodeSync | SchemaTypeSync ``` Show 2 other overloads #### `create`[​](#create-4 "Direct link to create-4") ``` create(self, kind: str, data: dict | None = ..., branch: str | None = ..., **kwargs: Any) -> InfrahubNodeSync ``` #### `create`[​](#create-5 "Direct link to create-5") ``` create(self, kind: type[SchemaTypeSync], data: dict | None = ..., branch: str | None = ..., **kwargs: Any) -> SchemaTypeSync ``` #### `get_version`[​](#get_version-1 "Direct link to get_version-1") ``` get_version(self) -> str ``` Return the Infrahub version. #### `get_user`[​](#get_user-1 "Direct link to get_user-1") ``` get_user(self) -> dict ``` Return user information. #### `get_user_permissions`[​](#get_user_permissions-1 "Direct link to get_user_permissions-1") ``` get_user_permissions(self) -> dict ``` Return user permissions. #### `clone`[​](#clone-1 "Direct link to clone-1") ``` clone(self, branch: str | None = None) -> InfrahubClientSync ``` Return a cloned version of the client using the same configuration. #### `execute_graphql`[​](#execute_graphql-1 "Direct link to execute_graphql-1") ``` execute_graphql(self, query: str, variables: dict | None = None, branch_name: str | None = None, at: str | Timestamp | None = None, timeout: int | None = None, tracker: str | None = None, operation_name: str | None = None) -> dict ``` Execute a GraphQL query (or mutation). If retry\_on\_failure is True, the query will retry until the server becomes reachable. **Args:** * `query`: GraphQL Query to execute, can be a query or a mutation * `variables`: Variables to pass along with the GraphQL query. Defaults to None. * `branch_name`: Name of the branch on which the query will be executed. Defaults to None. * `at`: Time when the query should be executed. Defaults to None. * `timeout`: Timeout in second for the query. Defaults to None. * `operation_name`: GraphQL operation name, sent as `operationName` in the request payload so tracing/observability tools can identify the operation. Defaults to None. **Returns:** * The GraphQL data payload (`response["data"]`). **Raises:** * `GraphQLError`: When the GraphQL response contains errors. * `ServerNotReachableError`: If the server is not reachable after exhausting retries. * `AuthenticationError`: If the server returns a 401 or 403 response. * `URLNotFoundError`: If the server returns a 404 response. * `Error`: If the response is unexpectedly missing. #### `count`[​](#count-1 "Direct link to count-1") ``` count(self, kind: str | type[SchemaType], at: Timestamp | None = None, branch: str | None = None, timeout: int | None = None, partial_match: bool = False, query_name: str | None = None, **kwargs: Any) -> int ``` Return the number of nodes of a given kind. #### `traverse_paths`[​](#traverse_paths-1 "Direct link to traverse_paths-1") ``` traverse_paths(self, source: str | InfrahubNodeSync, destination: str | InfrahubNodeSync) -> PathTraversalResult ``` Find the shortest path(s) between two nodes in the graph. Kind filters (`kind_filter`, `excluded_kinds`, `included_kinds`) accept kind-name strings and/or generated protocol classes. `relationship_filter` matches schema relationship identifiers (for example `dcimconnector__dcimendpoint`), not the per-side names shown in the result. Requires Infrahub 1.10 or later. **Args:** * `source`: Node to start from, as a UUID string or an `InfrahubNode` instance. * `destination`: Node to reach, as a UUID string or an `InfrahubNode` instance. * `max_depth`: Maximum number of relationship hops to explore. * `max_paths`: Maximum number of paths to return. * `kind_filter`: Only traverse through nodes of these kinds. * `relationship_filter`: Only traverse through these schema relationship identifiers. * `excluded_namespaces`: Schema namespaces to exclude from traversal. * `excluded_kinds`: Node kinds to exclude from traversal. * `included_kinds`: Node kinds to re-include when otherwise excluded by default. * `shortest_paths_only`: When True (the server default), only return the shortest path(s); when False, return all loopless paths (exhaustive mode). * `branch`: Name of the branch to query from. Defaults to default\_branch. * `at`: Time of the query. Defaults to now. * `timeout`: Overrides the default GraphQL timeout, in seconds. **Raises:** * `VersionNotSupportedError`: If the server does not support graph traversal (pre-1.10). * `GraphQLError`: When the GraphQL response contains errors (e.g. unknown node). #### `path_exists`[​](#path_exists-1 "Direct link to path_exists-1") ``` path_exists(self, source: str | InfrahubNodeSync, destination: str | InfrahubNodeSync) -> bool ``` Return whether at least one path connects `source` to `destination`. Convenience wrapper around :meth:`traverse_paths` for checks: it requests a single path (the cheapest way to answer "is there a path?") and returns `True` if one was found. Accepts the same source/destination and filter arguments as `traverse_paths`. Requires Infrahub 1.10 or later. **Args:** * `source`: Node to start from, as a UUID string or an `InfrahubNode` instance. * `destination`: Node to reach, as a UUID string or an `InfrahubNode` instance. * `max_depth`: Maximum number of relationship hops to explore. * `kind_filter`: Only traverse through nodes of these kinds. * `relationship_filter`: Only traverse through these schema relationship identifiers. * `excluded_namespaces`: Schema namespaces to exclude from traversal. * `excluded_kinds`: Node kinds to exclude from traversal. * `included_kinds`: Node kinds to re-include when otherwise excluded by default. * `branch`: Name of the branch to query from. Defaults to default\_branch. * `at`: Time of the query. Defaults to now. * `timeout`: Overrides the default GraphQL timeout, in seconds. **Raises:** * `VersionNotSupportedError`: If the server does not support graph traversal (pre-1.10). * `GraphQLError`: When the GraphQL response contains errors (e.g. unknown node). #### `reachable_nodes`[​](#reachable_nodes-1 "Direct link to reachable_nodes-1") ``` reachable_nodes(self, source: str | InfrahubNodeSync, target_kinds: list[str | type[SchemaTypeSync]]) -> ReachableNodesResult ``` Find all nodes of the given kinds reachable from a source node. `target_kinds` accepts kind-name strings and/or generated protocol classes. Requires Infrahub 1.10 or later. **Args:** * `source`: Node to start from, as a UUID string or an `InfrahubNode` instance. * `target_kinds`: Kinds of nodes to look for, as kind-name strings or protocol classes. * `max_depth`: Maximum number of relationship hops to explore. * `max_results`: Maximum number of reachable nodes to return. * `max_paths`: Maximum number of paths to compute per reachable node. * `shortest_paths_only`: When True, only return the shortest path(s) to each node. * `branch`: Name of the branch to query from. Defaults to default\_branch. * `at`: Time of the query. Defaults to now. * `timeout`: Overrides the default GraphQL timeout, in seconds. **Raises:** * `VersionNotSupportedError`: If the server does not support graph traversal (pre-1.10). * `GraphQLError`: When the GraphQL response contains errors (e.g. unknown node). #### `all`[​](#all-3 "Direct link to all-3") ``` all(self, kind: type[SchemaTypeSync], at: Timestamp | None = ..., branch: str | None = ..., timeout: int | None = ..., populate_store: bool = ..., offset: int | None = ..., limit: int | None = ..., include: list[str] | None = ..., exclude: list[str] | None = ..., fragment: bool = ..., prefetch_relationships: bool = ..., property: bool = ..., parallel: bool = ..., order: Order | None = ..., include_metadata: bool = ..., query_name: str | None = ...) -> list[SchemaTypeSync] ``` Show 2 other overloads #### `all`[​](#all-4 "Direct link to all-4") ``` all(self, kind: str, at: Timestamp | None = ..., branch: str | None = ..., timeout: int | None = ..., populate_store: bool = ..., offset: int | None = ..., limit: int | None = ..., include: list[str] | None = ..., exclude: list[str] | None = ..., fragment: bool = ..., prefetch_relationships: bool = ..., property: bool = ..., parallel: bool = ..., order: Order | None = ..., include_metadata: bool = ..., query_name: str | None = ...) -> list[InfrahubNodeSync] ``` #### `all`[​](#all-5 "Direct link to all-5") ``` all(self, kind: str | type[SchemaTypeSync], at: Timestamp | None = None, branch: str | None = None, timeout: int | None = None, populate_store: bool = True, offset: int | None = None, limit: int | None = None, include: list[str] | None = None, exclude: list[str] | None = None, fragment: bool = False, prefetch_relationships: bool = False, property: bool = False, parallel: bool = False, order: Order | None = None, include_metadata: bool = False, query_name: str | None = None) -> list[InfrahubNodeSync] | list[SchemaTypeSync] ``` Retrieve all nodes of a given kind. **Args:** * `kind`: kind of the nodes to query * `at`: Time of the query. Defaults to Now. * `branch`: Name of the branch to query from. Defaults to default\_branch. * `timeout`: Overrides default timeout used when querying the GraphQL API. Specified in seconds. * `populate_store`: Flag to indicate whether to populate the store with the retrieved nodes. * `offset`: The offset for pagination. * `limit`: The limit for pagination. * `include`: List of attributes or relationships to include in the query. * `exclude`: List of attributes or relationships to exclude from the query. * `fragment`: Flag to use GraphQL fragments for generic schemas. * `prefetch_relationships`: Flag to indicate whether to pre-fetch related node data. * `parallel`: Whether to use parallel processing for the query. * `order`: Ordering related options. Setting `disable=True` enhances performances. * `include_metadata`: If True, includes node\_metadata and relationship\_metadata in the query. * `query_name`: If provided is used as the GraphQL operation name else All\_\ is used. **Returns:** * list\[InfrahubNodeSync]: List of Nodes #### `filters`[​](#filters-3 "Direct link to filters-3") ``` filters(self, kind: type[SchemaTypeSync], at: Timestamp | None = ..., branch: str | None = ..., timeout: int | None = ..., populate_store: bool = ..., offset: int | None = ..., limit: int | None = ..., include: list[str] | None = ..., exclude: list[str] | None = ..., fragment: bool = ..., prefetch_relationships: bool = ..., partial_match: bool = ..., property: bool = ..., parallel: bool = ..., order: Order | None = ..., include_metadata: bool = ..., query_name: str | None = ..., **kwargs: Any) -> list[SchemaTypeSync] ``` Show 2 other overloads #### `filters`[​](#filters-4 "Direct link to filters-4") ``` filters(self, kind: str, at: Timestamp | None = ..., branch: str | None = ..., timeout: int | None = ..., populate_store: bool = ..., offset: int | None = ..., limit: int | None = ..., include: list[str] | None = ..., exclude: list[str] | None = ..., fragment: bool = ..., prefetch_relationships: bool = ..., partial_match: bool = ..., property: bool = ..., parallel: bool = ..., order: Order | None = ..., include_metadata: bool = ..., query_name: str | None = ..., **kwargs: Any) -> list[InfrahubNodeSync] ``` #### `filters`[​](#filters-5 "Direct link to filters-5") ``` filters(self, kind: str | type[SchemaTypeSync], at: Timestamp | None = None, branch: str | None = None, timeout: int | None = None, populate_store: bool = True, offset: int | None = None, limit: int | None = None, include: list[str] | None = None, exclude: list[str] | None = None, fragment: bool = False, prefetch_relationships: bool = False, partial_match: bool = False, property: bool = False, parallel: bool = False, order: Order | None = None, include_metadata: bool = False, query_name: str | None = None, **kwargs: Any) -> list[InfrahubNodeSync] | list[SchemaTypeSync] ``` Retrieve nodes of a given kind based on provided filters. **Args:** * `kind`: kind of the nodes to query * `at`: Time of the query. Defaults to Now. * `branch`: Name of the branch to query from. Defaults to default\_branch. * `timeout`: Overrides default timeout used when querying the GraphQL API. Specified in seconds. * `populate_store`: Flag to indicate whether to populate the store with the retrieved nodes. * `offset`: The offset for pagination. * `limit`: The limit for pagination. * `include`: List of attributes or relationships to include in the query. * `exclude`: List of attributes or relationships to exclude from the query. * `fragment`: Flag to use GraphQL fragments for generic schemas. * `prefetch_relationships`: Flag to indicate whether to pre-fetch related node data. * `partial_match`: Allow partial match of filter criteria for the query. * `parallel`: Whether to use parallel processing for the query. * `order`: Ordering related options. Setting `disable=True` enhances performances. * `include_metadata`: If True, includes node\_metadata and relationship\_metadata in the query. * `query_name`: If provided is used as the GraphQL operation name else Filters\_\ is used. * `**kwargs`: Additional filter criteria for the query. **Returns:** * list\[InfrahubNodeSync]: List of Nodes that match the given filters. #### `create_batch`[​](#create_batch-1 "Direct link to create_batch-1") ``` create_batch(self, return_exceptions: bool = False) -> InfrahubBatchSync ``` Create a batch to execute multiple queries concurrently. Executing the batch will be performed using a thread pool, meaning it cannot guarantee the execution order. It is not recommended to use such batch to manipulate objects that depend on each others. #### `get_list_repositories`[​](#get_list_repositories-1 "Direct link to get_list_repositories-1") ``` get_list_repositories(self, branches: dict[str, BranchData] | None = None, kind: str = 'CoreGenericRepository') -> dict[str, RepositoryData] ``` #### `query_gql_query`[​](#query_gql_query-1 "Direct link to query_gql_query-1") ``` query_gql_query(self, name: str, variables: dict | None = None, update_group: bool = False, subscribers: list[str] | None = None, params: dict | None = None, branch_name: str | None = None, at: str | None = None, timeout: int | None = None, tracker: str | None = None) -> dict ``` #### `create_diff`[​](#create_diff-1 "Direct link to create_diff-1") ``` create_diff(self, branch: str, name: str, from_time: datetime, to_time: datetime, wait_until_completion: bool = True) -> bool | str ``` #### `get_diff_summary`[​](#get_diff_summary-1 "Direct link to get_diff_summary-1") ``` get_diff_summary(self, branch: str, name: str | None = None, from_time: datetime | None = None, to_time: datetime | None = None, timeout: int | None = None, tracker: str | None = None) -> list[NodeDiff] ``` #### `get_diff_tree`[​](#get_diff_tree-1 "Direct link to get_diff_tree-1") ``` get_diff_tree(self, branch: str, name: str | None = None, from_time: datetime | None = None, to_time: datetime | None = None, timeout: int | None = None, tracker: str | None = None) -> DiffTreeData | None ``` Get complete diff tree with metadata and nodes. Returns None if no diff exists. **Raises:** * `ValueError`: If `from_time` is later than `to_time`. #### `allocate_next_ip_address`[​](#allocate_next_ip_address-3 "Direct link to allocate_next_ip_address-3") ``` allocate_next_ip_address(self, resource_pool: CoreNodeSync, kind: type[SchemaTypeSync], identifier: str | None = ..., prefix_length: int | None = ..., address_type: str | None = ..., data: dict[str, Any] | None = ..., branch: str | None = ..., timeout: int | None = ..., tracker: str | None = ...) -> SchemaTypeSync | None ``` Show 2 other overloads #### `allocate_next_ip_address`[​](#allocate_next_ip_address-4 "Direct link to allocate_next_ip_address-4") ``` allocate_next_ip_address(self, resource_pool: CoreNodeSync, kind: None = ..., identifier: str | None = ..., prefix_length: int | None = ..., address_type: str | None = ..., data: dict[str, Any] | None = ..., branch: str | None = ..., timeout: int | None = ..., tracker: str | None = ...) -> CoreNodeSync | None ``` #### `allocate_next_ip_address`[​](#allocate_next_ip_address-5 "Direct link to allocate_next_ip_address-5") ``` allocate_next_ip_address(self, resource_pool: CoreNodeSync, kind: type[SchemaTypeSync] | None = None, identifier: str | None = None, prefix_length: int | None = None, address_type: str | None = None, data: dict[str, Any] | None = None, branch: str | None = None, timeout: int | None = None, tracker: str | None = None) -> CoreNodeSync | SchemaTypeSync | None ``` Allocate a new IP address by using the provided resource pool. **Args:** * `resource_pool`: Node corresponding to the pool to allocate resources from. * `identifier`: Value to perform idempotent allocation, the same resource will be returned for a given identifier. * `prefix_length`: Length of the prefix to set on the address to allocate. * `address_type`: Kind of the address to allocate. * `data`: A key/value map to use to set attributes values on the allocated address. * `branch`: Name of the branch to allocate from. Defaults to default\_branch. * `timeout`: Flag to indicate whether to populate the store with the retrieved nodes. * `tracker`: The offset for pagination. **Returns:** * Node corresponding to the allocated resource. **Raises:** * `ValueError`: If `resource_pool` is not a `CoreIPAddressPool`. #### `allocate_next_ip_prefix`[​](#allocate_next_ip_prefix-3 "Direct link to allocate_next_ip_prefix-3") ``` allocate_next_ip_prefix(self, resource_pool: CoreNodeSync, kind: type[SchemaTypeSync], identifier: str | None = ..., prefix_length: int | None = ..., member_type: str | None = ..., prefix_type: str | None = ..., data: dict[str, Any] | None = ..., branch: str | None = ..., timeout: int | None = ..., tracker: str | None = ...) -> SchemaTypeSync | None ``` Show 2 other overloads #### `allocate_next_ip_prefix`[​](#allocate_next_ip_prefix-4 "Direct link to allocate_next_ip_prefix-4") ``` allocate_next_ip_prefix(self, resource_pool: CoreNodeSync, kind: None = ..., identifier: str | None = ..., prefix_length: int | None = ..., member_type: str | None = ..., prefix_type: str | None = ..., data: dict[str, Any] | None = ..., branch: str | None = ..., timeout: int | None = ..., tracker: str | None = ...) -> CoreNodeSync | None ``` #### `allocate_next_ip_prefix`[​](#allocate_next_ip_prefix-5 "Direct link to allocate_next_ip_prefix-5") ``` allocate_next_ip_prefix(self, resource_pool: CoreNodeSync, kind: type[SchemaTypeSync] | None = None, identifier: str | None = None, prefix_length: int | None = None, member_type: str | None = None, prefix_type: str | None = None, data: dict[str, Any] | None = None, branch: str | None = None, timeout: int | None = None, tracker: str | None = None) -> CoreNodeSync | SchemaTypeSync | None ``` Allocate a new IP prefix by using the provided resource pool. **Args:** * `resource_pool`: Node corresponding to the pool to allocate resources from. * `identifier`: Value to perform idempotent allocation, the same resource will be returned for a given identifier. * `size`: Length of the prefix to allocate. * `member_type`: Member type of the prefix to allocate. * `prefix_type`: Kind of the prefix to allocate. * `data`: A key/value map to use to set attributes values on the allocated prefix. * `branch`: Name of the branch to allocate from. Defaults to default\_branch. * `timeout`: Flag to indicate whether to populate the store with the retrieved nodes. * `tracker`: The offset for pagination. **Returns:** * Node corresponding to the allocated resource. **Raises:** * `ValueError`: If `resource_pool` is not a `CoreIPPrefixPool`. #### `repository_update_commit`[​](#repository_update_commit-1 "Direct link to repository_update_commit-1") ``` repository_update_commit(self, branch_name: str, repository_id: str, commit: str, is_read_only: bool = False) -> bool ``` #### `refresh_login`[​](#refresh_login-1 "Direct link to refresh_login-1") ``` refresh_login(self) -> None ``` #### `login`[​](#login-1 "Direct link to login-1") ``` login(self, refresh: bool = False) -> None ``` #### `convert_object_type`[​](#convert_object_type-1 "Direct link to convert_object_type-1") ``` convert_object_type(self, node_id: str, target_kind: str, branch: str | None = None, fields_mapping: dict[str, ConversionFieldInput] | None = None) -> InfrahubNodeSync ``` Convert a given node to another kind on a given branch. `fields_mapping` keys are target fields names and its values indicate how to fill in these fields. Any mandatory field not having an equivalent field in the source kind should be specified in this mapping. See for more information. ### `ProcessRelationsNode`[​](#processrelationsnode "Direct link to processrelationsnode") ### `ProxyConfig`[​](#proxyconfig "Direct link to proxyconfig") ### `ProxyConfigSync`[​](#proxyconfigsync "Direct link to proxyconfigsync") ### `ProcessRelationsNodeSync`[​](#processrelationsnodesync "Direct link to processrelationsnodesync") ### `BaseClient`[​](#baseclient "Direct link to baseclient") Base class for InfrahubClient and InfrahubClientSync. **Methods:** #### `request_context`[​](#request_context "Direct link to request_context") ``` request_context(self) -> RequestContext | None ``` #### `request_context`[​](#request_context-1 "Direct link to request_context-1") ``` request_context(self, request_context: RequestContext) -> None ``` #### `start_tracking`[​](#start_tracking "Direct link to start_tracking") ``` start_tracking(self, identifier: str | None = None, params: dict[str, Any] | None = None, delete_unused_nodes: bool = False, group_type: str | None = None, group_params: dict[str, Any] | None = None, branch: str | None = None) -> Self ``` #### `set_context_properties`[​](#set_context_properties "Direct link to set_context_properties") ``` set_context_properties(self, identifier: str, params: dict[str, str] | None = None, delete_unused_nodes: bool = True, reset: bool = True, group_type: str | None = None, group_params: dict[str, Any] | None = None, branch: str | None = None) -> None ``` ## Functions[​](#functions "Direct link to Functions") ### `handle_relogin`[​](#handle_relogin "Direct link to handle_relogin") ``` handle_relogin(func: Callable[..., Coroutine[Any, Any, httpx.Response]]) -> Callable[..., Coroutine[Any, Any, httpx.Response]] ``` ### `handle_relogin_sync`[​](#handle_relogin_sync "Direct link to handle_relogin_sync") ``` handle_relogin_sync(func: Callable[..., httpx.Response]) -> Callable[..., httpx.Response] ``` ### `get_kind_as_string`[​](#get_kind_as_string "Direct link to get_kind_as_string") ``` get_kind_as_string(kind: str | type[SchemaType | SchemaTypeSync]) -> str ``` --- # `infrahub_sdk.graph_traversal.models` Pydantic models for the Infrahub graph-traversal queries (Infrahub 1.10+). These mirror the server GraphQL types for `InfrahubPathTraversal` and `InfrahubReachableNodes`. The server returns snake\_case field names, so the Python attributes map directly without aliasing. Models ignore unknown fields so additive server changes do not break parsing. ## Classes[​](#classes "Direct link to Classes") ### `GraphTraversalModel`[​](#graphtraversalmodel "Direct link to graphtraversalmodel") Base for all traversal models: tolerate unknown/extra server fields. ### `PathNode`[​](#pathnode "Direct link to pathnode") Identity of a node encountered during a traversal. This is a lightweight identity (no attributes or relationships). Use :meth:`fetch` to resolve it into the full SDK node when needed. **Methods:** #### `fetch`[​](#fetch "Direct link to fetch") ``` fetch(self, timeout: int | None = None) -> Any ``` Resolve this node into the full SDK node. On an async client you await the return value (`await node.fetch()`); on a sync client it returns the node directly. The result is added to the client store, so fetching the same id again is served from the store. **Raises:** * `Error`: If this node is not bound to a client (for example, constructed manually). ### `PathRelationship`[​](#pathrelationship "Direct link to pathrelationship") A relationship (edge) traversed between two nodes. ### `PathHop`[​](#pathhop "Direct link to pathhop") A single step in a path: the node visited and the relationship used to reach it. `relationship` is `None` for the source-anchored first hop. ### `Path`[​](#path "Direct link to path") One route between two nodes, as an ordered list of hops. ### `PathTraversalResult`[​](#pathtraversalresult "Direct link to pathtraversalresult") Result of :meth:`InfrahubClient.traverse_paths`. ### `ReachableNode`[​](#reachablenode "Direct link to reachablenode") A node reachable from the source, with the path used to reach it. ### `ReachableNodesResult`[​](#reachablenodesresult "Direct link to reachablenodesresult") Result of :meth:`InfrahubClient.reachable_nodes`. --- # `infrahub_sdk.graph_traversal.query` GraphQL query strings and variable builders for graph traversal (Infrahub 1.10+). Both server queries accept a single complex input object passed as the GraphQL variable `$data`. The input field names are snake\_case on the wire, matching the SDK keyword arguments, so the variable dict is built directly with unset optional fields omitted (the server applies its own defaults). ## Functions[​](#functions "Direct link to Functions") ### `is_unknown_field_error`[​](#is_unknown_field_error "Direct link to is_unknown_field_error") ``` is_unknown_field_error(errors: list[dict[str, Any]], field_name: str) -> bool ``` Return True if the GraphQL errors indicate `field_name` is an unknown query field. Used to detect a pre-1.10 server that lacks the traversal queries, so the SDK can raise a clear version error instead of surfacing an opaque validation failure. The server's own runtime errors (such as "Source node not found") do not match. ### `build_path_traversal_input`[​](#build_path_traversal_input "Direct link to build_path_traversal_input") ``` build_path_traversal_input(source_id: str, destination_id: str) -> dict[str, Any] ``` Build the `PathTraversalInput` variable, omitting unset optional fields. ### `build_reachable_nodes_input`[​](#build_reachable_nodes_input "Direct link to build_reachable_nodes_input") ``` build_reachable_nodes_input(source_id: str, target_kinds: list[str]) -> dict[str, Any] ``` Build the `ReachableNodesInput` variable, omitting unset optional fields. --- # `infrahub_sdk.node.node` ## Classes[​](#classes "Direct link to Classes") ### `InfrahubNode`[​](#infrahubnode "Direct link to infrahubnode") Asynchronous Infrahub node bound to an :class:`InfrahubClient`. Provides full CRUD against the backend (:meth:`save`, :meth:`create`, :meth:`update`, :meth:`delete`) along with relationship traversal (:meth:`get_flat_value`, :meth:`extract`), feature-gated artifact and resource-pool helpers (:meth:`artifact_generate`, :meth:`get_pool_allocated_resources`), and file upload or download for nodes inheriting from `CoreFileObject`. Attributes and relationships defined on the schema are exposed as instance attributes via attribute-style access (`node.name.value`, `node.parent`). **Methods:** #### `from_graphql`[​](#from_graphql "Direct link to from_graphql") ``` from_graphql(cls, client: InfrahubClient, branch: str, data: dict, schema: MainSchemaTypesAPI | None = None, timeout: int | None = None) -> Self ``` Build an :class:`InfrahubNode` from a raw GraphQL response. When no `schema` is provided, the node kind is read from `__typename` in the payload and the schema is fetched from the client. **Args:** * `client`: The client used to interact with the backend. * `branch`: The branch the node belongs to. * `data`: The GraphQL payload describing the node. * `schema`: Pre-fetched schema for the node kind. Skips the schema lookup when provided. * `timeout`: Overrides the default timeout used when fetching the schema. Specified in seconds. **Returns:** * The hydrated node instance. **Raises:** * `ValueError`: If `__typename` is missing from `data` and no `schema` was provided. #### `generate`[​](#generate "Direct link to generate") ``` generate(self, nodes: list[str] | None = None) -> None ``` Trigger artifact generation for this artifact definition. Only available on nodes whose kind is `CoreArtifactDefinition`. **Args:** * `nodes`: The IDs of target nodes to generate artifacts for. When omitted, generation runs for all targets matched by the definition. **Raises:** * `FeatureNotSupportedError`: If this node is not a `CoreArtifactDefinition`. #### `artifact_generate`[​](#artifact_generate "Direct link to artifact_generate") ``` artifact_generate(self, name: str) -> None ``` Regenerate a named artifact targeting this node. Looks up the `CoreArtifact` named `name` for this node, then calls :meth:`generate` on the related definition with this artifact's ID. **Args:** * `name`: The name of the artifact to regenerate. **Raises:** * `FeatureNotSupportedError`: If this node does not inherit from `CoreArtifactTarget`. #### `artifact_fetch`[​](#artifact_fetch "Direct link to artifact_fetch") ``` artifact_fetch(self, name: str) -> str | dict[str, Any] ``` Fetch the stored content of a named artifact for this node. **Args:** * `name`: The name of the artifact to fetch. **Returns:** * str | dict\[str, Any]: The artifact content. Returns a parsed object for * JSON-typed artifacts and a string for text-typed artifacts. **Raises:** * `FeatureNotSupportedError`: If this node does not inherit from `CoreArtifactTarget`. #### `download_file`[​](#download_file "Direct link to download_file") ``` download_file(self, dest: None = None, skip_if_unchanged: bool = ...) -> bytes ``` Show 2 other overloads #### `download_file`[​](#download_file-1 "Direct link to download_file-1") ``` download_file(self, dest: Path, skip_if_unchanged: bool = ...) -> int ``` #### `download_file`[​](#download_file-2 "Direct link to download_file-2") ``` download_file(self, dest: Path | None = None, skip_if_unchanged: bool = False) -> bytes | int ``` Download the file content from this FileObject node. This method is only available for nodes that inherit from CoreFileObject. The node must have been saved (have an id) before calling this method. **Args:** * `dest`: Optional destination path. If provided, the file will be streamed directly to this path (memory-efficient for large files) and the number of bytes written will be returned. If not provided, the file content will be returned as bytes. * `skip_if_unchanged`: When `True`, compute the SHA-1 of the file at `dest` (which must be provided) and compare against the node's `checksum` attribute. If they match, return `0` without hitting the network. The `checksum` is the value loaded when this node was fetched — a later server-side change to the file will not be detected unless the caller re-fetches the node first. **Returns:** * If `dest` is None: The file content as bytes. * If `dest` is provided: The number of bytes written to the file. * If `skip_if_unchanged=True` and the local file matches the server checksum: `0`. **Raises:** * `FeatureNotSupportedError`: If this node doesn't inherit from CoreFileObject. * `ValueError`: If the node hasn't been saved yet, file not found, or `skip_if_unchanged=True` was passed without a `dest`. * `AuthenticationError`: If authentication fails. **Examples:** ``` >>> # Download to memory >>> content = await contract.download_file() >>> # Stream to file (memory-efficient for large files) >>> bytes_written = await contract.download_file(dest=Path("/tmp/contract.pdf")) >>> # Skip download if local file already matches server checksum >>> bytes_written = await contract.download_file( ... dest=Path("/tmp/contract.pdf"), skip_if_unchanged=True ... ) ``` #### `matches_local_checksum`[​](#matches_local_checksum "Direct link to matches_local_checksum") ``` matches_local_checksum(self, source: bytes | Path | BinaryIO) -> bool ``` Return True if `source`'s SHA-1 matches this node's server checksum. Only available for nodes inheriting from `CoreFileObject`. Callers that want to branch on the comparison without invoking a transfer should use this primitive instead of reading `node.checksum.value` and hashing `source` themselves, so the hashing convention stays centralised in the SDK. The comparison is against the `checksum` attribute as loaded when this node was retrieved from the server. If the server's file has been replaced since the node was fetched, this method will not see that change — re-fetch the node to refresh the checksum before comparing. **Args:** * `source`: Local content to hash and compare. Accepts the same shapes as :func:`infrahub_sdk.file_handler.sha1_of_source`. **Returns:** * True if the local digest equals the server's stored checksum. **Raises:** * `FeatureNotSupportedError`: Node is not a `CoreFileObject`. * `ValueError`: Node has no server-side checksum yet (unsaved or file never attached). #### `upload_if_changed`[​](#upload_if_changed "Direct link to upload_if_changed") ``` upload_if_changed(self, source: bytes | Path | BinaryIO, name: str | None = None) -> UploadResult ``` Upload `source` only if its SHA-1 differs from the server checksum. Composes :meth:`matches_local_checksum` with :meth:`upload_from_path` (or :meth:`upload_from_bytes`) and :meth:`save`. For unsaved nodes or nodes that have no prior server-side file, the upload is always performed — there is nothing to compare against. Idempotency is content-only: when the local SHA-1 matches the server checksum the upload is skipped even if `name` differs from the server-side filename. Use a regular :meth:`upload_from_path` / :meth:`save` round-trip if you need to rename without changing content. **Args:** * `source`: Content to upload. `bytes` and `BinaryIO` sources must supply `name`; for a `Path` the filename is derived from `source.name` when `name` is omitted. * `name`: Filename to use on the server. Required for `bytes` / `BinaryIO` sources. **Returns:** * class:`UploadResult` with `was_uploaded=False` (skipped) or * `was_uploaded=True` (transfer occurred), and the resulting server * checksum (`None` only when no server checksum was available * after the operation). **Raises:** * `FeatureNotSupportedError`: Node is not a `CoreFileObject`. * `ValueError`: `source` is `bytes` or `BinaryIO` and no `name` was supplied. #### `delete`[​](#delete "Direct link to delete") ``` delete(self, timeout: int | None = None, request_context: RequestContext | None = None) -> None ``` Delete this node on the backend. **Args:** * `timeout`: Overrides the default timeout used when querying the GraphQL API. Specified in seconds. * `request_context`: Request-level context passed through to the mutation. When omitted, the client's request context is used. #### `save`[​](#save "Direct link to save") ``` save(self, allow_upsert: bool = False, update_group_context: bool | None = None, timeout: int | None = None, request_context: RequestContext | None = None) -> None ``` Persist this node to the backend, creating or updating it as appropriate. New nodes are created (or upserted when `allow_upsert` is set), and existing nodes are updated with only the modified fields. After a successful save, the node is added to the client store and, when applicable, to the active group context for tracking. **Args:** * `allow_upsert`: When `True`, an existing node is upserted instead of failing with a duplicate. Defaults to `False`. * `update_group_context`: Whether to update the group context with this node. When `None` and the client is in tracking mode, defaults to `True`. * `timeout`: Overrides the default timeout used when querying the GraphQL API. Specified in seconds. * `request_context`: Request-level context passed through to the mutation. When omitted, the client's request context is used. #### `generate_query_data`[​](#generate_query_data "Direct link to generate_query_data") ``` generate_query_data(self, filters: dict[str, Any] | None = None, offset: int | None = None, limit: int | None = None, include: list[str] | None = None, exclude: list[str] | None = None, fragment: bool = False, prefetch_relationships: bool = False, partial_match: bool = False, property: bool = False, order: Order | None = None, include_metadata: bool = False) -> dict[str, Any | dict] ``` Generate the full GraphQL query payload for this node kind. The returned dict combines :meth:`generate_query_data_init` with :meth:`generate_query_data_node`. When the node is a generic and `fragment` is `True`, `...on Kind` fragments are added for every implementing kind so the relevant attributes are returned alongside the generic fields. **Args:** * `filters`: Filters to apply to the query. * `offset`: Pagination offset. * `limit`: Pagination limit. * `include`: Attributes or relationships to include. * `exclude`: Attributes or relationships to exclude. * `fragment`: When `True` and the schema is a generic, emit `...on Kind` fragments for each implementing kind. Defaults to `False`. * `prefetch_relationships`: When `True`, pre-fetch related node data instead of returning only their identifiers. Defaults to `False`. * `partial_match`: When `True`, allow partial matches on filter criteria. Defaults to `False`. * `property`: When `True`, include attribute and relationship properties (`source`, `owner`, `is_protected`, ...). Defaults to `False`. * `order`: Ordering options to apply to the query. * `include_metadata`: When `True`, include `node_metadata` and `relationship_metadata` in the result. Defaults to `False`. **Returns:** * dict\[str, Any | dict]: A query payload keyed by the node kind, ready to be * rendered as GraphQL. #### `generate_query_data_node`[​](#generate_query_data_node "Direct link to generate_query_data_node") ``` generate_query_data_node(self, include: list[str] | None = None, exclude: list[str] | None = None, inherited: bool = True, insert_alias: bool = False, prefetch_relationships: bool = False, property: bool = False, include_metadata: bool = False) -> dict[str, Any | dict] ``` Generate the node part of a GraphQL Query with attributes and nodes. **Args:** * `include`: List of attributes or relationships to include. Defaults to None. * `exclude`: List of attributes or relationships to exclude. Defaults to None. * `inherited`: Indicated of the attributes and the relationships inherited from generics should be included as well. Defaults to True. * `insert_alias`: If True, inserts aliases in the query for each attribute or relationship. * `prefetch_relationships`: If True, pre-fetches relationship data as part of the query. * `include_metadata`: If True, includes node\_metadata and relationship\_metadata in the query. **Returns:** * dict\[str, Union\[Any, Dict]]: GraphQL query in dictionary format #### `add_relationships`[​](#add_relationships "Direct link to add_relationships") ``` add_relationships(self, relation_to_update: str, related_nodes: list[str]) -> None ``` Add peers to a cardinality-many relationship through a dedicated mutation. Unlike :meth:`save`, this method targets a single relationship and only adds peers, leaving every other field untouched. **Args:** * `relation_to_update`: The name of the relationship to update. * `related_nodes`: The IDs of the peers to add. #### `remove_relationships`[​](#remove_relationships "Direct link to remove_relationships") ``` remove_relationships(self, relation_to_update: str, related_nodes: list[str]) -> None ``` Remove peers from a cardinality-many relationship through a dedicated mutation. Unlike :meth:`save`, this method targets a single relationship and only removes the listed peers, leaving every other field untouched. **Args:** * `relation_to_update`: The name of the relationship to update. * `related_nodes`: The IDs of the peers to remove. #### `create`[​](#create "Direct link to create") ``` create(self, allow_upsert: bool = False, timeout: int | None = None, request_context: RequestContext | None = None) -> None ``` Create this node on the backend. For nodes inheriting from `CoreFileObject`, the file content set with :meth:`upload_from_path` or :meth:`upload_from_bytes` is uploaded as part of the mutation and cleared from the node afterward. Prefer :meth:`save` over calling `create()` directly so existing-vs-new logic is handled for you. **Args:** * `allow_upsert`: When `True`, the operation upserts instead of erroring on a duplicate. Defaults to `False`. * `timeout`: Overrides the default timeout used when querying the GraphQL API. Specified in seconds. * `request_context`: Request-level context passed through to the mutation. When omitted, the client's request context is used. **Raises:** * `ValueError`: If this is a file-object node and no file content has been set. #### `update`[​](#update "Direct link to update") ``` update(self, do_full_update: bool = False, timeout: int | None = None, request_context: RequestContext | None = None) -> None ``` Update this node on the backend. By default only the modified attributes and relationships are sent so the server can compute a minimal diff. Setting `do_full_update` re-sends every field even when unchanged, which is useful when forcing relationship reconciliation. Prefer :meth:`save` over calling `update()` directly so existing-vs-new logic is handled for you. **Args:** * `do_full_update`: When `True`, send every field even when unmodified. Defaults to `False`. * `timeout`: Overrides the default timeout used when querying the GraphQL API. Specified in seconds. * `request_context`: Request-level context passed through to the mutation. When omitted, the client's request context is used. #### `get_pool_allocated_resources`[​](#get_pool_allocated_resources "Direct link to get_pool_allocated_resources") ``` get_pool_allocated_resources(self, resource: InfrahubNode) -> list[InfrahubNode] ``` Fetch all nodes that were allocated for the pool and a given resource. **Args:** * `resource`: The resource from which the nodes were allocated. **Returns:** * list\[InfrahubNode]: The allocated nodes. **Raises:** * `ValueError`: If the node is not a resource pool. #### `get_pool_resources_utilization`[​](#get_pool_resources_utilization "Direct link to get_pool_resources_utilization") ``` get_pool_resources_utilization(self) -> list[dict[str, Any]] ``` Fetch the utilization of each resource for the pool. **Returns:** * list\[dict\[str, Any]]: A list containing the allocation numbers for each resource of the pool. **Raises:** * `ValueError`: If the node is not a resource pool. #### `get_flat_value`[​](#get_flat_value "Direct link to get_flat_value") ``` get_flat_value(self, key: str, separator: str = '__') -> Any ``` Resolve a value addressed by a flat key over this node and its related nodes. Walks attributes on this node, descending through cardinality-one relationships (which are fetched on demand) until the final component is reached. Each relationship hop incurs a backend call, so this is intended for ad-hoc lookups rather than bulk traversal. **Args:** * `key`: The flat key to resolve (for example `"name__value"` or `"site__name__value"`). * `separator`: Component separator in `key`. Defaults to `"__"`. **Returns:** * The resolved value. **Raises:** * `ValueError`: If a component does not match an attribute or relationship, or if a relationship hop targets a non cardinality-one relationship. **Examples:** name\_\_value module.object.value #### `extract`[​](#extract "Direct link to extract") ``` extract(self, params: dict[str, str]) -> dict[str, Any] ``` Extract several values addressed by flat keys into a labeled dict. Each value in `params` is resolved with :meth:`get_flat_value`, and the corresponding key is preserved as the output label. **Args:** * `params`: A mapping of output label to flat key to resolve. **Returns:** * dict\[str, Any]: The resolved values keyed by their output label. ### `InfrahubNodeSync`[​](#infrahubnodesync "Direct link to infrahubnodesync") Synchronous Infrahub node bound to an :class:`InfrahubClientSync`. Synchronous counterpart of :class:`InfrahubNode`. Provides full CRUD against the backend (:meth:`save`, :meth:`create`, :meth:`update`, :meth:`delete`) along with relationship traversal (:meth:`get_flat_value`, :meth:`extract`), feature-gated artifact and resource-pool helpers (:meth:`artifact_generate`, :meth:`get_pool_allocated_resources`), and file upload or download for nodes inheriting from `CoreFileObject`. Attributes and relationships defined on the schema are exposed as instance attributes via attribute-style access (`node.name.value`, `node.parent`). **Methods:** #### `from_graphql`[​](#from_graphql-1 "Direct link to from_graphql-1") ``` from_graphql(cls, client: InfrahubClientSync, branch: str, data: dict, schema: MainSchemaTypesAPI | None = None, timeout: int | None = None) -> Self ``` Build an :class:`InfrahubNodeSync` from a raw GraphQL response. When no `schema` is provided, the node kind is read from `__typename` in the payload and the schema is fetched from the client. **Args:** * `client`: The client used to interact with the backend. * `branch`: The branch the node belongs to. * `data`: The GraphQL payload describing the node. * `schema`: Pre-fetched schema for the node kind. Skips the schema lookup when provided. * `timeout`: Overrides the default timeout used when fetching the schema. Specified in seconds. **Returns:** * The hydrated node instance. **Raises:** * `ValueError`: If `__typename` is missing from `data` and no `schema` was provided. #### `generate`[​](#generate-1 "Direct link to generate-1") ``` generate(self, nodes: list[str] | None = None) -> None ``` Trigger artifact generation for this artifact definition. Only available on nodes whose kind is `CoreArtifactDefinition`. **Args:** * `nodes`: The IDs of target nodes to generate artifacts for. When omitted, generation runs for all targets matched by the definition. **Raises:** * `FeatureNotSupportedError`: If this node is not a `CoreArtifactDefinition`. #### `artifact_generate`[​](#artifact_generate-1 "Direct link to artifact_generate-1") ``` artifact_generate(self, name: str) -> None ``` Regenerate a named artifact targeting this node. Looks up the `CoreArtifact` named `name` for this node, then calls :meth:`generate` on the related definition with this artifact's ID. **Args:** * `name`: The name of the artifact to regenerate. **Raises:** * `FeatureNotSupportedError`: If this node does not inherit from `CoreArtifactTarget`. #### `artifact_fetch`[​](#artifact_fetch-1 "Direct link to artifact_fetch-1") ``` artifact_fetch(self, name: str) -> str | dict[str, Any] ``` Fetch the stored content of a named artifact for this node. **Args:** * `name`: The name of the artifact to fetch. **Returns:** * str | dict\[str, Any]: The artifact content. Returns a parsed object for * JSON-typed artifacts and a string for text-typed artifacts. **Raises:** * `FeatureNotSupportedError`: If this node does not inherit from `CoreArtifactTarget`. #### `download_file`[​](#download_file-3 "Direct link to download_file-3") ``` download_file(self, dest: None = None, skip_if_unchanged: bool = ...) -> bytes ``` Show 2 other overloads #### `download_file`[​](#download_file-4 "Direct link to download_file-4") ``` download_file(self, dest: Path, skip_if_unchanged: bool = ...) -> int ``` #### `download_file`[​](#download_file-5 "Direct link to download_file-5") ``` download_file(self, dest: Path | None = None, skip_if_unchanged: bool = False) -> bytes | int ``` Download the file content from this FileObject node. This method is only available for nodes that inherit from CoreFileObject. The node must have been saved (have an id) before calling this method. **Args:** * `dest`: Optional destination path. If provided, the file will be streamed directly to this path (memory-efficient for large files) and the number of bytes written will be returned. If not provided, the file content will be returned as bytes. * `skip_if_unchanged`: When `True`, compute the SHA-1 of the file at `dest` (which must be provided) and compare against the node's `checksum` attribute. If they match, return `0` without hitting the network. The `checksum` is the value loaded when this node was fetched — a later server-side change to the file will not be detected unless the caller re-fetches the node first. **Returns:** * If `dest` is None: The file content as bytes. * If `dest` is provided: The number of bytes written to the file. * If `skip_if_unchanged=True` and the local file matches the server checksum: `0`. **Raises:** * `FeatureNotSupportedError`: If this node doesn't inherit from CoreFileObject. * `ValueError`: If the node hasn't been saved yet, file not found, or `skip_if_unchanged=True` was passed without a `dest`. * `AuthenticationError`: If authentication fails. **Examples:** ``` >>> # Download to memory >>> content = contract.download_file() >>> # Stream to file (memory-efficient for large files) >>> bytes_written = contract.download_file(dest=Path("/tmp/contract.pdf")) >>> # Skip download if local file already matches server checksum >>> bytes_written = contract.download_file( ... dest=Path("/tmp/contract.pdf"), skip_if_unchanged=True ... ) ``` #### `matches_local_checksum`[​](#matches_local_checksum-1 "Direct link to matches_local_checksum-1") ``` matches_local_checksum(self, source: bytes | Path | BinaryIO) -> bool ``` Return True if `source`'s SHA-1 matches this node's server checksum. Only available for nodes inheriting from `CoreFileObject`. Callers that want to branch on the comparison without invoking a transfer should use this primitive instead of reading `node.checksum.value` and hashing `source` themselves, so the hashing convention stays centralised in the SDK. The comparison is against the `checksum` attribute as loaded when this node was retrieved from the server. If the server's file has been replaced since the node was fetched, this method will not see that change — re-fetch the node to refresh the checksum before comparing. **Args:** * `source`: Local content to hash and compare. Accepts the same shapes as :func:`infrahub_sdk.file_handler.sha1_of_source`. **Returns:** * True if the local digest equals the server's stored checksum. **Raises:** * `FeatureNotSupportedError`: Node is not a `CoreFileObject`. * `ValueError`: Node has no server-side checksum yet (unsaved or file never attached). #### `upload_if_changed`[​](#upload_if_changed-1 "Direct link to upload_if_changed-1") ``` upload_if_changed(self, source: bytes | Path | BinaryIO, name: str | None = None) -> UploadResult ``` Upload `source` only if its SHA-1 differs from the server checksum. Composes :meth:`matches_local_checksum` with :meth:`upload_from_path` (or :meth:`upload_from_bytes`) and :meth:`save`. For unsaved nodes or nodes that have no prior server-side file, the upload is always performed — there is nothing to compare against. Idempotency is content-only: when the local SHA-1 matches the server checksum the upload is skipped even if `name` differs from the server-side filename. Use a regular :meth:`upload_from_path` / :meth:`save` round-trip if you need to rename without changing content. **Args:** * `source`: Content to upload. `bytes` and `BinaryIO` sources must supply `name`; for a `Path` the filename is derived from `source.name` when `name` is omitted. * `name`: Filename to use on the server. Required for `bytes` / `BinaryIO` sources. **Returns:** * class:`UploadResult` with `was_uploaded=False` (skipped) or * `was_uploaded=True` (transfer occurred), and the resulting server * checksum (`None` only when no server checksum was available * after the operation). **Raises:** * `FeatureNotSupportedError`: Node is not a `CoreFileObject`. * `ValueError`: `source` is `bytes` or `BinaryIO` and no `name` was supplied. #### `delete`[​](#delete-1 "Direct link to delete-1") ``` delete(self, timeout: int | None = None, request_context: RequestContext | None = None) -> None ``` Delete this node on the backend. **Args:** * `timeout`: Overrides the default timeout used when querying the GraphQL API. Specified in seconds. * `request_context`: Request-level context passed through to the mutation. When omitted, the client's request context is used. #### `save`[​](#save-1 "Direct link to save-1") ``` save(self, allow_upsert: bool = False, update_group_context: bool | None = None, timeout: int | None = None, request_context: RequestContext | None = None) -> None ``` Persist this node to the backend, creating or updating it as appropriate. New nodes are created (or upserted when `allow_upsert` is set), and existing nodes are updated with only the modified fields. After a successful save, the node is added to the client store and, when applicable, to the active group context for tracking. **Args:** * `allow_upsert`: When `True`, an existing node is upserted instead of failing with a duplicate. Defaults to `False`. * `update_group_context`: Whether to update the group context with this node. When `None` and the client is in tracking mode, defaults to `True`. * `timeout`: Overrides the default timeout used when querying the GraphQL API. Specified in seconds. * `request_context`: Request-level context passed through to the mutation. When omitted, the client's request context is used. #### `generate_query_data`[​](#generate_query_data-1 "Direct link to generate_query_data-1") ``` generate_query_data(self, filters: dict[str, Any] | None = None, offset: int | None = None, limit: int | None = None, include: list[str] | None = None, exclude: list[str] | None = None, fragment: bool = False, prefetch_relationships: bool = False, partial_match: bool = False, property: bool = False, order: Order | None = None, include_metadata: bool = False) -> dict[str, Any | dict] ``` Generate the full GraphQL query payload for this node kind. The returned dict combines :meth:`generate_query_data_init` with :meth:`generate_query_data_node`. When the node is a generic and `fragment` is `True`, `...on Kind` fragments are added for every implementing kind so the relevant attributes are returned alongside the generic fields. **Args:** * `filters`: Filters to apply to the query. * `offset`: Pagination offset. * `limit`: Pagination limit. * `include`: Attributes or relationships to include. * `exclude`: Attributes or relationships to exclude. * `fragment`: When `True` and the schema is a generic, emit `...on Kind` fragments for each implementing kind. Defaults to `False`. * `prefetch_relationships`: When `True`, pre-fetch related node data instead of returning only their identifiers. Defaults to `False`. * `partial_match`: When `True`, allow partial matches on filter criteria. Defaults to `False`. * `property`: When `True`, include attribute and relationship properties (`source`, `owner`, `is_protected`, ...). Defaults to `False`. * `order`: Ordering options to apply to the query. * `include_metadata`: When `True`, include `node_metadata` and `relationship_metadata` in the result. Defaults to `False`. **Returns:** * dict\[str, Any | dict]: A query payload keyed by the node kind, ready to be * rendered as GraphQL. #### `generate_query_data_node`[​](#generate_query_data_node-1 "Direct link to generate_query_data_node-1") ``` generate_query_data_node(self, include: list[str] | None = None, exclude: list[str] | None = None, inherited: bool = True, insert_alias: bool = False, prefetch_relationships: bool = False, property: bool = False, include_metadata: bool = False) -> dict[str, Any | dict] ``` Generate the node part of a GraphQL Query with attributes and nodes. **Args:** * `include`: List of attributes or relationships to include. Defaults to None. * `exclude`: List of attributes or relationships to exclude. Defaults to None. * `inherited`: Indicated of the attributes and the relationships inherited from generics should be included as well. Defaults to True. * `insert_alias`: If True, inserts aliases in the query for each attribute or relationship. * `prefetch_relationships`: If True, pre-fetches relationship data as part of the query. * `include_metadata`: If True, includes node\_metadata and relationship\_metadata in the query. **Returns:** * dict\[str, Union\[Any, Dict]]: GraphQL query in dictionary format #### `add_relationships`[​](#add_relationships-1 "Direct link to add_relationships-1") ``` add_relationships(self, relation_to_update: str, related_nodes: list[str]) -> None ``` Add peers to a cardinality-many relationship through a dedicated mutation. Unlike :meth:`save`, this method targets a single relationship and only adds peers, leaving every other field untouched. **Args:** * `relation_to_update`: The name of the relationship to update. * `related_nodes`: The IDs of the peers to add. #### `remove_relationships`[​](#remove_relationships-1 "Direct link to remove_relationships-1") ``` remove_relationships(self, relation_to_update: str, related_nodes: list[str]) -> None ``` Remove peers from a cardinality-many relationship through a dedicated mutation. Unlike :meth:`save`, this method targets a single relationship and only removes the listed peers, leaving every other field untouched. **Args:** * `relation_to_update`: The name of the relationship to update. * `related_nodes`: The IDs of the peers to remove. #### `create`[​](#create-1 "Direct link to create-1") ``` create(self, allow_upsert: bool = False, timeout: int | None = None, request_context: RequestContext | None = None) -> None ``` Create this node on the backend. For nodes inheriting from `CoreFileObject`, the file content set with :meth:`upload_from_path` or :meth:`upload_from_bytes` is uploaded as part of the mutation and cleared from the node afterward. Prefer :meth:`save` over calling `create()` directly so existing-vs-new logic is handled for you. **Args:** * `allow_upsert`: When `True`, the operation upserts instead of erroring on a duplicate. Defaults to `False`. * `timeout`: Overrides the default timeout used when querying the GraphQL API. Specified in seconds. * `request_context`: Request-level context passed through to the mutation. When omitted, the client's request context is used. **Raises:** * `ValueError`: If this is a file-object node and no file content has been set. #### `update`[​](#update-1 "Direct link to update-1") ``` update(self, do_full_update: bool = False, timeout: int | None = None, request_context: RequestContext | None = None) -> None ``` Update this node on the backend. By default only the modified attributes and relationships are sent so the server can compute a minimal diff. Setting `do_full_update` re-sends every field even when unchanged, which is useful when forcing relationship reconciliation. Prefer :meth:`save` over calling `update()` directly so existing-vs-new logic is handled for you. **Args:** * `do_full_update`: When `True`, send every field even when unmodified. Defaults to `False`. * `timeout`: Overrides the default timeout used when querying the GraphQL API. Specified in seconds. * `request_context`: Request-level context passed through to the mutation. When omitted, the client's request context is used. #### `get_pool_allocated_resources`[​](#get_pool_allocated_resources-1 "Direct link to get_pool_allocated_resources-1") ``` get_pool_allocated_resources(self, resource: InfrahubNodeSync) -> list[InfrahubNodeSync] ``` Fetch all nodes that were allocated for the pool and a given resource. **Args:** * `resource`: The resource from which the nodes were allocated. **Returns:** * list\[InfrahubNodeSync]: The allocated nodes. **Raises:** * `ValueError`: If the node is not a resource pool. #### `get_pool_resources_utilization`[​](#get_pool_resources_utilization-1 "Direct link to get_pool_resources_utilization-1") ``` get_pool_resources_utilization(self) -> list[dict[str, Any]] ``` Fetch the utilization of each resource for the pool. **Returns:** * list\[dict\[str, Any]]: A list containing the allocation numbers for each resource of the pool. **Raises:** * `ValueError`: If the node is not a resource pool. #### `get_flat_value`[​](#get_flat_value-1 "Direct link to get_flat_value-1") ``` get_flat_value(self, key: str, separator: str = '__') -> Any ``` Resolve a value addressed by a flat key over this node and its related nodes. Walks attributes on this node, descending through cardinality-one relationships (which are fetched on demand) until the final component is reached. Each relationship hop incurs a backend call, so this is intended for ad-hoc lookups rather than bulk traversal. **Args:** * `key`: The flat key to resolve (for example `"name__value"` or `"site__name__value"`). * `separator`: Component separator in `key`. Defaults to `"__"`. **Returns:** * The resolved value. **Raises:** * `ValueError`: If a component does not match an attribute or relationship, or if a relationship hop targets a non cardinality-one relationship. **Examples:** name\_\_value module.object.value #### `extract`[​](#extract-1 "Direct link to extract-1") ``` extract(self, params: dict[str, str]) -> dict[str, Any] ``` Extract several values addressed by flat keys into a labeled dict. Each value in `params` is resolved with :meth:`get_flat_value`, and the corresponding key is preserved as the output label. **Args:** * `params`: A mapping of output label to flat key to resolve. **Returns:** * dict\[str, Any]: The resolved values keyed by their output label. ### `UploadResult`[​](#uploadresult "Direct link to uploadresult") Outcome of an idempotent upload attempt. Returned by :meth:`InfrahubNode.upload_if_changed` and its sync twin. `was_uploaded` tells the caller whether a network transfer actually happened; `checksum` carries the SHA-1 of the content held on the server after the operation — on skip paths that is the server's pre-existing value, on upload paths it is the locally-computed SHA-1 used as a proxy (which matches what a standard CoreFileObject server stores, since the server computes SHA-1 of received bytes). `None` only when no server checksum was available (either the node was unsaved and nothing was transferred, or the save returned no checksum value). The comparison used by `upload_if_changed` reads the node's `checksum` attribute, which was populated when the node was fetched via `client.get(...)`. A server-side change to the file between the fetch and the call will not be detected unless the caller re-fetches the node first. ### `InfrahubNodeBase`[​](#infrahubnodebase "Direct link to infrahubnodebase") Base class for :class:`InfrahubNode` and :class:`InfrahubNodeSync`. Owns the schema-driven state shared between the async and sync clients: attributes, relationships, identity (`id`, `hfid`), node metadata, and the helpers that turn the in-memory state into GraphQL query and mutation payloads. This class is not meant to be instantiated directly; use :class:`InfrahubNode` or :class:`InfrahubNodeSync` instead. **Attributes:** * `id`: The unique identifier of the node, when known. * `display_label`: Human-readable label of the node. * `typename`: The GraphQL `__typename` of the node. **Methods:** #### `get_branch`[​](#get_branch "Direct link to get_branch") ``` get_branch(self) -> str ``` Return the branch this node is bound to. **Returns:** * The name of the branch. #### `get_path_value`[​](#get_path_value "Direct link to get_path_value") ``` get_path_value(self, path: str) -> Any ``` Resolve a value addressed by a dunder-separated path on this node. The path can target an attribute (`name__value`, `name__source`), a cardinality-one related node (`parent`), an attribute of that related node (`parent__name__value`), or a property of one of its attributes (`parent__name__source`). **Args:** * `path`: A path with components separated by `__`. **Returns:** * The resolved value, or `None` when any path component cannot be * resolved (for example, an unfetched related node not present in the store). #### `get_human_friendly_id`[​](#get_human_friendly_id "Direct link to get_human_friendly_id") ``` get_human_friendly_id(self) -> list[str] | None ``` Compute the human-friendly ID for this node from its schema. The HFID is composed of the values addressed by the schema's `human_friendly_id` paths. When any component cannot be resolved, the HFID is considered invalid and `None` is returned. **Returns:** * list\[str] | None: The HFID as a list of stringified components, or `None` * when the schema does not define an HFID or a component is missing. #### `get_human_friendly_id_as_string`[​](#get_human_friendly_id_as_string "Direct link to get_human_friendly_id_as_string") ``` get_human_friendly_id_as_string(self, include_kind: bool = False) -> str | None ``` Return the human-friendly ID joined into a single string. **Args:** * `include_kind`: When `True`, the node kind is prepended as the first component of the resulting string. Defaults to `False`. **Returns:** * str | None: The HFID joined with the HFID separator, or `None` when no * HFID is available. #### `hfid`[​](#hfid "Direct link to hfid") ``` hfid(self) -> list[str] | None ``` Return the human-friendly ID of this node as a list of components. **Returns:** * list\[str] | None: The HFID components, or `None` when unavailable. #### `hfid_str`[​](#hfid_str "Direct link to hfid_str") ``` hfid_str(self) -> str | None ``` Return the human-friendly ID of this node as a string, including the kind prefix. **Returns:** * str | None: The HFID as `Kind__part1__part2`, or `None` when unavailable. #### `get_node_metadata`[​](#get_node_metadata "Direct link to get_node_metadata") ``` get_node_metadata(self) -> NodeMetadata | None ``` Return the node metadata (`created_at`, `created_by`, `updated_at`, `updated_by`). The metadata is populated only when the parent query was executed with `include_metadata=True`. **Returns:** * NodeMetadata | None: The node metadata if fetched, otherwise `None`. #### `get_kind`[​](#get_kind "Direct link to get_kind") ``` get_kind(self) -> str ``` Return the schema kind of this node. **Returns:** * The schema kind (for example `"CoreAccount"`). #### `get_all_kinds`[​](#get_all_kinds "Direct link to get_all_kinds") ``` get_all_kinds(self) -> list[str] ``` Return this node's kind plus all generic kinds it inherits from. **Returns:** * list\[str]: The node's own kind followed by the inherited kinds, in the order * declared on the schema. #### `is_ip_prefix`[​](#is_ip_prefix "Direct link to is_ip_prefix") ``` is_ip_prefix(self) -> bool ``` Return whether this node represents an IP prefix. **Returns:** * `True` when the node kind is `BuiltinIPPrefix` or inherits from it. #### `is_ip_address`[​](#is_ip_address "Direct link to is_ip_address") ``` is_ip_address(self) -> bool ``` Return whether this node represents an IP address. **Returns:** * `True` when the node kind is `BuiltinIPAddress` or inherits from it. #### `is_resource_pool`[​](#is_resource_pool "Direct link to is_resource_pool") ``` is_resource_pool(self) -> bool ``` Return whether this node is a resource pool. **Returns:** * `True` when the node inherits from `CoreResourcePool`. #### `is_file_object`[​](#is_file_object "Direct link to is_file_object") ``` is_file_object(self) -> bool ``` Return whether this node inherits from `CoreFileObject` and supports file uploads. **Returns:** * `True` when file upload/download operations are supported on this node. #### `upload_from_path`[​](#upload_from_path "Direct link to upload_from_path") ``` upload_from_path(self, path: Path) -> None ``` Set a file from disk to be uploaded when saving this FileObject node. The file will be streamed during upload, avoiding loading the entire file into memory. **Args:** * `path`: Path to the file on disk. **Raises:** * `FeatureNotSupportedError`: If this node doesn't inherit from CoreFileObject. #### `upload_from_bytes`[​](#upload_from_bytes "Direct link to upload_from_bytes") ``` upload_from_bytes(self, content: bytes | BinaryIO, name: str) -> None ``` Set content to be uploaded when saving this FileObject node. The content can be provided as bytes or a file-like object. Using BinaryIO is recommended for large content to stream during upload. **Args:** * `content`: The file content as bytes or a file-like object. * `name`: The filename to use for the uploaded file. **Raises:** * `FeatureNotSupportedError`: If this node doesn't inherit from CoreFileObject. **Examples:** ``` >>> # Using bytes (for small files) >>> node.upload_from_bytes(content=b"file content", name="example.txt") >>> # Using file-like object (for large files) >>> with open("/path/to/file.bin", "rb") as f: ... node.upload_from_bytes(content=f, name="file.bin") ``` #### `clear_file`[​](#clear_file "Direct link to clear_file") ``` clear_file(self) -> None ``` Clear any pending file content. #### `get_raw_graphql_data`[​](#get_raw_graphql_data "Direct link to get_raw_graphql_data") ``` get_raw_graphql_data(self) -> dict | None ``` Return the raw GraphQL payload used to build this node. **Returns:** * dict | None: The original GraphQL data, or `None` when the node was * constructed without payload (for example, a brand-new node). #### `generate_query_data_init`[​](#generate_query_data_init "Direct link to generate_query_data_init") ``` generate_query_data_init(self, filters: dict[str, Any] | None = None, offset: int | None = None, limit: int | None = None, include: list[str] | None = None, exclude: list[str] | None = None, partial_match: bool = False, order: Order | None = None, include_metadata: bool = False) -> dict[str, Any | dict] ``` Build the top-level `count`/`edges` skeleton of a GraphQL query for this kind. The returned dict is the outer structure consumed by :meth:`generate_query_data`; it carries the `@filters` block and the empty `edges.node` placeholder that will later be filled by the caller. **Args:** * `filters`: Filters to apply to the query. * `offset`: Pagination offset. * `limit`: Pagination limit. * `include`: Attributes or relationships to include. * `exclude`: Attributes or relationships to exclude. * `partial_match`: When `True`, allow partial matches on filter criteria. Defaults to `False`. * `order`: Ordering options to apply to the query. * `include_metadata`: When `True`, include `node_metadata` in the result. Defaults to `False`. **Returns:** * dict\[str, Any | dict]: The query skeleton ready to be combined with node-level * attributes and relationships. **Raises:** * `ValueError`: If the same name appears in both `include` and `exclude`. --- # `infrahub_sdk.node.attribute` ## Classes[​](#classes "Direct link to Classes") ### `Attribute`[​](#attribute "Direct link to attribute") Represents an attribute of a Node, including its schema, value, and properties. An `Attribute` wraps a single attribute on an :class:`InfrahubNode`. It tracks the current value, the metadata properties (`source`, `owner`, `is_protected`, ...), and whether the value has been mutated since the node was loaded. Mutation tracking is used by `InfrahubNode.update()` to send only the changed fields to the API. **Attributes:** * `name`: The name of the attribute. * `id`: The unique identifier of the attribute, when known. * `value`: The current attribute value. Setting this marks the attribute as mutated. * `value_has_been_mutated`: True when `value` has been assigned after construction. * `is_default`: True when the value comes from the schema default. * `is_from_profile`: True when the value is inherited from a profile. * `is_inherited`: True when the attribute is inherited from a generic. * `is_protected`: True when the attribute is protected from modification. * `updated_at`: ISO-8601 timestamp of the most recent update. * `source`: The node that supplied this attribute value. * `owner`: The node that owns this attribute. * `updated_by`: The account that performed the most recent update. **Methods:** #### `value`[​](#value "Direct link to value") ``` value(self) -> Any ``` #### `value`[​](#value-1 "Direct link to value-1") ``` value(self, value: Any) -> None ``` #### `is_from_pool_attribute`[​](#is_from_pool_attribute "Direct link to is_from_pool_attribute") ``` is_from_pool_attribute(self) -> bool ``` Check whether this attribute's value is sourced from a resource pool. **Returns:** * True if the attribute value is a resource pool node or was explicitly allocated from a pool. #### `is_unresolved_pool_attribute`[​](#is_unresolved_pool_attribute "Direct link to is_unresolved_pool_attribute") ``` is_unresolved_pool_attribute(self) -> bool ``` Return True when pool-backed but no concrete scalar value is available yet. A pool-backed attribute is unresolved when: * its value is a pool node object (the pool reference itself, not an allocated scalar), or * its value is None and the from\_pool allocation dict is set. An attribute whose \_from\_pool dict is set but whose value has already been populated with the allocated scalar (e.g. after a prior save) is considered resolved. --- # `infrahub_sdk.node.constants` *This module is empty or contains only private/internal implementations.* --- # `infrahub_sdk.node.metadata` ## Classes[​](#classes "Direct link to Classes") ### `NodeMetadata`[​](#nodemetadata "Direct link to nodemetadata") Represents metadata about a node (created\_at, created\_by, updated\_at, updated\_by). Populated from the `node_metadata` GraphQL block when `include_metadata=True` is passed to a query. The `*_by` fields point to the user who created or last updated the node, exposed as :class:`NodeProperty` references. **Attributes:** * `created_at`: ISO-8601 timestamp of node creation. * `created_by`: The account that created the node. * `updated_at`: ISO-8601 timestamp of the most recent update. * `updated_by`: The account that performed the most recent update. ### `RelationshipMetadata`[​](#relationshipmetadata "Direct link to relationshipmetadata") Represents metadata about a relationship edge (updated\_at, updated\_by). Populated from the `relationship_metadata` GraphQL block when `include_metadata=True` is passed to a query. Unlike :class:`NodeMetadata`, this only carries update info because the creation timestamp of an edge is not tracked separately from its peer node. **Attributes:** * `updated_at`: ISO-8601 timestamp of the most recent edge update. * `updated_by`: The account that performed the most recent edge update. --- # `infrahub_sdk.node.parsers` ## Functions[​](#functions "Direct link to Functions") ### `parse_human_friendly_id`[​](#parse_human_friendly_id "Direct link to parse_human_friendly_id") ``` parse_human_friendly_id(hfid: str | list[str]) -> tuple[str | None, list[str]] ``` Parse a human-friendly ID into a kind and an identifier. Accepts the HFID either as a separator-joined string (`"Kind__part1__part2"`) or as a list of components. When a string is provided, the first component is treated as the node kind only when more than one component is present. **Args:** * `hfid`: The HFID to parse, either as a separator-joined string or as a list of components. **Returns:** * tuple\[str | None, list\[str]]: A tuple of `(kind, identifier_components)`. `kind` is * `None` when no kind prefix is present (single-component string or list input). **Raises:** * `ValueError`: If `hfid` is neither a string nor a list. --- # `infrahub_sdk.node.property` ## Classes[​](#classes "Direct link to Classes") ### `NodeProperty`[​](#nodeproperty "Direct link to nodeproperty") Represents a property of a node, typically used for metadata like display labels. A `NodeProperty` is a lightweight pointer to another node, used to expose attribute and relationship metadata such as `source`, `owner`, `created_by`, or `updated_by` without loading the full peer node. **Attributes:** * `id`: The identifier of the referenced node. * `display_label`: A human-readable label for the referenced node. * `typename`: The GraphQL `__typename` of the referenced node. --- # `infrahub_sdk.node.related_node` ## Classes[​](#classes "Direct link to Classes") ### `RelatedNodeBase`[​](#relatednodebase "Direct link to relatednodebase") Base class for representing a related node in a relationship. A `RelatedNodeBase` is the peer end of a cardinality-one relationship. It carries the lightweight identification of the peer (`id`, `hfid`, `typename`, ...) along with the relationship-edge properties (`source`, `owner`, `is_protected`, ...). The full peer node is fetched lazily through :meth:`RelatedNode.fetch` / :meth:`RelatedNodeSync.fetch`. **Attributes:** * `schema`: The schema describing the relationship. * `name`: The name of the relationship slot on the parent node. * `updated_at`: ISO-8601 timestamp of the most recent edge update. **Methods:** #### `id`[​](#id "Direct link to id") ``` id(self) -> str | None ``` Return the parsed peer id without triggering a store lookup. Returns None when the response carried only hfid\_str (no id, no peer) — in that case .peer.id would resolve through the store and yield a non-None id, so .id and .peer.id are NOT interchangeable. **Returns:** * str | None: The peer node ID, or `None` when neither the peer nor an ID is set. #### `hfid`[​](#hfid "Direct link to hfid") ``` hfid(self) -> list[Any] | None ``` Return the human-friendly ID of the related node. **Returns:** * list\[Any] | None: The peer HFID as a list of components, or `None` when not set. #### `hfid_str`[​](#hfid_str "Direct link to hfid_str") ``` hfid_str(self) -> str | None ``` Return the human-friendly ID of the related node as a separator-joined string. The returned string includes the kind prefix and is therefore suitable as a key for the client store. **Returns:** * str | None: The peer HFID joined with the HFID separator, or `None` when * unavailable (no resolved peer or missing HFID). #### `is_resource_pool`[​](#is_resource_pool "Direct link to is_resource_pool") ``` is_resource_pool(self) -> bool ``` Return whether the related node is a resource pool. **Returns:** * `True` when the resolved peer inherits from `CoreResourcePool`. #### `initialized`[​](#initialized "Direct link to initialized") ``` initialized(self) -> bool ``` Return whether this related node has an identifier. **Returns:** * `True` when an ID or HFID is known and the relationship can be referenced. #### `display_label`[​](#display_label "Direct link to display_label") ``` display_label(self) -> str | None ``` Return the human-readable label of the related node. **Returns:** * str | None: The peer display label, or `None` when not provided. #### `typename`[​](#typename "Direct link to typename") ``` typename(self) -> str | None ``` Return the GraphQL `__typename` of the related node. **Returns:** * str | None: The peer typename, or `None` when not provided. #### `kind`[​](#kind "Direct link to kind") ``` kind(self) -> str | None ``` Return the schema kind of the related node. **Returns:** * str | None: The peer schema kind, or `None` when not provided. #### `is_from_profile`[​](#is_from_profile "Direct link to is_from_profile") ``` is_from_profile(self) -> bool ``` Return whether this relationship was set from a profile. A relationship is considered profile-sourced when the typename of its `source` property starts with the profile kind prefix. **Returns:** * `True` when the relationship's source is a profile node. #### `get_relationship_metadata`[​](#get_relationship_metadata "Direct link to get_relationship_metadata") ``` get_relationship_metadata(self) -> RelationshipMetadata | None ``` Return the relationship-edge metadata (`updated_at`, `updated_by`). The metadata is populated only when the parent query was executed with `include_metadata=True`. **Returns:** * RelationshipMetadata | None: The edge metadata if fetched, otherwise `None`. ### `RelatedNode`[​](#relatednode "Direct link to relatednode") Asynchronous related node bound to an :class:`InfrahubClient`. Extends :class:`RelatedNodeBase` with the ability to lazily resolve the peer node: :meth:`fetch` retrieves the full peer from the backend, :meth:`get` returns it from the local cache or the client store, and :attr:`peer` is a convenience accessor around :meth:`get`. **Methods:** #### `fetch`[​](#fetch "Direct link to fetch") ``` fetch(self, timeout: int | None = None) -> None ``` Fetch the full peer node from the backend and cache it on this object. After `fetch()` completes, attribute and relationship access on the peer is available via :attr:`peer` or :meth:`get`. **Args:** * `timeout`: Overrides the default timeout used when querying the GraphQL API. Specified in seconds. **Raises:** * `Error`: If neither `id` nor `typename` is set on this related node. #### `peer`[​](#peer "Direct link to peer") ``` peer(self) -> PeerT ``` Return the resolved peer node. This is a convenience accessor for :meth:`get`; the peer must already have been fetched or stored in the client store. **Returns:** * The resolved peer node. #### `get`[​](#get "Direct link to get") ``` get(self) -> PeerT ``` Return the resolved peer node from cache or the client store. Lookup order: 1. The peer cached locally after a successful :meth:`fetch`. 2. The client store keyed by `id` and `typename`. 3. The client store keyed by `hfid_str`. When resolving via `hfid_str` the returned node has a non-None id even when this `RelatedNode`'s `.id` is None — that is the case in which `.peer.id` and `.id` diverge. **Returns:** * The resolved peer node. **Raises:** * `ValueError`: If neither an ID nor an HFID is available to look up the peer. ### `RelatedNodeSync`[​](#relatednodesync "Direct link to relatednodesync") Synchronous related node bound to an :class:`InfrahubClientSync`. Synchronous counterpart of :class:`RelatedNode`. Extends :class:`RelatedNodeBase` with the ability to lazily resolve the peer node: :meth:`fetch` retrieves the full peer from the backend, :meth:`get` returns it from the local cache or the client store, and :attr:`peer` is a convenience accessor around :meth:`get`. **Methods:** #### `fetch`[​](#fetch-1 "Direct link to fetch-1") ``` fetch(self, timeout: int | None = None) -> None ``` Fetch the full peer node from the backend and cache it on this object. After `fetch()` completes, attribute and relationship access on the peer is available via :attr:`peer` or :meth:`get`. **Args:** * `timeout`: Overrides the default timeout used when querying the GraphQL API. Specified in seconds. **Raises:** * `Error`: If neither `id` nor `typename` is set on this related node. #### `peer`[​](#peer-1 "Direct link to peer-1") ``` peer(self) -> PeerTSync ``` Return the resolved peer node. This is a convenience accessor for :meth:`get`; the peer must already have been fetched or stored in the client store. **Returns:** * The resolved peer node. #### `get`[​](#get-1 "Direct link to get-1") ``` get(self) -> PeerTSync ``` Return the resolved peer node from cache or the client store. Lookup order: 1. The peer cached locally after a successful :meth:`fetch`. 2. The client store keyed by `id` and `typename`. 3. The client store keyed by `hfid_str`. When resolving via `hfid_str` the returned node has a non-None id even when this `RelatedNode`'s `.id` is None — that is the case in which `.peer.id` and `.id` diverge. **Returns:** * The resolved peer node. **Raises:** * `ValueError`: If neither an ID nor an HFID is available to look up the peer. ### `RelationshipAttribute`[​](#relationshipattribute "Direct link to relationshipattribute") Typing descriptor for a cardinality-one relationship on a generated protocol. It reads back as `RelatedNode[PeerT]` (so `.peer` keeps the peer type) but accepts assignment of an id string, an HFID, a peer node, or `None` — mirroring the runtime `InfrahubNode.__setattr__` behaviour, which wraps the assigned value in a `RelatedNode`. This type only appears in generated protocols (it is never instantiated at runtime), so it exists purely to give `node.rel` separate read and assignment types under a type checker. ### `RelationshipAttributeSync`[​](#relationshipattributesync "Direct link to relationshipattributesync") Synchronous counterpart of :class:`RelationshipAttribute`. --- # `infrahub_sdk.node.relationship` ## Classes[​](#classes "Direct link to Classes") ### `RelationshipManagerBase`[​](#relationshipmanagerbase "Direct link to relationshipmanagerbase") Base class for :class:`RelationshipManager` and :class:`RelationshipManagerSync`. A `RelationshipManagerBase` exposes a cardinality-many relationship as a list of peers along with helpers to add, remove, or extend the set. Relationship managers are initialized lazily: until :meth:`fetch` (on the async/sync subclasses) is called, the members are not loaded and editing is not allowed. **Attributes:** * `name`: The name of the relationship slot on the parent node. * `schema`: The schema describing the relationship. * `branch`: The branch the relationship is bound to. * `peers`: The current peer set. * `initialized`: `True` once the manager has been populated with data. **Methods:** #### `peer_ids`[​](#peer_ids "Direct link to peer_ids") ``` peer_ids(self) -> list[str] ``` Return the IDs of all peers that have one. **Returns:** * list\[str]: The IDs of the peers, in insertion order. #### `peer_hfids`[​](#peer_hfids "Direct link to peer_hfids") ``` peer_hfids(self) -> list[list[Any]] ``` Return the HFIDs of all peers that have one. **Returns:** * list\[list\[Any]]: The HFIDs of the peers as lists of components, in insertion order. #### `peer_hfids_str`[​](#peer_hfids_str "Direct link to peer_hfids_str") ``` peer_hfids_str(self) -> list[str] ``` Return the HFIDs of all peers as separator-joined strings. **Returns:** * list\[str]: The HFIDs of the peers as `Kind__part1__part2` strings. #### `has_update`[​](#has_update "Direct link to has_update") ``` has_update(self) -> bool ``` Return whether the peer set has been modified since initialization. **Returns:** * `True` after a successful :meth:`add`, :meth:`extend`, or :meth:`remove`. #### `is_from_profile`[​](#is_from_profile "Direct link to is_from_profile") ``` is_from_profile(self) -> bool ``` Return whether this relationship was set from a profile. The relationship is considered profile-sourced only when every peer is itself sourced from a profile. **Returns:** * `True` when at least one peer exists and all peers are from a profile. ### `RelationshipManager`[​](#relationshipmanager "Direct link to relationshipmanager") Asynchronous manager for a cardinality-many relationship. Extends :class:`RelationshipManagerBase` with the ability to populate and edit the peer set against an :class:`InfrahubClient`: :meth:`fetch` resolves every peer in a parallel batch and :meth:`add`, :meth:`extend`, and :meth:`remove` mutate the peer list in memory. Peers are exposed as :class:`RelatedNode` instances and can be accessed by index via `manager[i]`. **Methods:** #### `fetch`[​](#fetch "Direct link to fetch") ``` fetch(self) -> None ``` Populate the peer set and resolve every peer to a full node. When the manager is not yet initialized, the parent node is re-queried with this relationship included so the peer list can be populated. The peers are then fetched in a parallel batch grouped by kind and stored in the client store. **Raises:** * `Error`: If any peer is missing an `id` or `typename` and cannot be resolved. #### `add`[​](#add "Direct link to add") ``` add(self, data: str | RelatedNode | dict) -> None ``` Add a new peer to this relationship. The new peer is only added when its ID or HFID is not already present; duplicate adds are silently ignored. **Args:** * `data`: The peer to add. Accepts an ID string, an existing :class:`RelatedNode`, or a dict describing the peer (with `id` or `hfid` keys, plus optional relationship properties). **Raises:** * `UninitializedError`: If :meth:`fetch` has not been called on this manager yet. #### `extend`[​](#extend "Direct link to extend") ``` extend(self, data: Iterable[str | RelatedNode | dict]) -> None ``` Add new peers to this relationship. This is a convenience wrapper that calls :meth:`add` for every item in `data`. Items already present (by ID or HFID) are silently ignored. **Args:** * `data`: The peers to add, in any of the formats accepted by :meth:`add`. **Raises:** * `UninitializedError`: If :meth:`fetch` has not been called on this manager yet. #### `remove`[​](#remove "Direct link to remove") ``` remove(self, data: str | RelatedNode | dict) -> None ``` Remove a peer from this relationship. The peer to remove is matched first by ID, then by HFID. When no match is found, the call is a no-op. **Args:** * `data`: The peer to remove. Accepts an ID string, an existing :class:`RelatedNode`, or a dict describing the peer. **Raises:** * `UninitializedError`: If :meth:`fetch` has not been called on this manager yet. * `IndexError`: If the internal peer index is inconsistent with the lookup result. ### `RelationshipManagerSync`[​](#relationshipmanagersync "Direct link to relationshipmanagersync") Synchronous manager for a cardinality-many relationship. Synchronous counterpart of :class:`RelationshipManager`. Extends :class:`RelationshipManagerBase` with the ability to populate and edit the peer set against an :class:`InfrahubClientSync`: :meth:`fetch` resolves every peer in a parallel batch and :meth:`add`, :meth:`extend`, and :meth:`remove` mutate the peer list in memory. Peers are exposed as :class:`RelatedNodeSync` instances and can be accessed by index via `manager[i]`. **Methods:** #### `fetch`[​](#fetch-1 "Direct link to fetch-1") ``` fetch(self) -> None ``` Populate the peer set and resolve every peer to a full node. When the manager is not yet initialized, the parent node is re-queried with this relationship included so the peer list can be populated. The peers are then fetched in a parallel batch grouped by kind and stored in the client store. **Raises:** * `Error`: If any peer is missing an `id` or `typename` and cannot be resolved. #### `add`[​](#add-1 "Direct link to add-1") ``` add(self, data: str | RelatedNodeSync | dict) -> None ``` Add a new peer to this relationship. The new peer is only added when its ID or HFID is not already present; duplicate adds are silently ignored. **Args:** * `data`: The peer to add. Accepts an ID string, an existing :class:`RelatedNodeSync`, or a dict describing the peer (with `id` or `hfid` keys, plus optional relationship properties). **Raises:** * `UninitializedError`: If :meth:`fetch` has not been called on this manager yet. #### `extend`[​](#extend-1 "Direct link to extend-1") ``` extend(self, data: Iterable[str | RelatedNodeSync | dict]) -> None ``` Add new peers to this relationship. This is a convenience wrapper that calls :meth:`add` for every item in `data`. Items already present (by ID or HFID) are silently ignored. **Args:** * `data`: The peers to add, in any of the formats accepted by :meth:`add`. **Raises:** * `UninitializedError`: If :meth:`fetch` has not been called on this manager yet. #### `remove`[​](#remove-1 "Direct link to remove-1") ``` remove(self, data: str | RelatedNodeSync | dict) -> None ``` Remove a peer from this relationship. The peer to remove is matched first by ID, then by HFID. When no match is found, the call is a no-op. **Args:** * `data`: The peer to remove. Accepts an ID string, an existing :class:`RelatedNodeSync`, or a dict describing the peer. **Raises:** * `UninitializedError`: If :meth:`fetch` has not been called on this manager yet. * `IndexError`: If the internal peer index is inconsistent with the lookup result. --- # Manage data with Object files ## Introduction[​](#introduction "Direct link to Introduction") An Object file is a YAML file that allows you to manage data to be loaded in Infrahub based on your own custom schema. It provides a declarative way to define and manage resources in your Infrahub instance. Object files work well for models that don't change too often and/or that need to be tracked in Git. Examples include: Groups, tags, Users, etc. Below is an example of an Object file that defines tags (`BuiltinTag`). ``` --- apiVersion: infrahub.app/v1 kind: Object spec: kind: BuiltinTag data: - name: Blue - name: Yellow - name: Red ``` Object files are meant to be used in an idempotent way and as such they work better for models with a Human Friendly ID (HFID) defined. An HFID is a unique identifier that makes it easier to reference objects across different files and operations. ## Load Object files into Infrahub[​](#load-object-files-into-infrahub "Direct link to Load Object files into Infrahub") Object files can be loaded into Infrahub using the `infrahubctl object load` command. ``` infrahubctl object load ``` Multiple object files can be loaded at once by specifying the path to multiple files or by specifying a directory. The `object load` command will create/update the objects using an `Upsert` operation. All objects previously loaded will NOT be deleted in the Infrahub instance. Also, if some objects present in different files are identical and dependent on each other, the `object load` command will NOT calculate the dependencies between the objects and as such it's the responsibility of the users to execute the command in the right order. > Object files can also be loaded into Infrahub when using external Git repositories. To see how to do this, please refer to the [.infrahub.yml](https://docs.infrahub.app/topics/infrahub-yml) documentation. ### Validate the format of object files[​](#validate-the-format-of-object-files "Direct link to Validate the format of object files") The object file can be validated using the `infrahubctl object validate` command. ``` infrahubctl object validate ``` ## Object file format[​](#object-file-format "Direct link to Object file format") All object files must start with the following format, all other formats will be automatically ignored. Each file is intended for one specific top level kind, but one file can include multiple nested objects of any kind. The kind of the top level object must be defined in spec/kind. ``` --- apiVersion: infrahub.app/v1 kind: Object spec: kind: parameters: expand_range: # Optional, defaults to false data: - [...] ``` > Multiple documents in a single YAML file are also supported, each document will be loaded separately. Documents are separated by `---` ### Data processing parameters[​](#data-processing-parameters "Direct link to Data processing parameters") The `parameters` field controls how the data in the object file is processed before loading into Infrahub: | Parameter | Description | Default | | -------------- | -------------------------------------------------------------------------------------------------------------- | ------- | | `expand_range` | When set to `true`, range patterns (for example, `[1-5]`) in string fields are expanded into multiple objects. | `false` | When `expand_range` is not specified, it defaults to `false`. ### Relationship of cardinality one[​](#relationship-of-cardinality-one "Direct link to Relationship of cardinality one") A relationship of cardinality one can either reference an existing node via its HFID or create a new node if it doesn't exist. In the example below, both `site` and `primary_ip` are relationships of cardinality one. ``` --- apiVersion: infrahub.app/v1 kind: Object spec: kind: InfraDevice data: - name: edge01 site: "Paris" # Reference existing node via its HFID primary_ip: # Nested object, will be created if it doesn't exist data: address: "192.168.1.1" ``` ### Relationship of cardinality many[​](#relationship-of-cardinality-many "Direct link to Relationship of cardinality many") A relationship of cardinality many can reference existing nodes via their HFID or define nested objects. #### Existing nodes referenced by their HFID[​](#existing-nodes-referenced-by-their-hfid "Direct link to Existing nodes referenced by their HFID") Existing nodes can be referenced by their HFID in string format or in list format. In the example below, both `best_friends` and `tags` are relationships of cardinality many. > An HFID is composed of a single value, it's possible to use a string instead of a list ``` --- apiVersion: infrahub.app/v1 kind: Object spec: kind: TestingPerson data: - name: Mike Johnson height: 175 best_friends: # Relationship of cardinality many that references existing nodes based on their HFID - [Jane Smith, Max] - [Sarah Williams, Charlie] tags: - Veterinarian # Existing Node referenced by its HFID in string format - [Breeder] # Existing Node referenced by its HFID in list format ``` #### Nested objects[​](#nested-objects "Direct link to Nested objects") When defining nested objects, the node will be automatically created if it doesn't exist and if the relationship between the parent object and the nested object exists, it will be automatically inserted. For example, in the example below, the `owner` of a `TestingDog` doesn't need to be specified because it will be automatically inserted. Two different syntax are supported: * A dictionary with multiple values under data * A list of objects ##### Nested objects as a dictionary[​](#nested-objects-as-a-dictionary "Direct link to Nested objects as a dictionary") In the example below, `tags` is a relationship of cardinality many that is defined as a dictionary with multiple values under data. > The kind is optional here because there is only one option possible (not a generic) ``` --- apiVersion: infrahub.app/v1 kind: Object spec: kind: TestingPerson data: - name: Alex Thompson tags: data: - name: dog-lover description: "Dog Lover" - name: veterinarian description: "Veterinarian" ``` This format works well when all objects are of the same kind and when all objects are using the same properties. For more complex cases, the list of objects format is more flexible. ##### Nested objects as a list of objects[​](#nested-objects-as-a-list-of-objects "Direct link to Nested objects as a list of objects") In the example below, `animals` is a relationship of cardinality many that is defined as a list of objects. Each object must contain a `data` key and each object can also define a specific `kind`. > If the kind is not specified, it will be inferred from schema ``` --- apiVersion: infrahub.app/v1 kind: Object spec: kind: TestingPerson data: - name: Alex Thompson height: 180 animals: - kind: TestingDog data: name: Max weight: 25 breed: Golden Retriever color: "#FFD700" - kind: TestingCat data: name: Mimi breed: Persian ``` ### Support for metadata[​](#support-for-metadata "Direct link to Support for metadata") Metadata support is planned for future releases. Currently, the Object file does not support metadata on attributes or relationships. ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Common issues[​](#common-issues "Direct link to Common issues") 1. **Objects not being created**: Ensure that the YAML syntax is correct and that the file follows the required format. 2. **Dependency errors**: When objects depend on each other, load them in the correct order (dependencies first). 3. **Validation errors**: Use the `infrahubctl object validate` command to check for syntax errors before loading. ### Best practices[​](#best-practices "Direct link to Best practices") 1. Use Human Friendly IDs (HFIDs) for all objects to ensure consistent referencing. 2. Keep object files organized by model type or purpose. 3. Validate object files before loading them into production environments. 4. Use comments in your YAML files to document complex relationships or dependencies. ## Range expansion in object files[​](#range-expansion-in-object-files "Direct link to Range expansion in object files") The Infrahub Python SDK supports **range expansion** for string fields in object files when the `parameters > expand_range` is set to `true`. This feature allows you to specify a range pattern (for example, `[1-5]`) in any string value, and the SDK will automatically expand it into multiple objects during validation and processing. ``` --- apiVersion: infrahub.app/v1 kind: Object spec: kind: BuiltinLocation parameters: expand_range: true # Enable range expansion data: - name: AMS[1-3] type: Country ``` ### How range expansion works[​](#how-range-expansion-works "Direct link to How range expansion works") * Any string field containing a pattern like `[1-5]`, `[10-15]`, or `[1,3,5]` will be expanded into multiple objects. * If multiple fields in the same object use range expansion, **all expanded lists must have the same length**. If not, validation will fail. * The expansion is performed before validation and processing, so all downstream logic works on the expanded data. ### Examples[​](#examples "Direct link to Examples") #### Single field expansion[​](#single-field-expansion "Direct link to Single field expansion") ``` spec: kind: BuiltinLocation parameters: expand_range: true data: - name: AMS[1-3] type: Country ``` This will expand to: ``` - name: AMS1 type: Country - name: AMS2 type: Country - name: AMS3 type: Country ``` #### Multiple field expansion (matching lengths)[​](#multiple-field-expansion-matching-lengths "Direct link to Multiple field expansion (matching lengths)") ``` spec: kind: BuiltinLocation parameters: expand_range: true data: - name: AMS[1-3] description: Datacenter [A-C] type: Country ``` This will expand to: ``` - name: AMS1 description: Datacenter A type: Country - name: AMS2 description: Datacenter B type: Country - name: AMS3 description: Datacenter C type: Country ``` #### Error: mismatched range lengths[​](#error-mismatched-range-lengths "Direct link to Error: mismatched range lengths") If you use ranges of different lengths in multiple fields: ``` spec: kind: BuiltinLocation parameters: expand_range: true data: - name: AMS[1-3] description: "Datacenter [10-15]" type: Country ``` This will **fail validation** with an error like: ``` Range expansion mismatch: fields expanded to different lengths: [3, 6] ``` ### Notes[​](#notes "Direct link to Notes") * Range expansion is supported for any string field in the `data` section. * If no range pattern is present, the field is left unchanged. * If expansion fails for any field, validation will fail with an error message. --- # Understanding tracking in the Python SDK ## Introduction[​](#introduction "Direct link to Introduction") Tracking mode in the Python SDK is an advanced functionality designed to optimize and streamline data operations. ## The essence of tracking[​](#the-essence-of-tracking "Direct link to The essence of tracking") ### What is tracking?[​](#what-is-tracking "Direct link to What is tracking?") Tracking mode in the Python SDK is aggregates operations, such as creations, updates, and deletions, performed during a session into a `CoreStandardGroup` object. This aggregation facilitates efficient management and auditing of data mutations. ### Why it matters?[​](#why-it-matters "Direct link to Why it matters?") In complex workflows where multiple operations occur sequentially, tracking ensures that these actions are consolidated, leading to easier auditing. This is particularly crucial in scenarios where operations must be repeatable without side effects — a core principle of idempotency. ## Under the hood[​](#under-the-hood "Direct link to Under the hood") At the heart of the tracking feature lies the `CoreStandardGroup` object. This object acts as a container, grouping related operations to provide a unified view of the changes made during a session. This grouping is not just for organizational purposes; it plays a vital role in enabling idempotent operations. ### Automatic vs. manual tracking[​](#automatic-vs-manual-tracking "Direct link to Automatic vs. manual tracking") The SDK offers two modes of operation for tracking: automatic and manual: * Automatic tracking is facilitated through the use of a context manager, simplifying session management and ensuring that all operations within the context are tracked. * Manual tracking, on the other hand, provides granular control, allowing developers to start and end sessions explicitly and manage group memberships as needed. ## Achieving idempotency[​](#achieving-idempotency "Direct link to Achieving idempotency") ### Idempotency explained[​](#idempotency-explained "Direct link to Idempotency explained") Idempotency is a property of certain operations that ensures the operation can be performed multiple times without changing the result beyond the initial application. This concept is critical in ensuring that scripts and operations are safe to retry, leading to more robust and reliable systems. ### Tracking in idempotency[​](#tracking-in-idempotency "Direct link to Tracking in idempotency") The tracking feature of the Python SDK shines in scenarios requiring idempotency. By grouping operations within a `CoreStandardGroup`, the SDK allows for operations to be rolled back or repeated without adverse effects, ensuring that the state of the system remains consistent. ## Practical applications[​](#practical-applications "Direct link to Practical applications") ### Use cases[​](#use-cases "Direct link to Use cases") * **Auditing:** Tracking provides a clear audit trail of operations performed during a session. * **Error Handling:** In case of errors, tracking enables precise identification and rollback of changes. ### Implementing an idempotent script[​](#implementing-an-idempotent-script "Direct link to Implementing an idempotent script") An idempotent script using the Python SDK might involve the following steps: 1. **Start a Tracking Session:** Begin with a unique identifier and any relevant parameters. 2. **Perform Operations:** Execute create, update, or delete operations as required. 3. **End the Session:** Conclude the tracking session, optionally updating the group context to reflect the changes made. 4. **Handle Errors:** In case of errors, use the tracking information to rollback or correct the state. For more detailed guidance on implementing tracking in your scripts, refer to the [tracking guide](/python-sdk/guides/tracking.md). --- # Schema library for Infrahub Welcome to the Schema Library for Infrahub! This repository offers a collection of schemas designed to streamline and standardize infrastructure-related data structures. warning This project is currently a collection of examples intended to serve as inspiration. Please note that it is in an experimental phase and may undergo significant changes. ## Use case[​](#use-case "Direct link to Use case") Infrahub ships without built-in schemas, giving you complete freedom to define your own. However, starting from scratch can be overwhelming, especially when many infrastructures share common models. That's where the [Schema Library](https://github.com/opsmill/schema-library), maintained by OpsMill and the community, comes in. **The library provides a curated collection of practical, ready-to-use schemas designed to reflect real-world infrastructure needs. Use them out of the box, or treat them as a foundation: copy, adapt, and extend each schema to align precisely with your organization.** ## Project structure[​](#project-structure "Direct link to Project structure") This project is divided into three main parts: * **Base**: The foundational layer required for most extensions. It must be loaded before adding extensions. * **Extensions**: Generic schema components for managing infrastructure. Extensions may have dependencies on each other. * **Experimental**: Schema components that are not yet fully supported. ## Getting started[​](#getting-started "Direct link to Getting started") ### Clone the repository[​](#clone-the-repository "Direct link to Clone the repository") ``` git clone https://github.com/opsmill/schema-library.git cd schema-library ``` note One of Infrahub's key strengths is its flexibility. **We encourage you to copy any schemas you find useful into your own repository** and tailor them to your specific needs, ensuring your schema aligns perfectly with your requirements. ### How to load schemas[​](#how-to-load-schemas "Direct link to How to load schemas") There are two main ways to load schemas into Infrahub, depending on where you are in your Infrahub journey: * **Quick View**: To quickly view a schema, you can use `infrahubctl`. Please reference the [`infrahubctl` Guide](https://docs.infrahub.app/infrahubctl) for further instructions. * **Controlled Integration**: For a more organized and unified approach, you can connect a Git repository. Follow [this guide](https://docs.infrahub.app/guides/repository) to connect a Git repository. note For proof of concept or development purposes, using `infrahubctl` is the preferred approach. When running Infrahub in production, you might need the extra control provided by Git integration to load schemas. ### Load schemas[​](#load-schemas "Direct link to Load schemas") important You need to load the `base` schema before loading any extensions. Extensions may have dependencies on each other, so you'll need to load them in the correct order. ``` # Load the base infrahubctl schema load base # Load an extension infrahubctl schema load extensions/location_minimal # Load another extension infrahubctl schema load extensions/vlan ``` ## Available schemas[​](#available-schemas "Direct link to Available schemas") This list provides an overview of the schemas available in this repository. Each schema is designed to address specific infrastructure needs, and you can find detailed documentation for each one by clicking its name. ### base[​](#base "Direct link to base") | Name | Description | | ------------------------------------------------------------- | ------------------------------------------------------------------------------------- | | **[DCIM](/schema-library/reference/dcim.md)** | Basic DCIM schema to capture devices, racks, interfaces, and related information. | | **[IPAM](/schema-library/reference/ipam.md)** | Basic IPAM schema to capture IP addresses, subnets, and related information. | | **[Locations](/schema-library/reference/location.md)** | Basic Location schema to capture locations, sites, and related information. | | **[Organization](/schema-library/reference/organization.md)** | Basic Organization schema to capture organizations, vendors, and related information. | ### experimental[​](#experimental "Direct link to experimental") | Name | Description | | --------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **[Azure](/schema-library/reference/azure.md)** | This schema extension introduces cloud support for Microsoft Azure. | | **[Circuit Service](/schema-library/reference/circuit_service.md)** | This schema extension contains model coming on top of circuit to capture a single service shared across multiple circuits. For example you have a MPLS network supported by a provider connecting multiple locations: - One single CircuitService would be needed to store MPLS related information (e.g. service id, provider ...) - On each site we would create a circuit connecting on one side our device and the CircuitService on the other side | | **[Infiniband](/schema-library/reference/infiniband.md)** | This schema extension adds support for InfiniBand switches. | | **[Location Extended](/schema-library/reference/location_extended.md)** | This schema extension is the most detailed when it comes to location, you'll find all the layers you can think of. | | **[Modules Linecards](/schema-library/reference/modules_linecards.md)** | This schema extension allows you to capture Linecard related information like the version. You can insert the Linecard into a Dcim Physical Device and leverage the Linecard type model. The Linecard can accept PIC to help configure PORT information like breakout-capabilities and configurations. | | **[Modules Routing Engine](/schema-library/reference/modules_routing_engine.md)** | This schema extension allows you to capture Routing Engine related information like the version. You can insert the Routing Engine into a Dcim Physical Device and leverage the Routing Engine type model. | | **[Optical Transport](/schema-library/reference/optical_transport.md)** | Comprehensive optical transport network schemas for DWDM/WDM systems (ADVA FSP 3000 and similar platforms). Covers four layers: wavelength (ITU-T G.694.1 grid, optical bands, DWDM channels), topology (logical optical nodes, passive multiplexers, fiber links), equipment (transponder/amplifier/ROADM modules, ROADM degrees, WSS cross-connects), and service (end-to-end optical services, optical paths, path segments). Not designed to be loaded together with extensions/dwdm. | | **[QoS](/schema-library/reference/qos.md)** | This schema extension contains models for Quality of Service (QoS) | | **[Security](/schema-library/reference/security.md)** | This schema extension contains models for implementing detailed security. | | **[Tenancy](/schema-library/reference/tenancy.md)** | This schema extension introduces tenancy for some of the schema nodes (circuits...) | | **[Topology](/schema-library/reference/topology.md)** | A schema for defining and managing network topology, strategies, and services. | | **[VLAN Translation](/schema-library/reference/vlan-translation.md)** | This schema extension is based on Juniper VLAN MAP, and not yet test out for other vendors. | ### extensions[​](#extensions "Direct link to extensions") | Name | Description | | ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **[Cable](/schema-library/reference/cable.md)** | This schema extension contains a basic Cable model allowing you to connect two endpoints. | | **[Circuit](/schema-library/reference/circuit.md)** | This schema extension contains Circuits and ways to connect them with your infrastructure! The circuit could be a fiber connecting two sites, you would then have two endpoints, one on each site. | | **[Cluster](/schema-library/reference/cluster.md)** | This schema extension contains the foundations to capture clusters. With this one in place you can unlock various clusters flavors (hosting cluster able to host VMs, firewall clusters built with specific appliances ...) | | **[Compute](/schema-library/reference/compute.md)** | With this schema extension in place you will be able to capture all your physical servers. It also gives you the baseline to build virtualization. You might consider HostingCluster extension to go with! | | **[Cross Connect](/schema-library/reference/cross_connect.md)** | This extension contains schema to capture Cross Connect. You can see it as "a cable operated by a provider". You will be able to attach it to a location and then connect endpoints to it (e.g. rear interface of a patch panel, circuit endpoint ...) | | **[DWDM](/schema-library/reference/dwdm.md)** | This schema extension contains models for OADM (Optical Add Drop Multiplexer) supporting various WDM (Wavelength Division Multiplexing) technologies such as DWDM (Dense Wavelength Division Multiplexing) or CWDM (Coarse Wavelength Division Multiplexing). With some vendors, the tunable optics are not configured via the channel number but via the wavelength and/or the frequency. This model provides a unique entry in Infrahub for those. | | **[Firewall Policer](/schema-library/reference/firewall_policer.md)** | This schema extension contains models for VMs. You might consider Cluster or/and Hypervisor extension to go with! | | **[Hosting Cluster](/schema-library/reference/hosting_cluster.md)** | A rather generic cluster built with compute units (e.g. servers) and able to host VMs. | | **[Interface Breakout](/schema-library/reference/interface_breakout.md)** | This schema extension introduces relationships to support breakout interfaces, enabling you to document the breakout of a physical interface into smaller physical interfaces. | | **[Lag](/schema-library/reference/lag.md)** | This schema extension includes models for Link Aggregation Groups (LAGs), enabling you to link physical interfaces as building blocs of your LAG interface. It can be used in standard networking environments as well as in compute scenarios, such as capturing bond interfaces. | | **[Location Minimal](/schema-library/reference/location_minimal.md)** | This schema extension is minimal but will provide you with basic items to store location related data. | | **[MLAG](/schema-library/reference/mlag.md)** | This schema extension contains the foundations to capture Multi-Chassis Link Aggregation Group (MLAG). It comes on top of the LAG extension. In this implementation, a MLAG interface is essentially a LAG interface but linked to a MLAG domain (instead of a device). The MLAG domain regroup devices together (usually 2) and is built over LAG interfaces used as peer-link between the devices. MLAG interfaces defined at the MLAG domain level are then spread across all devices in the domain. Note: This is a very minimalist implementation of MLAG, the goal is to make it easy to blend with existing models you might have. For example, if you are operating in a data center fabric environment you might already have a LeafGroup or similar concept. In that case you can have your LeafGroup inherit from GenericMlagDomain and add the necessary relationships/attributes. Next steps: - Add support for layer 3 overlay for MLAG interfaces (loopback, peer address ...) - Create virtual relationship so MLAG interfaces can be seen on the devices in the domain | | **[Modules](/schema-library/reference/modules.md)** | This schema extension allows you to capture Device Modules related information like the serial number or the status. You can insert the Module into a Dcim Physical Device. NOTE: This extension doesn't contain any Nodes, you can use the extension module\_linecards or modules\_routing\_engine to use it | | **[Patch Panel](/schema-library/reference/patch_panel.md)** | This schema extension allows you to capture patch panel related information like rear/front interfaces and mapping between them. You can insert the patch panel into a rack and leverage the device type model. Finally you can also capture information about potential modules you would insert into your patch panel. NOTE: This extension is compatible with all sort of connectors, meaning you can plug cable, circuits, cross-connect to front & rear interfaces! | | **[Peering IXP](/schema-library/reference/peering_ixp.md)** | This schema extension contains all you need to model anything revolving around internet peering (Exchange points ...)! | | **[Physical Disk](/schema-library/reference/physical_disk.md)** | Simple schema allowing you to capture physical disks information for the sake of inventory and lifecycle management. NOTE: This extension is compatible with all sort of device. You can apply the generic "DeviceWithPhysicalDisks" to particular model to enable disks tracking. You might also link that schema to location for instance to capture spares. | | **[QinQ](/schema-library/reference/qinq.md)** | This schema extension brings extensions to VLAN model in order to support QinQ. | | **[Routing](/schema-library/reference/routing.md)** | This schema extension contains generics to create Routing Protocol "Instance". The idea is to create one Routing Protocol instance per IpamVRF + DcimDevice pair. | | **[Routing Aggregate](/schema-library/reference/routing_aggregate.md)** | This schema extension contains all you need to model the Aggregate Routing Protocol. | | **[Routing BGP](/schema-library/reference/routing_bgp.md)** | This schema extension contains all you need to model your BGP platform. | | **[Routing BGP Community](/schema-library/reference/routing_bgp_community.md)** | This schema extension adds the BGP Communities models. | | **[Routing BGP RR](/schema-library/reference/routing_bgp_rr.md)** | This schema extension extend the BGP extension to add BGP Route Reflector Clustering. | | **[Routing OSPF](/schema-library/reference/routing_ospf.md)** | This schema extension contains all you need to model the OSPF Routing Protocol. | | **[Routing PIM](/schema-library/reference/routing_pim.md)** | This schema extension contains all you need to model the PIM Protocol. | | **[Routing Policies](/schema-library/reference/routing_policies.md)** | This schema extension contains a generic to create Routing Policies. This Generic can be extend for each Routing Protocols you may want to use. | | **[Routing Policies (PIM)](/schema-library/reference/routing_policies_pim.md)** | This schema inherits the `RoutingPolicy` schema and removes `import_policies` and `export_policies` attributes. However it adds a number of relationships to `RoutingPIM`. | | **[Routing Policies Aggregate](/schema-library/reference/routing_policies_aggregate.md)** | This extension is using the Routing Policies extensions and the Routing Aggregate one together. | | **[Routing Policies BGP](/schema-library/reference/routing_policies_bgp.md)** | This extension is using the Routing Policies extensions and the Routing BGP one together. | | **[Routing Policies OSPF](/schema-library/reference/routing_policies_ospf.md)** | This extension is using the Routing Policies extensions and the Routing OSPF one together. | | **[SFP](/schema-library/reference/sfp.md)** | This schema extension gives you all the models you need to document Small Form-factor Pluggable (SFP). You can either plug it into an interface or attach it to a location (e.g. it's a spare SFP stored in a rack). Improvements: - As of now there is no verification with type / form factor / protocol / distance ... - You could plug any SFP into any equipment interface (e.g. a virtual interface ...) - You could link a SFP to an interface AND a location ... | | **[SNMP](/schema-library/reference/snmp.md)** | This schema extension contains models for SNMP Communities and SNMP Clients. As you can see this extension is not linked to Tenancy or Device, as you could decide to link the Community to different models based on your use case. | | **[Topology](/schema-library/reference/topology.md)** | This schema extension introduces abstract network pods and services running in the pods, such as MPLS and EVPN. | | **[Users](/schema-library/reference/users.md)** | This schema extension contains models for account management. | | **[VLAN](/schema-library/reference/vlan.md)** | This schema extension contains models to support VLANs in you network. | | **[VRF](/schema-library/reference/vrf.md)** | This schema extension contains models to support VRF in your network. | | **[VRRP](/schema-library/reference/vrrp.md)** | This schema extension contains models for VRRP. | ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") * **`Unable to load the schema: xxx unable to find the generic yyy` - Missing requirements** This error indicates that the schema you are trying to load has dependencies on other schemas that are not present in your Infrahub instance. success To resolve this, ensure that you have loaded all required schemas before attempting to load the one in question. You can find the list of dependencies in the schema documentation. --- # Contributing We welcome contributions and feedback! Please open an issue or submit a pull request to suggest additions, improvements, or to report bugs. ## Adding a new schema[​](#adding-a-new-schema "Direct link to Adding a new schema") To add a new schema: * Create a directory in either `experimental` or `extension` with the schema name (ensure the name is unique). * Add a `.yml` file using the [Infrahub schema format](https://docs.infrahub.app/reference/schema). * Update the `.metadata.yml` file to include your schema, providing its `name`, `description`, and `dependencies`. * To verify integration, run `invoke schemas.load-all-schemas` to load the entire schema library in your local Infrahub instance. To add documentation for your schema: * Run `invoke docs.generate` to generate documentation files. * Then run `invoke docs.build` and `invoke docs.serve` to build and serve the documentation locally. ## Documentation[​](#documentation "Direct link to Documentation") Most documentation is generated automatically: * The `docs.py` task generates documentation. * `home.mdx` is generated from the `_templates/home_page.j2` template. * Each file in the `reference` directory is generated from the `_templates/schema_reference.j2` template. important To modify the documentation, edit the templates in the `_templates` directory. After making changes, run `invoke docs.generate` to update the documentation. --- # Azure This schema extension introduces cloud support for Microsoft Azure. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) * **Attribution:** [Rowan Coleman](https://www.linkedin.com/in/rowan-coleman-6a147156/) ## Nodes[​](#nodes "Direct link to Nodes") ### Location[​](#location "Direct link to Location") * **Namespace:** Azure #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ---- | ----------- | ---- | -------- | -------------- | ------- | | name | | Text | | | | ### Tenant[​](#tenant "Direct link to Tenant") * **Namespace:** Azure #### Attributes[​](#attributes-1 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ---------- | ----------- | ---- | -------- | -------------- | ------- | | name | | Text | | | | | tenant\_id | | Text | | | | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------- | ----------------- | -------- | ----------- | --------- | | subscriptions | AzureSubscription | | many | Component | ### Subscription[​](#subscription "Direct link to Subscription") * **Namespace:** Azure #### Attributes[​](#attributes-2 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ---------------- | ----------- | ---- | -------- | -------------- | ------- | | name | | Text | | | | | subscription\_id | | Text | | | | #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | -------------- | ------------------ | -------- | ----------- | --------- | | tenant | AzureTenant | False | one | Parent | | resourcegroups | AzureResourceGroup | | many | Component | ### ResourceGroup[​](#resourcegroup "Direct link to ResourceGroup") * **Namespace:** Azure #### Attributes[​](#attributes-3 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ---- | ----------- | ---- | -------- | -------------- | ------- | | name | | Text | | | | #### Relationships[​](#relationships-2 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------ | ----------------- | -------- | ----------- | --------- | | location | AzureLocation | | one | Attribute | | subscription | AzureSubscription | False | one | Parent | ### VirtualNetwork[​](#virtualnetwork "Direct link to VirtualNetwork") * **Label:** Virtual Networks * **Namespace:** Azure * **Inherit From:** AzureResource #### Relationships[​](#relationships-3 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | -------------- | ------------------------- | -------- | ----------- | --------- | | address\_space | BuiltinIPPrefix | | many | Attribute | | subnets | AzureVirtualNetworkSubnet | | many | Component | ### VirtualNetworkSubnet[​](#virtualnetworksubnet "Direct link to VirtualNetworkSubnet") * **Label:** Subnets * **Namespace:** Azure #### Attributes[​](#attributes-4 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ---- | ----------- | ---- | -------- | -------------- | ------- | | name | | Text | | | | #### Relationships[​](#relationships-4 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ----------------- | ------------------- | -------- | ----------- | --------- | | virtualnetwork | AzureVirtualNetwork | False | one | Parent | | address\_prefixes | BuiltinIPPrefix | | many | Attribute | ## Generics[​](#generics "Direct link to Generics") ### Resource[​](#resource "Direct link to Resource") * **Label:** Azure * **Namespace:** Azure #### Attributes[​](#attributes-5 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ---- | ----------- | ---- | -------- | -------------- | ------- | | name | | Text | | | | #### Relationships[​](#relationships-5 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------- | ------------------ | -------- | ----------- | --------- | | location | AzureLocation | | one | Attribute | | resourcegroup | AzureResourceGroup | False | one | Parent | ## Code[​](#code "Direct link to Code") ``` version: '1.0' generics: - name: Resource namespace: Azure label: Azure include_in_menu: true attributes: - name: name kind: Text relationships: - name: location cardinality: one kind: Attribute peer: AzureLocation - name: resourcegroup cardinality: one peer: AzureResourceGroup kind: Parent optional: false nodes: - name: Location namespace: Azure menu_placement: AzureResource display_label: name__value attributes: - name: name kind: Text - name: Tenant namespace: Azure menu_placement: AzureResource display_label: '{{ name__value }} (Tenant ID: {{ tenant_id__value }})' attributes: - name: name kind: Text - name: tenant_id kind: Text relationships: - name: subscriptions cardinality: many peer: AzureSubscription kind: Component - name: Subscription namespace: Azure menu_placement: AzureResource display_label: '{{ name__value }} (Subscription ID: {{ subscription_id__value }})' attributes: - name: name kind: Text - name: subscription_id kind: Text relationships: - name: tenant cardinality: one peer: AzureTenant kind: Parent optional: false - name: resourcegroups cardinality: many peer: AzureResourceGroup kind: Component - name: ResourceGroup namespace: Azure menu_placement: AzureResource display_label: name__value attributes: - name: name kind: Text relationships: - name: location cardinality: one kind: Attribute peer: AzureLocation - name: subscription cardinality: one peer: AzureSubscription kind: Parent optional: false - name: VirtualNetwork label: Virtual Networks namespace: Azure menu_placement: AzureResource display_label: name__value inherit_from: - AzureResource relationships: - name: address_space cardinality: many kind: Attribute peer: BuiltinIPPrefix - name: subnets cardinality: many kind: Component peer: AzureVirtualNetworkSubnet - name: VirtualNetworkSubnet label: Subnets namespace: Azure menu_placement: AzureResource include_in_menu: true display_label: name__value attributes: - name: name kind: Text relationships: - name: virtualnetwork cardinality: one peer: AzureVirtualNetwork kind: Parent optional: false - name: address_prefixes cardinality: many kind: Attribute peer: BuiltinIPPrefix ``` --- # Cable This schema extension contains a basic Cable model allowing you to connect two endpoints. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) ## Nodes[​](#nodes "Direct link to Nodes") ### Cable[​](#cable "Direct link to Cable") * **Label:** Cable * **Description:** Physical cable connecting two endpoints * **Namespace:** Dcim * **Icon:** mdi :cable-data * **Inherit From:** DcimConnector #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------- | ----------- | -------- | -------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | status | | Dropdown | True | | connected, planned | | cable\_type | | Dropdown | True | | cat3, cat5, cat5e, cat6, cat6a, cat7, cat7a, cat8, mmf-om1, mmf-om2, mmf-om3, mmf-om4, mmf-om5, smf-os1, smf-os2, dac-passive, dac-active, aoc, coaxial, mrj21-trunk, power, usb | | color | | Dropdown | True | | dark-red, red, pink, rose, fuchsia, purple, dark-purple, indigo, blue, light-blue, cyan, teal, aqua, dark-green, green, light-green, lime, yellow, amber, orange, dark-orange, brown, light-grey, grey, dark-grey, black | | label | | Text | True | | | | length | | Number | True | | | ## Code[​](#code "Direct link to Code") ``` version: '1.0' nodes: - name: Cable namespace: Dcim description: Physical cable connecting two endpoints label: Cable icon: mdi:cable-data inherit_from: - DcimConnector attributes: - name: status kind: Dropdown optional: true order_weight: 1000 choices: - name: connected label: Connected description: Fully operational and currently in connected color: '#7fbf7f' - name: planned label: Planned description: In the process of being set up color: '#ffff7f' - name: cable_type kind: Dropdown optional: true order_weight: 1100 choices: - name: cat3 label: CAT3 description: Category 3 twisted pair cable. color: '#99ccff' - name: cat5 label: CAT5 description: Category 5 twisted pair cable. color: '#99ccff' - name: cat5e label: CAT5e description: Enhanced Category 5 cable. color: '#99ccff' - name: cat6 label: CAT6 description: Category 6 twisted pair cable. color: '#99ccff' - name: cat6a label: CAT6A description: Augmented Category 6 cable. color: '#99ccff' - name: cat7 label: CAT7 description: Category 7 twisted pair cable. color: '#99ccff' - name: cat7a label: CAT7A description: Augmented Category 7 cable. color: '#99ccff' - name: cat8 label: CAT8 description: Category 8 twisted pair cable. color: '#99ccff' - name: mmf-om1 label: Multimode Fiber OM1 description: "Multimode fiber (62.5/125 \xB5m)." color: '#ff9933' - name: mmf-om2 label: Multimode Fiber OM2 description: "Multimode fiber (50/125 \xB5m)." color: '#ff9933' - name: mmf-om3 label: Multimode Fiber OM3 description: "Optimized multimode fiber (50/125 \xB5m)." color: '#ff9933' - name: mmf-om4 label: Multimode Fiber OM4 description: "Enhanced multimode fiber (50/125 \xB5m)." color: '#ff9933' - name: mmf-om5 label: Multimode Fiber OM5 description: "Wideband multimode fiber (50/125 \xB5m)." color: '#ff9933' - name: smf-os1 label: Singlemode Fiber OS1 description: Standard singlemode fiber. color: '#ffcc00' - name: smf-os2 label: Singlemode Fiber OS2 description: Improved singlemode fiber. color: '#ffcc00' - name: dac-passive label: Passive DAC description: Direct Attach Copper (Passive). color: '#a0a0a0' - name: dac-active label: Active DAC description: Direct Attach Copper (Active). color: '#a0a0a0' - name: aoc label: Active Optical Cable description: Active Optical Cable. color: '#a0a0a0' - name: coaxial label: Coaxial description: Coaxial cable for broadband. color: '#000000' - name: mrj21-trunk label: MRJ21 Trunk description: High-density copper cable. color: '#000000' - name: power label: Power description: Power cables for electrical connections. color: '#000000' - name: usb label: USB description: Universal Serial Bus cable. color: '#000000' - name: color kind: Dropdown optional: true order_weight: 1300 choices: - name: dark-red label: Dark Red color: '#aa1409' - name: red label: Red color: '#f44336' - name: pink label: Pink color: '#e91e63' - name: rose label: Rose color: '#ffe4e1' - name: fuchsia label: Fuchsia color: '#ff66ff' - name: purple label: Purple color: '#9c27b0' - name: dark-purple label: Dark Purple color: '#673ab7' - name: indigo label: Indigo color: '#3f51b5' - name: blue label: Blue color: '#2196f3' - name: light-blue label: Light Blue color: '#03a9f4' - name: cyan label: Cyan color: '#00bcd4' - name: teal label: Teal color: '#009688' - name: aqua label: Aqua color: '#00ffff' - name: dark-green label: Dark Green color: '#2f6a31' - name: green label: Green color: '#4caf50' - name: light-green label: Light Green color: '#8bc34a' - name: lime label: Lime color: '#cddc39' - name: yellow label: Yellow color: '#ffeb3b' - name: amber label: Amber color: '#ffc107' - name: orange label: Orange color: '#ff9800' - name: dark-orange label: Dark Orange color: '#ff5722' - name: brown label: Brown color: '#795548' - name: light-grey label: Light Grey color: '#c0c0c0' - name: grey label: Grey color: '#9e9e9e' - name: dark-grey label: Dark Grey color: '#607d8b' - name: black label: Black color: '#111111' - name: label kind: Text optional: true order_weight: 1200 - name: length label: Length (in cm) kind: Number optional: true order_weight: 1350 ``` --- # Circuit This schema extension contains Circuits and ways to connect them with your infrastructure! The circuit could be a fiber connecting two sites, you would then have two endpoints, one on each site. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) * [extensions/location\_minimal](/schema-library/reference/location_minimal.md) ## Nodes[​](#nodes "Direct link to Nodes") ### Circuit[​](#circuit "Direct link to Circuit") * **Label:** Circuit * **Description:** A Circuit represent service operated by a provider. * **Namespace:** Dcim * **Icon:** mdi :cable-data * **Human Friendly ID:** circuit\_id\_\_value #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ------------- | ------------------------------ | -------- | -------- | -------------- | ------------------------------------------ | | circuit\_id | | Text | | | | | description | | Text | True | | | | circuit\_type | Specifies the type of circuit. | Dropdown | | | upstream, peering, dark\_fiber, mpls | | status | | Dropdown | | | active, provisioning, maintenance, drained | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | -------- | -------------------- | -------- | ----------- | --------- | | provider | OrganizationProvider | False | one | Attribute | | location | LocationHosting | True | one | Attribute | | enpoints | DcimCircuitEndpoint | True | many | Component | ### CircuitEndpoint[​](#circuitendpoint "Direct link to CircuitEndpoint") * **Label:** Circuit Endpoint * **Description:** A circuit endpoint, could be a position in a MMR... * **Namespace:** Dcim * **Icon:** mdi :ethernet * **Uniqueness Constraints:** * circuit, name\_\_value * **Human Friendly ID:** circuit\_\_circuit\_id\_\_value, name\_\_value * **Inherit From:** DcimEndpoint #### Attributes[​](#attributes-1 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------- | ------------------------------------------------------------------ | -------- | -------- | -------------- | ------------------------------------------ | | name | Name of the circuit endoint, could be a MMR position for instance. | Text | | | | | status | | Dropdown | True | | active, provisioning, maintenance, drained | | description | | Text | True | | | #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | -------- | --------------- | -------- | ----------- | --------- | | circuit | DcimCircuit | False | one | Parent | | location | LocationHosting | False | one | Attribute | ## Extensions[​](#extensions "Direct link to Extensions") note In this context "extensions" refer to modifications or additions to the existing schema, such as adding new attributes, relationships, or other schema elements. ### OrganizationProvider[​](#organizationprovider "Direct link to OrganizationProvider") #### Relationships[​](#relationships-2 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | -------- | ----------- | -------- | ----------- | ---- | | circuits | DcimCircuit | True | many | | ### LocationHosting[​](#locationhosting "Direct link to LocationHosting") #### Relationships[​](#relationships-3 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | -------- | ----------- | -------- | ----------- | ---- | | circuits | DcimCircuit | True | many | | ## Code[​](#code "Direct link to Code") ``` version: '1.0' nodes: - name: Circuit namespace: Dcim description: A Circuit represent service operated by a provider. label: Circuit icon: mdi:cable-data human_friendly_id: - circuit_id__value order_by: - circuit_id__value display_label: circuit_id__value attributes: - name: circuit_id kind: Text unique: true - name: description kind: Text optional: true - name: circuit_type kind: Dropdown description: Specifies the type of circuit. choices: - name: upstream label: Upstream description: Connection to an upstream provider or Internet service provider (ISP) color: '#1e90ff' - name: peering label: Peering description: Connection to another network for exchange of traffic color: '#20b2aa' - name: dark_fiber label: Dark Fiber description: Leased, unlit fiber for customer management and operation color: '#333333' - name: mpls label: MPLS description: Multi-Protocol Label Switching circuit for QoS-based routing color: '#7f00ff' - name: status kind: Dropdown choices: - name: active label: Active description: Fully operational and currently in service. color: '#7fbf7f' - name: provisioning label: Provisioning description: In the process of being set up and configured. color: '#ffff7f' - name: maintenance label: Maintenance description: Undergoing routine maintenance or repairs. color: '#ffd27f' - name: drained label: Drained description: Temporarily taken out of service. color: '#bfbfbf' relationships: - name: provider peer: OrganizationProvider optional: false cardinality: one kind: Attribute - name: location label: Location peer: LocationHosting optional: true cardinality: one kind: Attribute order_weight: 1500 - name: enpoints peer: DcimCircuitEndpoint optional: true cardinality: many kind: Component - name: CircuitEndpoint namespace: Dcim description: A circuit endpoint, could be a position in a MMR... label: Circuit Endpoint inherit_from: - DcimEndpoint icon: mdi:ethernet menu_placement: DcimCircuit human_friendly_id: - circuit__circuit_id__value - name__value uniqueness_constraints: - - circuit - name__value order_by: - name__value display_label: name__value attributes: - name: name kind: Text description: Name of the circuit endoint, could be a MMR position for instance. order_weight: 1000 - name: status kind: Dropdown optional: true choices: - name: active label: Active description: Fully operational and currently in service. color: '#7fbf7f' - name: provisioning label: Provisioning description: In the process of being set up and configured. color: '#ffff7f' - name: maintenance label: Maintenance description: Undergoing routine maintenance or repairs. color: '#ffd27f' - name: drained label: Drained description: Temporarily taken out of service. color: '#bfbfbf' - name: description kind: Text optional: true relationships: - name: circuit peer: DcimCircuit order_weight: 900 optional: false cardinality: one kind: Parent - name: location label: Location peer: LocationHosting optional: false cardinality: one kind: Attribute order_weight: 1500 extensions: nodes: - kind: OrganizationProvider relationships: - name: circuits peer: DcimCircuit cardinality: many optional: true - kind: LocationHosting relationships: - name: circuits peer: DcimCircuit cardinality: many optional: true ``` --- # Circuit Contract This schema extension provides models for managing Circuit Contracts, enabling structured representation of service agreements with network providers. Compatible with Infrahub 1.8. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) * [extensions/circuit](/schema-library/reference/circuit.md) ## Nodes[​](#nodes "Direct link to Nodes") ### CircuitContract[​](#circuitcontract "Direct link to CircuitContract") * **Label:** Circuit Contract * **Description:** A contract document associated with a circuit service, including terms, costs, and validity period. * **Namespace:** Dcim * **Icon:** mdi :file-sign * **Display Label:** Contract (Circuit `{{ circuit__circuit_id__value }}`) * **Human Friendly ID:** name\_\_value * **Inherit From:** CoreFileObject #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | --------------- | ----------- | -------- | -------- | -------------- | ------------- | | name | | Text | False | | | | contract\_start | | DateTime | False | | | | contract\_end | | DateTime | False | | | | monthly\_cost | | Number | True | | | | currency | | Dropdown | True | | usd, eur, gbp | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------- | ----------- | -------- | ----------- | --------- | | circuit | DcimCircuit | False | one | Attribute | ## Extensions[​](#extensions "Direct link to Extensions") note In this context "extensions" refer to modifications or additions to the existing schema, such as adding new attributes, relationships, or other schema elements. ### DcimCircuit[​](#dcimcircuit "Direct link to DcimCircuit") #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | -------- | ------------------- | -------- | ----------- | ---- | | contract | DcimCircuitContract | True | one | | ## Code[​](#code "Direct link to Code") ``` version: '1.0' nodes: - name: CircuitContract namespace: Dcim inherit_from: - CoreFileObject description: A contract document associated with a circuit service, including terms, costs, and validity period. label: Circuit Contract icon: mdi:file-sign menu_placement: DcimCircuit human_friendly_id: - name__value order_by: - contract_end__value display_label: Contract (Circuit `{{ circuit__circuit_id__value }}`) attributes: - name: name kind: Text computed_attribute: kind: Jinja2 jinja2_template: contract_{{ circuit__circuit_id__value }} read_only: true optional: false - name: contract_start kind: DateTime optional: false - name: contract_end kind: DateTime optional: false - name: monthly_cost kind: Number optional: true - name: currency kind: Dropdown optional: true choices: - name: usd label: USD description: United States Dollar - name: eur label: EUR description: Euro - name: gbp label: GBP description: British Pound Sterling relationships: - name: circuit peer: DcimCircuit kind: Attribute cardinality: one optional: false order_weight: 950 extensions: nodes: - kind: DcimCircuit relationships: - name: contract peer: DcimCircuitContract cardinality: one optional: true ``` --- # Circuit Service This schema extension contains model coming on top of circuit to capture a single service shared across multiple circuits. For example you have a MPLS network supported by a provider connecting multiple locations: * One single CircuitService would be needed to store MPLS related information (e.g. service id, provider ...) * On each site we would create a circuit connecting on one side our device and the CircuitService on the other side ## Details[​](#details "Direct link to Details") * **Dependencies:** * [extensions/circuit](/schema-library/reference/circuit.md) ## Nodes[​](#nodes "Direct link to Nodes") ### Service[​](#service "Direct link to Service") * **Label:** Circuit Service * **Description:** Represent the boundary of a provider network, the details of which are unknown or unimportant * **Namespace:** Circuit * **Icon:** mdi :cloud * **Human Friendly ID:** name\_\_value #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------- | ----------- | ---- | -------- | -------------- | ------- | | name | | Text | | | | | service\_id | | Text | True | | | | description | | Text | True | | | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------------ | -------------------- | -------- | ----------- | --------- | | provider | OrganizationProvider | False | one | Attribute | | circuit\_endpoints | CircuitEndpoint | True | many | Component | ## Extensions[​](#extensions "Direct link to Extensions") note In this context "extensions" refer to modifications or additions to the existing schema, such as adding new attributes, relationships, or other schema elements. ### OrganizationProvider[​](#organizationprovider "Direct link to OrganizationProvider") #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ----------------- | -------------- | -------- | ----------- | ---- | | circuit\_services | CircuitService | True | many | | ### CircuitEndpoint[​](#circuitendpoint "Direct link to CircuitEndpoint") #### Relationships[​](#relationships-2 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ---------------- | -------------- | -------- | ----------- | ---- | | circuit\_service | CircuitService | True | one | | ## Code[​](#code "Direct link to Code") ``` version: '1.0' nodes: - name: Service namespace: Circuit description: Represent the boundary of a provider network, the details of which are unknown or unimportant label: Circuit Service icon: mdi:cloud menu_placement: DcimCircuit human_friendly_id: - name__value order_by: - name__value display_label: name__value attributes: - name: name kind: Text unique: true - name: service_id kind: Text optional: true - name: description kind: Text optional: true relationships: - name: provider peer: OrganizationProvider optional: false cardinality: one kind: Attribute - name: circuit_endpoints peer: CircuitEndpoint optional: true cardinality: many kind: Component extensions: nodes: - kind: OrganizationProvider relationships: - name: circuit_services peer: CircuitService cardinality: many optional: true - kind: CircuitEndpoint relationships: - name: circuit_service peer: CircuitService cardinality: one optional: true ``` --- # Cluster This schema extension contains the foundations to capture clusters. With this one in place you can unlock various clusters flavors (hosting cluster able to host VMs, firewall clusters built with specific appliances ...) ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) * [extensions/compute](/schema-library/reference/compute.md) ## Generics[​](#generics "Direct link to Generics") ### Generic[​](#generic "Direct link to Generic") * **Label:** Clusters * **Description:** A cluster of machines hosting services or other machines. * **Namespace:** Cluster * **Icon:** mdi :dots-hexagon * **Human Friendly ID:** name\_\_value #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------- | -------------------- | ---- | -------- | -------------- | ------- | | name | Name of the cluster. | Text | | | | | description | | Text | True | | | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | -------- | --------------- | -------- | ----------- | --------- | | location | LocationGeneric | False | many | Attribute | | tags | BuiltinTag | True | many | Attribute | ### GenericComputeUnitNodes[​](#genericcomputeunitnodes "Direct link to GenericComputeUnitNodes") * **Description:** A generic to apply on clusters that can be built out of generic compute units. * **Namespace:** Cluster #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ----- | ------------------ | -------- | ----------- | --------- | | nodes | ComputeGenericUnit | | many | Component | ## Extensions[​](#extensions "Direct link to Extensions") note In this context "extensions" refer to modifications or additions to the existing schema, such as adding new attributes, relationships, or other schema elements. ### ComputeGenericUnit[​](#computegenericunit "Direct link to ComputeGenericUnit") #### Relationships[​](#relationships-2 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------------- | ------------------------------ | -------- | ----------- | ---- | | worker\_in\_cluster | ClusterGenericComputeUnitNodes | True | one | | ### LocationGeneric[​](#locationgeneric "Direct link to LocationGeneric") #### Relationships[​](#relationships-3 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | -------- | -------------- | -------- | ----------- | --------- | | clusters | ClusterGeneric | True | many | Component | ## Code[​](#code "Direct link to Code") ``` version: '1.0' generics: - name: Generic namespace: Cluster description: A cluster of machines hosting services or other machines. label: Clusters icon: mdi:dots-hexagon human_friendly_id: - name__value order_by: - name__value display_label: name__value attributes: - name: name kind: Text description: Name of the cluster. unique: true order_weight: 1000 - name: description kind: Text optional: true relationships: - name: location label: Location peer: LocationGeneric optional: false cardinality: many kind: Attribute order_weight: 1400 - name: tags peer: BuiltinTag optional: true cardinality: many kind: Attribute order_weight: 2000 - name: GenericComputeUnitNodes namespace: Cluster description: A generic to apply on clusters that can be built out of generic compute units. include_in_menu: false relationships: - name: nodes label: Nodes identifier: worker_in_cluster cardinality: many peer: ComputeGenericUnit kind: Component extensions: nodes: - kind: ComputeGenericUnit relationships: - name: worker_in_cluster identifier: worker_in_cluster label: Worker in cluster peer: ClusterGenericComputeUnitNodes cardinality: one description: This device is a worker node of the specified cluster. optional: true - kind: LocationGeneric relationships: - name: clusters label: Clusters peer: ClusterGeneric cardinality: many kind: Component description: All clusters available on that location. optional: true ``` --- # Compute With this schema extension in place you will be able to capture all your physical servers. It also gives you the baseline to build virtualization. You might consider HostingCluster extension to go with! ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) ## Nodes[​](#nodes "Direct link to Nodes") ### PhysicalServer[​](#physicalserver "Direct link to PhysicalServer") * **Label:** Physical Server * **Description:** A physical server with fixed resources and specific hardware characteristics. * **Namespace:** Compute * **Icon:** mdi :server * **Inherit From:** ComputeGenericUnit, DcimGenericDevice, DcimPhysicalDevice, VirtualizationHostVirtualMachine #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ------ | ----------- | -------- | -------- | -------------- | ------------------------------------------ | | status | | Dropdown | False | | active, provisioning, maintenance, drained | ### VirtualMachine[​](#virtualmachine "Direct link to VirtualMachine") * **Label:** Virtual Machine * **Description:** A virtual machine hosted on a server or a cluster. * **Namespace:** Virtualization * **Icon:** carbon :virtual-machine * **Inherit From:** ComputeGenericUnit, DcimGenericDevice #### Attributes[​](#attributes-1 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ------ | -------------------------------------------- | -------- | -------- | -------------- | -------------------- | | role | Role of the virtual machine. | Dropdown | True | | application, storage | | vcpu | Number of CPU cores assigned to the VM. | Number | True | | | | memory | Amount of memory (in GB) assigned to the VM. | Number | True | | | | disk | Disk space (in GB) assigned to the VM. | Number | True | | | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ---- | -------------------------------- | -------- | ----------- | --------- | | host | VirtualizationHostVirtualMachine | False | one | Attribute | ## Generics[​](#generics "Direct link to Generics") ### GenericUnit[​](#genericunit "Direct link to GenericUnit") * **Description:** A generic unit that can compute (e.g. server, vm ...). * **Namespace:** Compute ### HostVirtualMachine[​](#hostvirtualmachine "Direct link to HostVirtualMachine") * **Description:** A generic unit that can host VM * **Namespace:** Virtualization #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ----------------- | ---------------------------- | -------- | ----------- | --------- | | virtual\_machines | VirtualizationVirtualMachine | True | many | Component | ## Code[​](#code "Direct link to Code") ``` version: '1.0' generics: - name: GenericUnit namespace: Compute description: A generic unit that can compute (e.g. server, vm ...). include_in_menu: false - name: HostVirtualMachine namespace: Virtualization description: A generic unit that can host VM include_in_menu: false relationships: - name: virtual_machines cardinality: many peer: VirtualizationVirtualMachine kind: Component optional: true nodes: - name: PhysicalServer namespace: Compute description: A physical server with fixed resources and specific hardware characteristics. label: Physical Server icon: mdi:server inherit_from: - ComputeGenericUnit - DcimGenericDevice - DcimPhysicalDevice - VirtualizationHostVirtualMachine attributes: - name: status kind: Dropdown optional: false order_weight: 1100 choices: - name: active label: Active description: Fully operational and currently in service. color: '#7fbf7f' - name: provisioning label: Provisioning description: In the process of being set up and configured. color: '#ffff7f' - name: maintenance label: Maintenance description: Undergoing routine maintenance or repairs. color: '#ffd27f' - name: drained label: Drained description: Temporarily taken out of service. color: '#bfbfbf' - name: VirtualMachine namespace: Virtualization description: A virtual machine hosted on a server or a cluster. label: Virtual Machine icon: carbon:virtual-machine inherit_from: - ComputeGenericUnit - DcimGenericDevice attributes: - name: role kind: Dropdown optional: true description: Role of the virtual machine. order_weight: 1400 choices: - name: application color: '#7f7fff' - name: storage color: '#bf7fbf' - name: vcpu kind: Number optional: true order_weight: 1900 description: Number of CPU cores assigned to the VM. - name: memory kind: Number optional: true order_weight: 1900 description: Amount of memory (in GB) assigned to the VM. - name: disk kind: Number optional: true order_weight: 1900 description: Disk space (in GB) assigned to the VM. relationships: - name: host peer: VirtualizationHostVirtualMachine optional: false cardinality: one kind: Attribute order_weight: 1500 ``` --- # Cross Connect This extension contains schema to capture Cross Connect. You can see it as "a cable operated by a provider". You will be able to attach it to a location and then connect endpoints to it (e.g. rear interface of a patch panel, circuit endpoint ...) ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) ## Nodes[​](#nodes "Direct link to Nodes") ### CrossConnect[​](#crossconnect "Direct link to CrossConnect") * **Label:** Cross-Connect * **Description:** Cross-connect between different endpoints within a datacenter. * **Namespace:** Dcim * **Icon:** streamline :arrow-crossover-right-solid * **Uniqueness Constraints:** * identifier\_\_value, provider * **Human Friendly ID:** provider\_\_name\_\_value, identifier\_\_value * **Inherit From:** DcimConnector #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------- | ----------- | -------- | -------- | -------------- | ---------------------------- | | identifier | | Text | | | | | description | | Text | True | | | | status | | Dropdown | True | | connected, planned, reserved | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | -------- | -------------------- | -------- | ----------- | --------- | | location | LocationHosting | False | one | Attribute | | provider | OrganizationProvider | False | one | Attribute | ## Code[​](#code "Direct link to Code") ``` version: '1.0' nodes: - name: CrossConnect namespace: Dcim description: Cross-connect between different endpoints within a datacenter. label: Cross-Connect icon: streamline:arrow-crossover-right-solid inherit_from: - DcimConnector order_by: - provider__name__value display_label: '{{ identifier__value }} (Provider: {{ provider__name__value }})' human_friendly_id: - provider__name__value - identifier__value uniqueness_constraints: - - identifier__value - provider attributes: - name: identifier kind: Text order_weight: 900 - name: description kind: Text optional: true order_weight: 1300 - name: status kind: Dropdown optional: true order_weight: 1200 choices: - name: connected label: Connected description: Fully operational and currently in connected color: '#7fbf7f' - name: planned label: Planned description: In the process of being set up color: '#ffff7f' - name: reserved label: Reserved description: Fully connected and reserved for a future use color: '#9090de' relationships: - name: location label: Location peer: LocationHosting optional: false cardinality: one kind: Attribute order_weight: 1100 - name: provider peer: OrganizationProvider optional: false cardinality: one kind: Attribute order_weight: 1000 ``` --- # DCIM Basic DCIM schema to capture devices, racks, interfaces, and related information. ## Details[​](#details "Direct link to Details") * **Dependencies:** No dependencies ## Nodes[​](#nodes "Direct link to Nodes") ### DeviceType[​](#devicetype "Direct link to DeviceType") * **Label:** Device Type * **Description:** A model of device * **Namespace:** Dcim * **Icon:** mdi :poll * **Uniqueness Constraints:** * manufacturer, name\_\_value * **Human Friendly ID:** name\_\_value #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ------------ | ----------- | ------- | -------- | -------------- | ------- | | name | | Text | | | | | description | | Text | True | | | | part\_number | | Text | True | | | | height | | Number | False | 1 | | | full\_depth | | Boolean | | True | | | weight | | Number | True | | | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------ | ------------------------ | -------- | ----------- | --------- | | platform | DcimPlatform | | one | Attribute | | manufacturer | OrganizationManufacturer | False | one | Attribute | | tags | BuiltinTag | True | many | Attribute | ### Platform[​](#platform "Direct link to Platform") * **Label:** Platform * **Description:** A Platform represent the type of software running on a device. * **Namespace:** Dcim * **Icon:** mdi :application-cog-outline * **Uniqueness Constraints:** * name\_\_value * **Human Friendly ID:** name\_\_value #### Attributes[​](#attributes-1 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | --------------------- | ----------- | ---- | -------- | -------------- | ------- | | name | | Text | | | | | description | | Text | True | | | | nornir\_platform | | Text | True | | | | napalm\_driver | | Text | True | | | | netmiko\_device\_type | | Text | True | | | | ansible\_network\_os | | Text | True | | | | containerlab\_os | | Text | True | | | #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------ | ------------------------ | -------- | ----------- | --------- | | devices | DcimGenericDevice | True | many | | | manufacturer | OrganizationManufacturer | | one | Attribute | ### Device[​](#device "Direct link to Device") * **Label:** Network Device * **Description:** A configurable network device for managing and directing data traffic, including routers, switches... * **Namespace:** Dcim * **Icon:** clarity :network-switch-solid * **Inherit From:** CoreArtifactTarget, DcimGenericDevice, DcimPhysicalDevice #### Attributes[​](#attributes-2 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ------ | ----------- | -------- | -------- | -------------- | ------------------------------------------ | | status | | Dropdown | False | | active, provisioning, maintenance, drained | | role | | Dropdown | True | | core, edge, cpe, spine, leaf, tor | ### Physical[​](#physical "Direct link to Physical") * **Label:** Physical Interface * **Description:** Physical network port on a device * **Namespace:** Interface * **Inherit From:** DcimInterface, InterfaceLayer2, InterfaceLayer3, DcimEndpoint, InterfaceHasSubInterface ### Virtual[​](#virtual "Direct link to Virtual") * **Label:** Virtual Interface * **Description:** Virtual interface like VLAN or Loopback * **Namespace:** Interface * **Inherit From:** DcimInterface, InterfaceLayer2, InterfaceLayer3 #### Relationships[​](#relationships-2 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ----------------- | ------------------------ | -------- | ----------- | --------- | | parent\_interface | InterfaceHasSubInterface | | one | Attribute | ## Generics[​](#generics "Direct link to Generics") ### GenericDevice[​](#genericdevice "Direct link to GenericDevice") * **Label:** Device * **Description:** Generic Device object. * **Namespace:** Dcim * **Icon:** mdi :server * **Human Friendly ID:** name\_\_value #### Attributes[​](#attributes-3 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------- | ----------- | ---- | -------- | -------------- | ------- | | name | | Text | | | | | description | | Text | True | | | | os\_version | | Text | True | | | #### Relationships[​](#relationships-3 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ---------------- | ------------- | -------- | ----------- | --------- | | interfaces | DcimInterface | True | many | Component | | tags | BuiltinTag | True | many | Attribute | | primary\_address | IpamIPAddress | True | one | Attribute | | platform | DcimPlatform | True | one | Attribute | ### PhysicalDevice[​](#physicaldevice "Direct link to PhysicalDevice") * **Description:** Generic holding attributes and relationships relevant for physical device. * **Namespace:** Dcim #### Attributes[​](#attributes-4 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ---------- | ------------------------------------------------ | -------- | -------- | -------------- | ----------- | | position | Lowest unit. | Number | True | | | | serial | | Text | True | | | | rack\_face | On which face of the rack the device is mounted. | Dropdown | False | front | front, rear | #### Relationships[​](#relationships-4 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------ | --------------- | -------- | ----------- | --------- | | device\_type | DcimDeviceType | True | one | Attribute | | location | LocationHosting | False | one | Attribute | ### Endpoint[​](#endpoint "Direct link to Endpoint") * **Description:** Generic Endpoint to receive a connector. * **Namespace:** Dcim #### Relationships[​](#relationships-5 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | --------- | ------------- | -------- | ----------- | --------- | | connector | DcimConnector | True | one | Attribute | ### Connector[​](#connector "Direct link to Connector") * **Description:** Generic Connector to link two endpoints together. * **Namespace:** Dcim #### Relationships[​](#relationships-6 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | -------------------- | ------------ | -------- | ----------- | ------- | | connected\_endpoints | DcimEndpoint | True | many | Generic | ### Interface[​](#interface "Direct link to Interface") * **Label:** Interface * **Description:** Generic Network Interface * **Namespace:** Dcim * **Uniqueness Constraints:** * device, name\_\_value * **Human Friendly ID:** device\_\_name\_\_value, name\_\_value #### Attributes[​](#attributes-5 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------- | ---------------------------------------- | -------- | -------- | -------------- | ------------------------------------------------------------------ | | name | Name of the interface | Text | | | | | description | A brief description of the interface | Text | True | | | | mtu | | Number | | 1514 | | | status | The status of the interface | Dropdown | | active | provisioning, free, active, maintenance, disabled, deleted, outage | | role | The role of the interface in the network | Dropdown | True | | lag, core, cust, access, management, peering, upstream | #### Relationships[​](#relationships-7 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------ | ----------------- | -------- | ----------- | --------- | | device | DcimGenericDevice | False | one | Parent | | tags | BuiltinTag | True | many | Attribute | ### Layer2[​](#layer2 "Direct link to Layer2") * **Label:** Layer 2 Interface * **Description:** Layer 2 specific attributes for network interfaces * **Namespace:** Interface #### Attributes[​](#attributes-6 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | -------- | ----------------------------- | -------- | -------- | -------------- | ------------------------- | | l2\_mode | Layer 2 mode of the interface | Dropdown | True | | access, trunk, trunk\_all | ### Layer3[​](#layer3 "Direct link to Layer3") * **Label:** Layer 3 Interface * **Description:** Layer 3 specific attributes for network interfaces * **Namespace:** Interface #### Attributes[​](#attributes-7 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ------------ | ------------- | ------ | -------- | -------------- | ------- | | dot1q\_id | Dot1Q VLAN ID | Number | True | | | | mac\_address | | Text | True | | | #### Relationships[​](#relationships-8 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------- | ------------- | -------- | ----------- | --------- | | ip\_addresses | IpamIPAddress | True | many | Attribute | ### HasSubInterface[​](#hassubinterface "Direct link to HasSubInterface") * **Description:** A generic interface that can have sub-interfaces * **Namespace:** Interface #### Relationships[​](#relationships-9 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | --------------- | ---------------- | -------- | ----------- | --------- | | sub\_interfaces | InterfaceVirtual | True | many | Attribute | ## Code[​](#code "Direct link to Code") ``` version: '1.0' generics: - name: GenericDevice namespace: Dcim description: Generic Device object. label: Device icon: mdi:server human_friendly_id: - name__value order_by: - name__value display_label: name__value attributes: - name: name kind: Text unique: true order_weight: 1000 - name: description kind: Text optional: true order_weight: 2000 - name: os_version kind: Text optional: true order_weight: 2200 relationships: - name: interfaces peer: DcimInterface optional: true cardinality: many identifier: device__interface kind: Component - name: tags peer: BuiltinTag optional: true cardinality: many kind: Attribute order_weight: 2000 - name: primary_address peer: IpamIPAddress label: Primary IP Address optional: true cardinality: one kind: Attribute order_weight: 1700 - name: platform peer: DcimPlatform optional: true cardinality: one kind: Attribute order_weight: 1250 - name: PhysicalDevice namespace: Dcim description: Generic holding attributes and relationships relevant for physical device. include_in_menu: false attributes: - name: position label: Position (U) description: Lowest unit. kind: Number optional: true order_weight: 1500 - name: serial kind: Text optional: true order_weight: 1500 - name: rack_face label: Rack Face description: On which face of the rack the device is mounted. kind: Dropdown optional: false default_value: front order_weight: 1515 choices: - name: front label: Front description: Device mounted on the front face of the rack. - name: rear label: Rear description: Device mounted on the rear face of the rack. relationships: - name: device_type peer: DcimDeviceType optional: true cardinality: one kind: Attribute order_weight: 1200 - name: location label: Location peer: LocationHosting optional: false cardinality: one kind: Attribute order_weight: 1500 - name: Endpoint namespace: Dcim description: Generic Endpoint to receive a connector. include_in_menu: false relationships: - name: connector peer: DcimConnector optional: true cardinality: one order_weight: 1500 kind: Attribute - name: Connector namespace: Dcim description: Generic Connector to link two endpoints together. include_in_menu: false relationships: - name: connected_endpoints peer: DcimEndpoint optional: true cardinality: many order_weight: 1500 kind: Generic - name: Interface namespace: Dcim description: Generic Network Interface label: Interface include_in_menu: false display_label: name__value order_by: - device__name__value - name__value uniqueness_constraints: - - device - name__value human_friendly_id: - device__name__value - name__value attributes: - name: name kind: Text description: Name of the interface order_weight: 1000 - name: description kind: Text optional: true description: A brief description of the interface order_weight: 1100 - name: mtu label: MTU kind: Number default_value: 1514 order_weight: 1300 - name: status kind: Dropdown description: The status of the interface choices: - name: provisioning label: Provisioning description: Interface is being provisioned. color: '#A9DFBF' - name: free label: Free description: Interface is unused. color: '#CDEACC' - name: active label: Active description: Interface is active and operational. color: '#A9CCE3' - name: maintenance label: Maintenance description: Interface is under maintenance. color: '#FFF2CC' - name: disabled label: Disabled description: Interface has been disabled. color: '#D3D3D3' - name: deleted label: Deleted description: Interface has been deleted. color: '#FAD7A0' - name: outage label: Outage description: Interface is currently experiencing an outage. color: '#F4CCCC' default_value: active order_weight: 1200 - name: role kind: Dropdown optional: true description: The role of the interface in the network choices: - name: lag label: Lag description: Interface LAG. color: '#A9DFBF' - name: core label: Core Interface description: Central part of the network. color: '#A9CCE3' - name: cust label: Customer Interface description: Interface dedicated to customer connections. color: '#D2B4DE' - name: access label: Access Interfaces description: Interface connecting endpoint devices. color: '#B4E0DC' - name: management label: Management Interface description: Interface dedicated to device management. color: '#E3DAC9' - name: peering label: Peering Interface description: Interface dedicated to peering with other networks. color: '#C4B7E6' - name: upstream label: Upstream Interface description: Interface dedicated to upstream traffic between networks. color: '#B2D4E6' order_weight: 1250 relationships: - name: device peer: DcimGenericDevice identifier: device__interface optional: false cardinality: one kind: Parent order_weight: 1025 - name: tags peer: BuiltinTag optional: true cardinality: many kind: Attribute order_weight: 3000 - name: Layer2 namespace: Interface include_in_menu: false description: Layer 2 specific attributes for network interfaces label: Layer 2 Interface attributes: - name: l2_mode label: Layer2 Mode kind: Dropdown optional: true choices: - name: access label: Access description: Access mode - name: trunk label: Trunk description: Trunk mode - name: trunk_all label: Trunk (All) description: Trunk all mode description: Layer 2 mode of the interface order_weight: 1500 - name: Layer3 namespace: Interface include_in_menu: false description: Layer 3 specific attributes for network interfaces label: Layer 3 Interface attributes: - name: dot1q_id label: VLAN ID (dot1q) kind: Number description: Dot1Q VLAN ID order_weight: 1600 optional: true - name: mac_address label: Mac Address kind: Text optional: true order_weight: 1550 relationships: - name: ip_addresses label: IP Addresses peer: IpamIPAddress cardinality: many kind: Attribute optional: true description: List of IP addresses associated with the interface order_weight: 1150 - name: HasSubInterface namespace: Interface description: A generic interface that can have sub-interfaces include_in_menu: false relationships: - name: sub_interfaces label: Sub-interface(s) peer: InterfaceVirtual identifier: sub__interface optional: true cardinality: many kind: Attribute description: Sub-interfaces of this interface order_weight: 1750 nodes: - name: DeviceType namespace: Dcim description: A model of device label: Device Type icon: mdi:poll human_friendly_id: - name__value display_label: name__value order_by: - manufacturer__name__value - name__value uniqueness_constraints: - - manufacturer - name__value attributes: - name: name kind: Text unique: true order_weight: 1000 - name: description kind: Text optional: true order_weight: 1100 - name: part_number label: Part Number optional: true kind: Text order_weight: 1200 - name: height label: Height (U) optional: false default_value: 1 kind: Number order_weight: 1400 - name: full_depth label: Full Depth default_value: true kind: Boolean order_weight: 1500 - name: weight label: Weight (kg) optional: true kind: Number order_weight: 1600 relationships: - name: platform peer: DcimPlatform cardinality: one kind: Attribute order_weight: 1300 - name: manufacturer peer: OrganizationManufacturer cardinality: one kind: Attribute order_weight: 1250 optional: false - name: tags peer: BuiltinTag optional: true cardinality: many kind: Attribute order_weight: 2000 - name: Platform namespace: Dcim description: A Platform represent the type of software running on a device. label: Platform icon: mdi:application-cog-outline human_friendly_id: - name__value display_label: name__value order_by: - manufacturer__name__value - name__value uniqueness_constraints: - - name__value attributes: - name: name kind: Text unique: true order_weight: 1000 - name: description kind: Text optional: true order_weight: 1200 - name: nornir_platform kind: Text optional: true order_weight: 1500 - name: napalm_driver kind: Text optional: true order_weight: 1600 - name: netmiko_device_type kind: Text optional: true order_weight: 1700 - name: ansible_network_os kind: Text optional: true order_weight: 1800 - name: containerlab_os kind: Text optional: true order_weight: 1900 relationships: - name: devices peer: DcimGenericDevice optional: true cardinality: many order_weight: 1350 - name: manufacturer peer: OrganizationManufacturer cardinality: one kind: Attribute order_weight: 1300 - name: Device label: Network Device description: A configurable network device for managing and directing data traffic, including routers, switches... icon: clarity:network-switch-solid namespace: Dcim inherit_from: - CoreArtifactTarget - DcimGenericDevice - DcimPhysicalDevice attributes: - name: status kind: Dropdown optional: false order_weight: 1100 choices: - name: active label: Active description: Fully operational and currently in service. color: '#7fbf7f' - name: provisioning label: Provisioning description: In the process of being set up and configured. color: '#ffff7f' - name: maintenance label: Maintenance description: Undergoing routine maintenance or repairs. color: '#ffd27f' - name: drained label: Drained description: Temporarily taken out of service. color: '#bfbfbf' - name: role kind: Dropdown optional: true order_weight: 1400 choices: - name: core label: Core Router description: Central part of the network. color: '#7f7fff' - name: edge label: Edge Router description: Network boundary with external networks. color: '#bf7fbf' - name: cpe label: Customer Premise Equipment description: Devices located at the customer's premises. color: '#bf7f7f' - name: spine label: Spine Router description: Aggregation router part of a Fabric. color: '#aeeeee' - name: leaf label: Leaf Switch description: Top of Rack part of a Fabric. color: '#e6e6fa' - name: tor label: Tor Switch description: Tor switch part of a Fabric. color: '#e8e7fd' - name: Physical namespace: Interface label: Physical Interface description: Physical network port on a device inherit_from: - DcimInterface - InterfaceLayer2 - InterfaceLayer3 - DcimEndpoint - InterfaceHasSubInterface include_in_menu: false - name: Virtual namespace: Interface label: Virtual Interface description: Virtual interface like VLAN or Loopback inherit_from: - DcimInterface - InterfaceLayer2 - InterfaceLayer3 include_in_menu: false relationships: - name: parent_interface peer: InterfaceHasSubInterface cardinality: one kind: Attribute identifier: sub__interface description: Parent interface to which this sub-interface belongs ``` --- # DWDM This schema extension contains models for OADM (Optical Add Drop Multiplexer) supporting various WDM (Wavelength Division Multiplexing) technologies such as DWDM (Dense Wavelength Division Multiplexing) or CWDM (Coarse Wavelength Division Multiplexing). With some vendors, the tunable optics are not configured via the channel number but via the wavelength and/or the frequency. This model provides a unique entry in Infrahub for those. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) * [extensions/sfp](/schema-library/reference/sfp.md) ## Nodes[​](#nodes "Direct link to Nodes") ### OpticalMultiplexer[​](#opticalmultiplexer "Direct link to OpticalMultiplexer") * **Label:** Optical Multiplexer * **Description:** An OADM (Optical Add Drop Multiplexer) supporting various WDM (Wavelength Division Multiplexing) technologies. * **Namespace:** Dcim * **Icon:** mdi :transit-connection-variant * **Human Friendly ID:** name\_\_value * **Inherit From:** DcimPhysicalDevice #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------- | ---------------------------- | -------- | -------- | -------------- | ---------- | | name | | Text | | | | | wdm\_type | Type of WDM (e.g CWDM. DWDM) | Dropdown | False | dwdm | cwdm, dwdm | | description | | Text | True | | | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ----------------- | ---------------------- | -------- | ----------- | --------- | | front\_interfaces | DcimOadmFrontInterface | True | many | Component | | rear\_interface | DcimOadmRearInterface | True | one | Component | ### OadmFrontInterface[​](#oadmfrontinterface "Direct link to OadmFrontInterface") * **Label:** Optical Multiplexer Front Interfaces * **Namespace:** Dcim * **Uniqueness Constraints:** * optical\_multiplexer, name\_\_value * **Human Friendly ID:** optical\_multiplexer\_\_name\_\_value, name\_\_value * **Inherit From:** DcimEndpoint, DcimGenericOadmInterface #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | -------------------- | ---------------------- | -------- | ----------- | --------- | | optical\_multiplexer | DcimOpticalMultiplexer | False | one | Parent | | channels | DcimWdmChannel | True | many | Attribute | ### OadmRearInterface[​](#oadmrearinterface "Direct link to OadmRearInterface") * **Label:** Optical Multiplexer Rear Interfaces * **Namespace:** Dcim * **Uniqueness Constraints:** * optical\_multiplexer, name\_\_value * **Human Friendly ID:** optical\_multiplexer\_\_name\_\_value, name\_\_value * **Inherit From:** DcimEndpoint, DcimGenericOadmInterface #### Relationships[​](#relationships-2 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | -------------------- | ---------------------- | -------- | ----------- | ------ | | optical\_multiplexer | DcimOpticalMultiplexer | False | one | Parent | ### WdmChannel[​](#wdmchannel "Direct link to WdmChannel") * **Label:** WDM Channel * **Description:** A WDM channel with its wavelength and frequency. * **Namespace:** Dcim * **Icon:** game-icons :laser-warning * **Uniqueness Constraints:** * frequency\_\_value, wavelength\_\_value, channel\_\_value, wdm\_type\_\_value * channel\_\_value, wdm\_type\_\_value * **Human Friendly ID:** wdm\_type\_\_value, channel\_\_value #### Attributes[​](#attributes-1 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ---------- | -------------------------------- | -------- | -------- | -------------- | ---------- | | channel | WDM channel number. | Number | | | | | wdm\_type | Type of WDM (e.g CWDM. DWDM) | Dropdown | False | dwdm | cwdm, dwdm | | wavelength | Wavelength of the channel in nm. | Text | | | | | frequency | Frequency of the channel in GHz. | Text | | | | ### WdmSFP[​](#wdmsfp "Direct link to WdmSFP") * **Label:** WDM SFP * **Description:** SFP with configuration for Wavelength Division Multiplexing. * **Namespace:** Dcim * **Icon:** mdi :laser-pointer * **Inherit From:** DcimGenericSFP #### Attributes[​](#attributes-2 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | --------- | ---------------------------- | -------- | -------- | -------------- | ---------- | | wdm\_type | Type of WDM (e.g CWDM. DWDM) | Dropdown | False | dwdm | cwdm, dwdm | #### Relationships[​](#relationships-3 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------ | -------------- | -------- | ----------- | --------- | | wdm\_channel | DcimWdmChannel | False | one | Attribute | ## Generics[​](#generics "Direct link to Generics") ### GenericOadmInterface[​](#genericoadminterface "Direct link to GenericOadmInterface") * **Label:** Optical Multiplexer Interfaces * **Namespace:** Dcim * **Icon:** mdi :ethernet #### Attributes[​](#attributes-3 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | --------------- | ----------- | -------- | -------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | name | | Text | | | | | description | | Text | True | | | | connector\_type | | Dropdown | | | FC, LC, LC\_PC, LC\_UPC, LC\_APC, LSH, LSH\_PC, LSH\_UPC, LSH\_APC, LX\_5, LX\_5\_PC, LX\_5\_UPC, LX\_5\_APC, MPO, MTRJ, SC, SC\_PC, SC\_UPC, SC\_APC, ST, CS, SN, SMA\_905, SMA\_906, URM\_P2 | ## Code[​](#code "Direct link to Code") ``` version: '1.0' generics: - name: GenericOadmInterface label: Optical Multiplexer Interfaces menu_placement: DcimOpticalMultiplexer namespace: Dcim icon: mdi:ethernet attributes: - name: name kind: Text order_weight: 1000 - name: description kind: Text optional: true order_weight: 2000 - name: connector_type kind: Dropdown order_weight: 1100 choices: - name: FC label: FC description: Standardized fiber optic connector used primarily in datacom and telecom applications. - name: LC label: LC description: Compact fiber optic connector with a push-pull mechanism. - name: LC_PC label: LC/PC description: Polished LC connector providing physical contact (PC). - name: LC_UPC label: LC/UPC description: Ultra-Physical Contact (UPC) variant of the LC connector with enhanced polish. - name: LC_APC label: LC/APC description: Angled Physical Contact (APC) version of the LC connector with a slanted fiber end-face. - name: LSH label: LSH description: European fiber optic connector offering high durability. - name: LSH_PC label: LSH/PC description: Physical Contact version of LSH with standard polish. - name: LSH_UPC label: LSH/UPC description: Ultra-Physical Contact variant of LSH, minimizing return loss with a superior polish. - name: LSH_APC label: LSH/APC description: Angled Physical Contact version of LSH, designed to reduce back reflections. - name: LX_5 label: LX.5 description: Miniaturized fiber optic connector similar to LC but with an additional shutter mechanism. - name: LX_5_PC label: LX.5/PC description: Physical Contact version of LX.5. - name: LX_5_UPC label: LX.5/UPC description: Ultra-Physical Contact variant of LX.5. - name: LX_5_APC label: LX.5/APC description: Angled Physical Contact version of LX.5. - name: MPO label: MPO description: Multi-fiber Push-On connector typically used in data centers for high-speed applications. - name: MTRJ label: MTRJ description: Male-to-female fiber optic connector with two fibers. - name: SC label: SC description: Square fiber optic connector with push-pull lock. - name: SC_PC label: SC/PC description: Physical Contact SC connector with a polished end-face. - name: SC_UPC label: SC/UPC description: Ultra-Physical Contact variant of SC. - name: SC_APC label: SC/APC description: Angled Physical Contact version of SC. - name: ST label: ST description: Bayonet-style fiber optic connector primarily used in industrial and military applications. - name: CS label: CS description: Compact connector with a high-density duplex configuration. - name: SN label: SN description: Small-form connector with dual fibers. - name: SMA_905 label: SMA 905 description: Stainless steel fiber optic connector. - name: SMA_906 label: SMA 906 description: Variant of SMA 905 with similar durability, frequently used in high-vibration settings. - name: URM_P2 label: URM-P2 description: Specialized fiber optic connector for industrial and harsh environments. nodes: - name: OpticalMultiplexer label: Optical Multiplexer description: An OADM (Optical Add Drop Multiplexer) supporting various WDM (Wavelength Division Multiplexing) technologies. icon: mdi:transit-connection-variant namespace: Dcim human_friendly_id: - name__value order_by: - name__value display_label: name__value inherit_from: - DcimPhysicalDevice attributes: - name: name kind: Text unique: true order_weight: 1000 - name: wdm_type kind: Dropdown description: Type of WDM (e.g CWDM. DWDM) order_weight: 1110 optional: false choices: - name: cwdm label: CWDM (Coarse Wavelength Division Multiplexing) description: Supports multiple wavelengths for communication up to 70km. color: '#0099cc' - name: dwdm label: DWDM (Dense Wavelength Division Multiplexing) description: Supports dense wavelengths and amplification for long-distance communication. color: '#9933cc' default_value: dwdm - name: description kind: Text optional: true order_weight: 2000 relationships: - name: front_interfaces peer: DcimOadmFrontInterface identifier: front_interfaces optional: true cardinality: many kind: Component - name: rear_interface peer: DcimOadmRearInterface identifier: rear_interfaces optional: true cardinality: one kind: Component - name: OadmFrontInterface label: Optical Multiplexer Front Interfaces menu_placement: DcimGenericOadmInterface namespace: Dcim human_friendly_id: - optical_multiplexer__name__value - name__value display_label: '{{ optical_multiplexer__name__value }} > {{ name__value }}' order_by: - optical_multiplexer__name__value - name__value uniqueness_constraints: - - optical_multiplexer - name__value inherit_from: - DcimEndpoint - DcimGenericOadmInterface relationships: - name: optical_multiplexer peer: DcimOpticalMultiplexer order_weight: 900 identifier: front_interfaces optional: false cardinality: one kind: Parent - name: channels peer: DcimWdmChannel order_weight: 1200 optional: true cardinality: many kind: Attribute - name: OadmRearInterface label: Optical Multiplexer Rear Interfaces menu_placement: DcimGenericOadmInterface namespace: Dcim human_friendly_id: - optical_multiplexer__name__value - name__value display_label: '{{ optical_multiplexer__name__value }} > {{ name__value }}' order_by: - optical_multiplexer__name__value - name__value uniqueness_constraints: - - optical_multiplexer - name__value inherit_from: - DcimEndpoint - DcimGenericOadmInterface relationships: - name: optical_multiplexer peer: DcimOpticalMultiplexer order_weight: 900 identifier: rear_interface optional: false cardinality: one kind: Parent - name: WdmChannel namespace: Dcim description: A WDM channel with its wavelength and frequency. label: WDM Channel icon: game-icons:laser-warning order_by: - wdm_type__value - channel__value display_label: '{{ wdm_type__value }} Channel {{ channel__value }}' uniqueness_constraints: - - frequency__value - wavelength__value - channel__value - wdm_type__value - - channel__value - wdm_type__value human_friendly_id: - wdm_type__value - channel__value attributes: - name: channel kind: Number description: WDM channel number. - name: wdm_type kind: Dropdown description: Type of WDM (e.g CWDM. DWDM) order_weight: 1110 optional: false choices: - name: cwdm label: CWDM (Coarse Wavelength Division Multiplexing) description: Supports multiple wavelengths for communication up to 70km. color: '#0099cc' - name: dwdm label: DWDM (Dense Wavelength Division Multiplexing) description: Supports dense wavelengths and amplification for long-distance communication. color: '#9933cc' default_value: dwdm - name: wavelength label: Wavelength (nm) kind: Text description: Wavelength of the channel in nm. - name: frequency label: Frequency (GHz) kind: Text description: Frequency of the channel in GHz. - name: WdmSFP namespace: Dcim inherit_from: - DcimGenericSFP description: SFP with configuration for Wavelength Division Multiplexing. label: WDM SFP icon: mdi:laser-pointer menu_placement: DcimGenericSFP attributes: - name: wdm_type kind: Dropdown description: Type of WDM (e.g CWDM. DWDM) order_weight: 1110 optional: false choices: - name: cwdm label: CWDM (Coarse Wavelength Division Multiplexing) description: Supports multiple wavelengths for communication up to 70km. color: '#0099cc' - name: dwdm label: DWDM (Dense Wavelength Division Multiplexing) description: Supports dense wavelengths and amplification for long-distance communication. color: '#9933cc' default_value: dwdm relationships: - name: wdm_channel label: WDM Channel peer: DcimWdmChannel kind: Attribute cardinality: one optional: false order_weight: 1150 ``` --- # Firewall Policer This schema extension contains models for VMs. You might consider Cluster or/and Hypervisor extension to go with! ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) ## Nodes[​](#nodes "Direct link to Nodes") ### Policer[​](#policer "Direct link to Policer") * **Label:** Network Policer * **Description:** A generic policer configuration. * **Namespace:** Security * **Icon:** mdi :car-speed-limiter * **Human Friendly ID:** name\_\_value #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ------------------ | ----------------------------------------------- | -------- | -------- | -------------- | ------------------------------------------------------------------------------- | | name | Unique name of the policer. | Text | | | | | description | | Text | True | | | | policer\_type | Type of policer. | Dropdown | True | | bandwidth-policer, interface-policer, shared-policer, hierarchical-policer | | bandwidth\_limit | Bandwidth limit for the policer. | Dropdown | True | | 500k, 2125k, 5250k, 10m, 20m, 30m, 50m, 75m, 100m, 200m, 300m, 1000m | | pps\_limit | Packets per second (PPS) limit for the policer. | Dropdown | True | | 500pps, 1000pps, 5000pps | | burst\_size\_limit | Burst size limit for the policer. | Dropdown | True | | 50k, 100k, 128k, 256k, 512k, 1m, 1500k, 2m, 3m, 4m, 8m, 12m, 37m, 40m, 1000000k | | packet\_burst | Packet burst size for the policer. | Dropdown | True | | 1k, 5k, 10k | | action | Action to take when limits are exceeded. | Dropdown | | | discard, drop, accept | ## Code[​](#code "Direct link to Code") ``` version: '1.0' nodes: - name: Policer namespace: Security label: Network Policer icon: mdi:car-speed-limiter description: A generic policer configuration. display_label: name__value order_by: - name__value human_friendly_id: - name__value attributes: - name: name kind: Text label: Name description: Unique name of the policer. order_weight: 1000 unique: true - name: description kind: Text optional: true order_weight: 1100 - name: policer_type kind: Dropdown description: Type of policer. label: Policer Type optional: true order_weight: 1150 choices: - name: bandwidth-policer label: Bandwidth Policer description: Policer that limits bandwidth on interfaces. color: '#C3E0E5' - name: interface-policer label: Interface Policer description: Policer applied to interfaces for rate-limiting traffic. color: '#D1E7E1' - name: shared-policer label: Shared Policer description: Policer with shared bandwidth across multiple links. color: '#A5C9C7' - name: hierarchical-policer label: Hierarchical Policer description: Policer applied in a hierarchical manner (e.g., parent-child relationships). color: '#B1E0D9' - name: bandwidth_limit kind: Dropdown description: Bandwidth limit for the policer. label: Bandwidth Limit optional: true order_weight: 1200 choices: - name: 500k label: 500 Kbps description: Bandwidth limit of 500 Kbps. color: '#A9CCE3' - name: 2125k label: 2,125 Kbps description: Bandwidth limit of 2,125 Kbps. color: '#AED6F1' - name: 5250k label: 5,250 Kbps description: Bandwidth limit of 5,250 Kbps. color: '#B4DDED' - name: 10m label: 10 Mbps description: Bandwidth limit of 10 Mbps. color: '#C2E2F3' - name: 20m label: 20 Mbps description: Bandwidth limit of 20 Mbps. color: '#D0E7F8' - name: 30m label: 30 Mbps description: Bandwidth limit of 30 Mbps. color: '#E0ECF9' - name: 50m label: 50 Mbps description: Bandwidth limit of 50 Mbps. color: '#AFC7F2' - name: 75m label: 75 Mbps description: Bandwidth limit of 75 Mbps. color: '#E8F3FD' - name: 100m label: 100 Mbps description: Bandwidth limit of 100 Mbps. color: '#F0F9FF' - name: 200m label: 200 Mbps description: Bandwidth limit of 200 Mbps. color: '#D1E6F9' - name: 300m label: 300 Mbps description: Bandwidth limit of 300 Mbps. color: '#EAF2FC' - name: 1000m label: 1 Gbps description: Bandwidth limit of 1 Gbps. color: '#E6E6FA' - name: pps_limit kind: Dropdown description: Packets per second (PPS) limit for the policer. label: PPS Limit optional: true order_weight: 1250 choices: - name: 500pps label: 500 PPS description: PPS limit of 500. color: '#E0BBE4' - name: 1000pps label: 1,000 PPS description: PPS limit of 1,000. color: '#D4A5E4' - name: 5000pps label: 5,000 PPS description: PPS limit of 5,000. color: '#C89BE4' - name: burst_size_limit kind: Dropdown description: Burst size limit for the policer. label: Burst Size Limit optional: true order_weight: 1300 choices: - name: 50k label: 50 KB description: Burst size limit of 50 KB. color: '#CDEACC' - name: 100k label: 100 KB description: Burst size limit of 100 KB. color: '#B3E2A8' - name: 128k label: 128 KB description: Burst size limit of 128 KB. color: '#A3D89E' - name: 256k label: 256 KB description: Burst size limit of 256 KB. color: '#92CF91' - name: 512k label: 512 KB description: Burst size limit of 512 KB. color: '#88C786' - name: 1m label: 1 MB description: Burst size limit of 1 MB. color: '#7FCF79' - name: 1500k label: 1.5 MB description: Burst size limit of 1.5 MB. color: '#77C46B' - name: 2m label: 2 MB description: Burst size limit of 2 MB. color: '#63A17E' - name: 3m label: 3 MB description: Burst size limit of 3 MB. color: '#8FD19E' - name: 4m label: 4 MB description: Burst size limit of 4 MB. color: '#70B961' - name: 8m label: 8 MB description: Burst size limit of 8 MB. color: '#6BAD57' - name: 12m label: 12 MB description: Burst size limit of 12 MB. color: '#63A14C' - name: 37m label: 37 MB description: Burst size limit of 37 MB. color: '#56A56C' - name: 40m label: 40 MB description: Burst size limit of 40 MB. color: '#5F9742' - name: 1000000k label: 1 GB description: Burst size limit of 1 GB. color: '#599D4A' - name: packet_burst kind: Dropdown description: Packet burst size for the policer. label: Packet Burst optional: true order_weight: 1350 choices: - name: 1k label: 1,000 packets description: Packet burst size of 1,000 packets. color: '#FFE4E1' - name: 5k label: 5,000 packets description: Packet burst size of 5,000 packets. color: '#FFFACD' - name: 10k label: 10,000 packets description: Packet burst size of 10,000 packets. color: '#FFF0F5' - name: action kind: Dropdown description: Action to take when limits are exceeded. label: Action order_weight: 1400 choices: - name: discard label: Discard description: Discard the packet. color: '#F4CCCC' - name: drop label: Drop description: Drop the packet. color: '#FAD7A0' - name: accept label: Accept description: Accept the packet. color: '#CDEACC' ``` --- # Hosting Cluster A rather generic cluster built with compute units (e.g. servers) and able to host VMs. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) * [extensions/cluster](/schema-library/reference/cluster.md) * [extensions/compute](/schema-library/reference/compute.md) ## Nodes[​](#nodes "Direct link to Nodes") ### Hosting[​](#hosting "Direct link to Hosting") * **Label:** Hosting Cluster * **Description:** A cluster hosting virtual machines. * **Namespace:** Cluster * **Icon:** mdi :dots-hexagon * **Inherit From:** ClusterGeneric, VirtualizationHostVirtualMachine, ClusterGenericComputeUnitNodes #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ------------- | -------------------- | -------- | -------- | -------------- | ------------------------------------------ | | cluster\_type | Type of the cluster. | Dropdown | | | aws, kvm, gcp, vmware | | status | | Dropdown | False | | active, provisioning, maintenance, drained | ## Code[​](#code "Direct link to Code") ``` version: '1.0' nodes: - name: Hosting namespace: Cluster label: Hosting Cluster description: A cluster hosting virtual machines. icon: mdi:dots-hexagon menu_placement: ClusterGeneric inherit_from: - ClusterGeneric - VirtualizationHostVirtualMachine - ClusterGenericComputeUnitNodes attributes: - name: cluster_type kind: Dropdown order_weight: 1200 description: Type of the cluster. choices: - name: aws label: Amazon Web Service color: '#b90028' - name: kvm label: KVM color: '#0082e2' - name: gcp label: Google Cloud Platform color: '#e29200' - name: vmware label: VMware color: '#3d8600' - name: status kind: Dropdown optional: false order_weight: 1300 choices: - name: active label: Active description: Fully operational and currently in service. color: '#7fbf7f' - name: provisioning label: Provisioning description: In the process of being set up and configured. color: '#ffff7f' - name: maintenance label: Maintenance description: Undergoing routine maintenance or repairs. color: '#ffd27f' - name: drained label: Drained description: Temporarily taken out of service. color: '#bfbfbf' ``` --- # Infiniband This schema extension adds support for InfiniBand switches. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) * [extensions/compute](/schema-library/reference/compute.md) ## Nodes[​](#nodes "Direct link to Nodes") ### Switch[​](#switch "Direct link to Switch") * **Label:** InfiniBand Switch * **Description:** InfiniBand Switch * **Namespace:** Infiniband * **Icon:** mdi :server * **Inherit From:** CoreArtifactTarget #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------------------------- | ------------------------------------------------------- | -------- | -------- | -------------------------------- | ------------- | | name | | Text | | | | | description | | Text | True | | | | os\_version | | Text | True | | | | rsu\_rail\_id | InfiniBand RSU Rail ID (numeric) assigned to the switch | Number | True | | | | aaa\_authentication\_model | AAA authentication model to be used by the switch | Dropdown | False | local | tacacs, local | | aaa\_accounting\_model | AAA accounting model to be used by the switch | Dropdown | False | local | tacacs, local | | role | Role of the switch in the InfiniBand network | Dropdown | | | spine, leaf | | split\_ready | Enable Split-Ready profile for switch | Boolean | | False | | | ipv6\_enable | Enable IPv6 on the switch | Boolean | | False | | | cli\_prefix\_modes | Enable CLI prefix modes | Boolean | | True | | | xml\_gateway | Enable XML Gateway | Boolean | | False | | | ssh\_server\_security\_strict | Enable SSH Server Security Strict | Boolean | | False | | | banner | Banner to be displayed on login | Text | | NVIDIA MLNX-OS Switch Management | | | password\_hardening | Enable Password Hardening | Boolean | | False | | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | --------------- | ----------------------------- | -------- | ----------- | --------- | | interfaces | InfinibandSwitchInterface | True | many | Component | | mgmt\_interface | InfinibandSwitchMgmtInterface | True | one | Component | | rsu | InfinibandRSU | True | one | Attribute | ### SwitchInterface[​](#switchinterface "Direct link to SwitchInterface") * **Label:** InfiniBand Switch Interface * **Description:** InfiniBand Switch Interface * **Namespace:** Infiniband * **Icon:** mdi :ethernet * **Uniqueness Constraints:** * switch, name\_\_value * **Human Friendly ID:** switch\_\_name\_\_value, name\_\_value #### Attributes[​](#attributes-1 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | --------------------------- | ----------------------------------------------------------------------------------------------- | -------- | -------- | -------------- | ------------------------- | | name | | Text | | | | | description | | Text | True | | | | speed | | Number | | | | | enabled | | Boolean | | True | | | width | Width value sets supported lane options for the interface | Number | True | 7 | | | port\_type | Enable interface to be split X times (requires Split-Ready profile to be enabled on the switch) | Number | True | | | | port\_type\_force | Force the configured port type setting; use in conjunction with Port Type | Boolean | | False | | | operational\_virtual\_lanes | Number of operational virtual lanes for an interface | Number | | 8 | | | mtu | Maximum Transmission Unit (bytes) | Number | | | | | speed\_forced | Force the configured speed setting(s); use in conjunction with Speed options | Boolean | | False | | | sfp\_type | Type of SFP module used in the interface | Text | True | | | | role | Role of the interface in the InfiniBand network | Dropdown | True | | endhost, uplink, reserved | #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------- | -------------------------------- | -------- | ----------- | --------- | | speed\_option | InfinibandSwitchIntfSpeedOptions | True | many | Attribute | | switch | InfinibandSwitch | False | one | Parent | ### SwitchMgmtInterface[​](#switchmgmtinterface "Direct link to SwitchMgmtInterface") * **Label:** InfiniBand Mgmt Interface * **Description:** InfiniBand Switch Management Interface * **Namespace:** Infiniband * **Icon:** mdi :ethernet * **Uniqueness Constraints:** * switch, name\_\_value * **Human Friendly ID:** switch\_\_name\_\_value, name\_\_value #### Attributes[​](#attributes-2 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------- | ---------------------------------------- | ------- | -------- | -------------- | ------- | | name | | Text | | | | | description | | Text | True | | | | speed | | Number | | | | | mtu | | Number | | 1500 | | | enabled | | Boolean | | True | | | dhcp | Enable DHCP for the management interface | Boolean | | True | | #### Relationships[​](#relationships-2 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------- | ---------------- | -------- | ----------- | ------ | | switch | InfinibandSwitch | False | one | Parent | | ipv4\_address | IpamIPAddress | True | one | | ### SwitchIntfSpeedOptions[​](#switchintfspeedoptions "Direct link to SwitchIntfSpeedOptions") * **Label:** InfiniBand Interface Speed Options * **Description:** InfiniBand Interface Speed Options * **Namespace:** Infiniband * **Icon:** mdi :ethernet * **Human Friendly ID:** description\_\_value #### Attributes[​](#attributes-3 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------- | ------------------------------------------------ | -------- | -------- | -------------- | ---------------------------- | | speed | Speed of the interface | Dropdown | | | SDR, NDR, QDR, FDR, EDR, HDR | | description | Description of the speed option (must be unique) | Text | | | | #### Relationships[​](#relationships-3 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------ | ------------------------- | -------- | ----------- | ------ | | switch | InfinibandSwitchInterface | False | one | Parent | ### RSU[​](#rsu "Direct link to RSU") * **Label:** InfiniBand RSU * **Description:** InfiniBand Rail-Optimized Scalable Unit * **Namespace:** Infiniband * **Icon:** mdi :network * **Uniqueness Constraints:** * identifier\_\_value #### Attributes[​](#attributes-4 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ---------- | ---------------------------------------------------------- | ------ | -------- | -------------- | ------- | | identifier | InfiniBand RSU Identifier (A-Z: a unique character string) | Text | | | | | size | Number of InfiniBand Leaf Switches (Rails) in the RSU | Number | True | | | #### Relationships[​](#relationships-4 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ---------- | ------------------------- | -------- | ----------- | --------- | | switches | InfinibandSwitch | True | many | Attribute | | interfaces | InfinibandSwitchInterface | True | many | Attribute | ### Fabric[​](#fabric "Direct link to Fabric") * **Label:** InfiniBand Network Fabric * **Description:** InfiniBand Network Fabric * **Namespace:** Infiniband * **Icon:** mdi :network * **Human Friendly ID:** name\_\_value * **Inherit From:** CoreArtifactTarget #### Attributes[​](#attributes-5 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ---- | ------------------------------ | ---- | -------- | -------------- | ------- | | name | InfiniBand Network Fabric Name | Text | | | | ## Code[​](#code "Direct link to Code") ``` version: '1.0' nodes: - name: Switch namespace: Infiniband description: InfiniBand Switch label: InfiniBand Switch icon: mdi:server inherit_from: - CoreArtifactTarget order_by: - name__value display_label: name__value attributes: - name: name kind: Text unique: true order_weight: 1000 - name: description kind: Text optional: true order_weight: 2000 - name: os_version kind: Text optional: true order_weight: 2200 - name: rsu_rail_id kind: Number label: RSU Rail ID description: InfiniBand RSU Rail ID (numeric) assigned to the switch optional: true - name: aaa_authentication_model kind: Dropdown label: AAA Authentication Model description: AAA authentication model to be used by the switch optional: false default_value: local choices: - name: tacacs label: TACACS+ description: Default AAA authentication will use TACACS+ server color: '#7fbf71' - name: local label: local description: Default AAA authentication will use local AAA configuration color: '#7fbf72' - name: aaa_accounting_model kind: Dropdown label: AAA Accounting Model description: AAA accounting model to be used by the switch default_value: local optional: false choices: - name: tacacs label: TACACS+ description: Default AAA accounting will use TACACS+ server color: '#7fbf71' - name: local label: local description: Default AAA accounting will use local AAA configuration color: '#ffff7f' - name: role kind: Dropdown label: Switch Role description: Role of the switch in the InfiniBand network choices: - name: spine label: Spine description: Spine Switch color: '#7fbf7f' - name: leaf label: Leaf description: Leaf Switch color: '#ffff7f' - name: split_ready kind: Boolean label: Split Ready description: Enable Split-Ready profile for switch default_value: false - name: ipv6_enable kind: Boolean label: IPv6 Enable description: Enable IPv6 on the switch default_value: false - name: cli_prefix_modes kind: Boolean label: CLI Prefix Modes description: Enable CLI prefix modes default_value: true - name: xml_gateway kind: Boolean label: XML Gateway description: Enable XML Gateway default_value: false - name: ssh_server_security_strict kind: Boolean label: SSH Server Security Strict description: Enable SSH Server Security Strict default_value: false - name: banner kind: Text label: Banner description: Banner to be displayed on login default_value: NVIDIA MLNX-OS Switch Management - name: password_hardening kind: Boolean label: Password Hardening description: Enable Password Hardening default_value: false relationships: - name: interfaces kind: Component cardinality: many peer: InfinibandSwitchInterface optional: true - name: mgmt_interface kind: Component cardinality: one peer: InfinibandSwitchMgmtInterface optional: true - name: rsu peer: InfinibandRSU optional: true cardinality: one kind: Attribute - name: SwitchInterface namespace: Infiniband description: InfiniBand Switch Interface label: InfiniBand Switch Interface icon: mdi:ethernet human_friendly_id: - switch__name__value - name__value order_by: - name__value display_label: name__value uniqueness_constraints: - - switch - name__value attributes: - name: name kind: Text order_weight: 1000 - name: description kind: Text optional: true order_weight: 1100 - name: speed kind: Number order_weight: 1400 - name: enabled kind: Boolean default_value: true order_weight: 1200 - name: width kind: Number label: Width description: Width value sets supported lane options for the interface optional: true default_value: 7 enum: - 1 - 3 - 5 - 7 - name: port_type kind: Number label: 'Port Type: Split' description: Enable interface to be split X times (requires Split-Ready profile to be enabled on the switch) optional: true enum: - 2 - name: port_type_force kind: Boolean label: Port Type Force description: Force the configured port type setting; use in conjunction with Port Type default_value: false - name: operational_virtual_lanes kind: Number label: Operational Virtual Lanes description: Number of operational virtual lanes for an interface default_value: 8 enum: - 1 - 2 - 4 - 8 - name: mtu kind: Number label: MTU (bytes) description: Maximum Transmission Unit (bytes) enum: - 256 - 512 - 1024 - 2048 - 4096 - name: speed_forced kind: Boolean label: Speed Forced description: Force the configured speed setting(s); use in conjunction with Speed options default_value: false - name: sfp_type kind: Text label: SFP Type description: Type of SFP module used in the interface optional: true - name: role kind: Dropdown label: Interface Role description: Role of the interface in the InfiniBand network optional: true choices: - name: endhost label: Network Device Endhost Port description: InfiniBand Switch Interface <> Server HCA color: '#98b2d1' - name: uplink label: Uplink description: InfiniBand Switch Interface <> InfiniBand Switch Interface color: '#93e9be' - name: reserved label: Reserved description: Reserved for future use color: '#d3d3d3' relationships: - name: speed_option peer: InfinibandSwitchIntfSpeedOptions optional: true cardinality: many kind: Attribute max_count: 6 - name: switch peer: InfinibandSwitch optional: false cardinality: one kind: Parent - name: SwitchMgmtInterface namespace: Infiniband description: InfiniBand Switch Management Interface label: InfiniBand Mgmt Interface icon: mdi:ethernet human_friendly_id: - switch__name__value - name__value order_by: - name__value display_label: name__value uniqueness_constraints: - - switch - name__value attributes: - name: name kind: Text order_weight: 1000 - name: description kind: Text optional: true order_weight: 1100 - name: speed kind: Number order_weight: 1400 - name: mtu label: MTU default_value: 1500 kind: Number order_weight: 1500 - name: enabled kind: Boolean default_value: true order_weight: 1200 - name: dhcp kind: Boolean label: DHCP description: Enable DHCP for the management interface default_value: true relationships: - name: switch peer: InfinibandSwitch optional: false cardinality: one kind: Parent - name: ipv4_address peer: IpamIPAddress optional: true cardinality: one - name: SwitchIntfSpeedOptions namespace: Infiniband description: InfiniBand Interface Speed Options label: InfiniBand Interface Speed Options icon: mdi:ethernet human_friendly_id: - description__value order_by: - speed__value display_label: speed__value attributes: - name: speed kind: Dropdown label: Speed description: Speed of the interface choices: - name: SDR label: SDR - name: NDR label: NDR - name: QDR label: QDR - name: FDR label: FDR - name: EDR label: EDR - name: HDR label: HDR - name: description kind: Text label: Description description: Description of the speed option (must be unique) unique: true relationships: - name: switch peer: InfinibandSwitchInterface optional: false cardinality: one kind: Parent - name: RSU namespace: Infiniband description: InfiniBand Rail-Optimized Scalable Unit label: InfiniBand RSU icon: mdi:network order_by: - identifier__value display_label: identifier__value uniqueness_constraints: - - identifier__value attributes: - name: identifier kind: Text label: Identifier description: 'InfiniBand RSU Identifier (A-Z: a unique character string)' regex: '[A-Z]' - name: size kind: Number label: Size description: Number of InfiniBand Leaf Switches (Rails) in the RSU optional: true relationships: - name: switches peer: InfinibandSwitch optional: true cardinality: many kind: Attribute - name: interfaces peer: InfinibandSwitchInterface optional: true cardinality: many kind: Attribute - name: Fabric namespace: Infiniband description: InfiniBand Network Fabric label: InfiniBand Network Fabric human_friendly_id: - name__value icon: mdi:network inherit_from: - CoreArtifactTarget order_by: - name__value display_label: name__value attributes: - name: name kind: Text label: Name description: InfiniBand Network Fabric Name unique: true ``` --- # Interface Breakout This schema extension introduces relationships to support breakout interfaces, enabling you to document the breakout of a physical interface into smaller physical interfaces. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) ## Extensions[​](#extensions "Direct link to Extensions") note In this context "extensions" refer to modifications or additions to the existing schema, such as adding new attributes, relationships, or other schema elements. ### InterfacePhysical[​](#interfacephysical "Direct link to InterfacePhysical") #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | -------------------- | -------------------------------------------------- | ------- | -------- | -------------- | ------- | | breakout\_capability | Indicates if the port supports breakout capability | Boolean | False | False | | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | --------------------------- | ----------------- | -------- | ----------- | --------- | | breakout\_child\_interfaces | InterfacePhysical | True | many | Attribute | | breakout\_parent\_interface | InterfacePhysical | True | one | Attribute | ## Code[​](#code "Direct link to Code") ``` version: '1.0' extensions: nodes: - kind: InterfacePhysical attributes: - name: breakout_capability kind: Boolean optional: false default_value: false description: Indicates if the port supports breakout capability relationships: - name: breakout_child_interfaces peer: InterfacePhysical label: Breakout child interface(s) optional: true cardinality: many kind: Attribute identifier: physical__breakout direction: outbound description: Interfaces resulting from the breakout order_weight: 1650 - name: breakout_parent_interface peer: InterfacePhysical label: Breakout parent interface optional: true cardinality: one kind: Attribute identifier: physical__breakout direction: inbound description: Interface from which breakout is created order_weight: 1700 ``` --- # IPAM Basic IPAM schema to capture IP addresses, subnets, and related information. ## Details[​](#details "Direct link to Details") * **Dependencies:** No dependencies ## Nodes[​](#nodes "Direct link to Nodes") ### IPAddress[​](#ipaddress "Direct link to IPAddress") * **Label:** IP Address * **Description:** IP Address * **Namespace:** Ipam * **Icon:** mdi :ip * **Uniqueness Constraints:** * address\_\_value, ip\_namespace * **Human Friendly ID:** address\_\_value, ip\_namespace\_\_name\_\_value * **Inherit From:** BuiltinIPAddress #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ---- | ----------- | ---- | -------- | -------------- | ------- | | fqdn | | Text | True | | | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | --------- | --------------- | -------- | ----------- | ---- | | interface | InterfaceLayer3 | True | one | | ### Prefix[​](#prefix "Direct link to Prefix") * **Label:** Prefix * **Description:** IPv4 or IPv6 network (with mask) * **Namespace:** Ipam * **Icon:** mdi :ip-network * **Uniqueness Constraints:** * prefix\_\_value, ip\_namespace * **Human Friendly ID:** prefix\_\_value, ip\_namespace\_\_name\_\_value * **Inherit From:** BuiltinIPPrefix #### Attributes[​](#attributes-1 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ------ | ----------- | -------- | -------- | -------------- | ------------------------------------------------------------------------ | | status | | Dropdown | | | active, deprecated, reserved | | role | | Dropdown | True | | loopback, management, public, server, supernet, technical, loopback-vtep | #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------ | ------------------- | -------- | ----------- | --------- | | organization | OrganizationGeneric | True | one | Attribute | | location | LocationHosting | True | one | Attribute | | gateway | IpamIPAddress | True | one | Attribute | ## Code[​](#code "Direct link to Code") ``` version: '1.0' nodes: - name: IPAddress namespace: Ipam description: IP Address label: IP Address icon: mdi:ip include_in_menu: false order_by: - address__value display_label: address__value inherit_from: - BuiltinIPAddress uniqueness_constraints: - - address__value - ip_namespace human_friendly_id: - address__value - ip_namespace__name__value attributes: - name: fqdn label: FQDN kind: Text optional: true regex: (?=^.{1,253}$)(^(((?!-)[a-zA-Z0-9-]{1,63}(? :location-line * **Human Friendly ID:** shortname\_\_value #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------- | ----------- | ---- | -------- | -------------- | ------- | | name | | Text | | | | | shortname | | Text | | | | | description | | Text | True | | | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ---- | ---------- | -------- | ----------- | --------- | | tags | BuiltinTag | True | many | Attribute | ### Hosting[​](#hosting "Direct link to Hosting") * **Description:** Location directly hosting device and services. * **Namespace:** Location * **Human Friendly ID:** shortname\_\_value #### Attributes[​](#attributes-1 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | --------- | ----------- | ---- | -------- | -------------- | ------- | | shortname | | Text | | | | #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | -------- | ------------------ | -------- | ----------- | ---- | | prefixes | IpamPrefix | True | many | | | devices | DcimPhysicalDevice | True | many | | ## Code[​](#code "Direct link to Code") ``` version: '1.0' generics: - name: Generic namespace: Location description: Generic Location, could be a country, city ... label: Location icon: mingcute:location-line include_in_menu: true hierarchical: true human_friendly_id: - shortname__value order_by: - name__value display_label: name__value attributes: - name: name kind: Text order_weight: 1000 - name: shortname kind: Text unique: true order_weight: 1100 - name: description kind: Text optional: true order_weight: 1200 relationships: - name: tags peer: BuiltinTag kind: Attribute optional: true cardinality: many - name: Hosting namespace: Location description: Location directly hosting device and services. include_in_menu: false human_friendly_id: - shortname__value attributes: - name: shortname kind: Text unique: true order_weight: 1100 relationships: - name: prefixes label: Prefixes peer: IpamPrefix cardinality: many optional: true - name: devices label: Devices peer: DcimPhysicalDevice cardinality: many optional: true ``` --- # Location Extended This schema extension is the most detailed when it comes to location, you'll find all the layers you can think of. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) ## Nodes[​](#nodes "Direct link to Nodes") ### Continent[​](#continent "Direct link to Continent") * **Label:** Continent * **Namespace:** Location * **Icon:** jam :world * **Inherit From:** LocationGeneric ### Country[​](#country "Direct link to Country") * **Label:** Country * **Namespace:** Location * **Icon:** gis :search-country * **Inherit From:** LocationGeneric ### Region[​](#region "Direct link to Region") * **Label:** Region * **Namespace:** Location * **Icon:** carbon :cics-region-target * **Inherit From:** LocationGeneric ### Metro[​](#metro "Direct link to Metro") * **Label:** Metro * **Namespace:** Location * **Icon:** healthicons :city * **Inherit From:** LocationGeneric ### Building[​](#building "Direct link to Building") * **Label:** Building * **Namespace:** Location * **Icon:** ri :building-line * **Inherit From:** LocationGeneric #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------------- | ----------- | ---- | -------- | -------------- | ------- | | facility\_id | | Text | True | | | | physical\_address | | Text | True | | | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ----- | ------------------- | -------- | ----------- | ---- | | owner | OrganizationGeneric | True | one | | ### Floor[​](#floor "Direct link to Floor") * **Label:** Floor * **Namespace:** Location * **Icon:** mdi :home-floor-0 * **Inherit From:** LocationGeneric ### Suite[​](#suite "Direct link to Suite") * **Label:** Suite * **Namespace:** Location * **Icon:** game-icons :cage * **Inherit From:** LocationGeneric #### Attributes[​](#attributes-1 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ------------ | ----------- | ---- | -------- | -------------- | ------- | | facility\_id | | Text | True | | | #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ----- | ------------------- | -------- | ----------- | ---- | | owner | OrganizationGeneric | True | one | | ### Rack[​](#rack "Direct link to Rack") * **Label:** Rack * **Namespace:** Location * **Icon:** clarity :rack-server-line * **Human Friendly ID:** shortname\_\_value * **Inherit From:** LocationGeneric #### Attributes[​](#attributes-2 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ------------ | ----------- | ---- | -------- | -------------- | ------- | | facility\_id | | Text | True | | | #### Relationships[​](#relationships-2 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ----- | ------------------- | -------- | ----------- | ---- | | owner | OrganizationGeneric | True | one | | ## Extensions[​](#extensions "Direct link to Extensions") note In this context "extensions" refer to modifications or additions to the existing schema, such as adding new attributes, relationships, or other schema elements. ### OrganizationProvider[​](#organizationprovider "Direct link to OrganizationProvider") #### Relationships[​](#relationships-3 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | -------- | ---------------- | -------- | ----------- | ---- | | location | LocationBuilding | True | many | | ## Code[​](#code "Direct link to Code") ``` version: '1.0' nodes: - name: Continent namespace: Location inherit_from: - LocationGeneric include_in_menu: true menu_placement: LocationGeneric label: Continent display_label: name__value parent: '' children: LocationCountry icon: jam:world - name: Country namespace: Location inherit_from: - LocationGeneric include_in_menu: true menu_placement: LocationGeneric label: Country display_label: name__value parent: LocationContinent children: LocationRegion icon: gis:search-country - name: Region namespace: Location inherit_from: - LocationGeneric include_in_menu: true menu_placement: LocationGeneric label: Region display_label: name__value parent: LocationCountry children: LocationMetro icon: carbon:cics-region-target - name: Metro namespace: Location inherit_from: - LocationGeneric include_in_menu: true menu_placement: LocationGeneric label: Metro display_label: name__value parent: LocationRegion children: LocationBuilding icon: healthicons:city - name: Building namespace: Location inherit_from: - LocationGeneric include_in_menu: true menu_placement: LocationGeneric label: Building display_label: name__value parent: LocationMetro children: LocationFloor icon: ri:building-line attributes: - name: facility_id kind: Text unique: false optional: true order_weight: 1100 - name: physical_address kind: Text unique: false optional: true order_weight: 1500 relationships: - name: owner peer: OrganizationGeneric optional: true cardinality: one - name: Floor namespace: Location inherit_from: - LocationGeneric include_in_menu: true menu_placement: LocationGeneric label: Floor display_label: name__value parent: LocationBuilding icon: mdi:home-floor-0 children: LocationSuite - name: Suite namespace: Location inherit_from: - LocationGeneric include_in_menu: true menu_placement: LocationGeneric label: Suite display_label: name__value parent: LocationFloor children: LocationRack icon: game-icons:cage attributes: - name: facility_id kind: Text unique: false optional: true order_weight: 1100 relationships: - name: owner peer: OrganizationGeneric optional: true cardinality: one - name: Rack namespace: Location inherit_from: - LocationGeneric include_in_menu: true menu_placement: LocationGeneric label: Rack display_label: name__value human_friendly_id: - shortname__value parent: LocationSuite children: '' icon: clarity:rack-server-line attributes: - name: facility_id kind: Text unique: false optional: true order_weight: 1100 relationships: - name: owner peer: OrganizationGeneric optional: true cardinality: one extensions: nodes: - kind: OrganizationProvider relationships: - name: location peer: LocationBuilding cardinality: many optional: true ``` --- # Location Minimal This schema extension is minimal but will provide you with basic items to store location related data. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) ## Nodes[​](#nodes "Direct link to Nodes") ### Country[​](#country "Direct link to Country") * **Label:** Country * **Namespace:** Location * **Icon:** gis :search-country * **Inherit From:** LocationGeneric #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | -------- | ----------- | ---- | -------- | -------------- | ------- | | timezone | | Text | True | | | ### Metro[​](#metro "Direct link to Metro") * **Label:** Metro * **Namespace:** Location * **Icon:** healthicons :city * **Inherit From:** LocationGeneric ### Site[​](#site "Direct link to Site") * **Label:** Site * **Namespace:** Location * **Icon:** ri :building-line * **Inherit From:** LocationGeneric, LocationHosting #### Attributes[​](#attributes-1 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------------- | ----------- | ---- | -------- | -------------- | ------- | | facility\_id | | Text | True | | | | physical\_address | | Text | True | | | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ----- | ------------------- | -------- | ----------- | ---- | | owner | OrganizationGeneric | True | one | | ### Rack[​](#rack "Direct link to Rack") * **Label:** Rack * **Namespace:** Location * **Icon:** clarity :rack-server-line * **Inherit From:** LocationGeneric, LocationHosting #### Attributes[​](#attributes-2 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ------------ | ----------- | ------ | -------- | -------------- | ------- | | facility\_id | | Text | True | | | | height | | Number | False | 42 | | #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ----- | ------------------- | -------- | ----------- | ---- | | owner | OrganizationGeneric | True | one | | ## Extensions[​](#extensions "Direct link to Extensions") note In this context "extensions" refer to modifications or additions to the existing schema, such as adding new attributes, relationships, or other schema elements. ### OrganizationProvider[​](#organizationprovider "Direct link to OrganizationProvider") #### Relationships[​](#relationships-2 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ----- | ------------ | -------- | ----------- | ---- | | sites | LocationSite | True | many | | ## Code[​](#code "Direct link to Code") ``` version: '1.0' nodes: - name: Country namespace: Location label: Country inherit_from: - LocationGeneric menu_placement: LocationGeneric display_label: name__value parent: '' children: LocationMetro icon: gis:search-country attributes: - name: timezone kind: Text optional: true order_weight: 1300 - name: Metro namespace: Location label: Metro inherit_from: - LocationGeneric menu_placement: LocationGeneric display_label: name__value parent: LocationCountry children: LocationSite icon: healthicons:city - name: Site namespace: Location label: Site inherit_from: - LocationGeneric - LocationHosting menu_placement: LocationGeneric display_label: name__value parent: LocationMetro children: LocationRack icon: ri:building-line attributes: - name: facility_id kind: Text unique: false optional: true order_weight: 1100 - name: physical_address kind: Text unique: false optional: true order_weight: 1500 relationships: - name: owner peer: OrganizationGeneric optional: true cardinality: one - name: Rack namespace: Location label: Rack inherit_from: - LocationGeneric - LocationHosting include_in_menu: true display_label: name__value menu_placement: LocationGeneric parent: LocationSite icon: clarity:rack-server-line attributes: - name: facility_id kind: Text unique: false optional: true order_weight: 1100 - name: height label: Height (U) optional: false default_value: 42 kind: Number order_weight: 1300 relationships: - name: owner peer: OrganizationGeneric optional: true cardinality: one extensions: nodes: - kind: OrganizationProvider relationships: - name: sites peer: LocationSite cardinality: many optional: true ``` --- # MLAG This schema extension contains the foundations to capture Multi-Chassis Link Aggregation Group (MLAG). It comes on top of the LAG extension. In this implementation, a MLAG interface is essentially a LAG interface but linked to a MLAG domain (instead of a device). The MLAG domain regroup devices together (usually 2) and is built over LAG interfaces used as peer-link between the devices. MLAG interfaces defined at the MLAG domain level are then spread across all devices in the domain. Note: This is a very minimalist implementation of MLAG, the goal is to make it easy to blend with existing models you might have. For example, if you are operating in a data center fabric environment you might already have a LeafGroup or similar concept. In that case you can have your LeafGroup inherit from GenericMlagDomain and add the necessary relationships/attributes. Next steps: * Add support for layer 3 overlay for MLAG interfaces (loopback, peer address ...) * Create virtual relationship so MLAG interfaces can be seen on the devices in the domain ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) * [extensions/lag](/schema-library/reference/lag.md) ## Nodes[​](#nodes "Direct link to Nodes") ### Interface[​](#interface "Direct link to Interface") * **Label:** MLAG Interface * **Description:** Multi-Chassis Link Aggregation Group Interface * **Namespace:** Mlag * **Uniqueness Constraints:** * mlag\_domain, name\_\_value * **Human Friendly ID:** mlag\_domain\_\_domain\_id\_\_value, name\_\_value * **Inherit From:** InterfaceLayer2, GenericInterfaceBundle #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------- | ---------------------------------------- | -------- | -------- | -------------- | ------------------------------------------------------------------ | | name | Name of the interface | Text | | | | | mlag\_id | Identifier for the MLAG interface | Number | | | | | description | A brief description of the interface | Text | True | | | | status | The status of the interface | Dropdown | | active | provisioning, free, active, maintenance, disabled, deleted, outage | | role | The role of the interface in the network | Dropdown | True | | server, router | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------ | ----------------- | -------- | ----------- | ------ | | mlag\_domain | GenericMlagDomain | False | one | Parent | ### Domain[​](#domain "Direct link to Domain") * **Label:** MLAG Domain * **Description:** MLAG (Multi-Chassis Link Aggregation Group) Domain * **Namespace:** Mlag * **Icon:** mingcute :cloud-line * **Inherit From:** GenericMlagDomain #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ----- | ---------- | -------- | ----------- | ------- | | peers | DcimDevice | False | many | Generic | ## Generics[​](#generics "Direct link to Generics") ### MlagDomain[​](#mlagdomain "Direct link to MlagDomain") * **Label:** MLAG Domain * **Namespace:** Generic * **Human Friendly ID:** domain\_id\_\_value #### Attributes[​](#attributes-1 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ------------- | ------------------------------------------------------------------ | ------ | -------- | -------------- | ------- | | domain\_id | Identifier for the MLAG domain | Text | | | | | reload\_delay | Time in seconds to wait before bringing up the MLAG after a reload | Number | | 300 | | #### Relationships[​](#relationships-2 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ---------------- | ------------- | -------- | ----------- | --------- | | peer\_links | InterfaceLag | | many | Attribute | | mlag\_interfaces | MlagInterface | | many | Component | ## Extensions[​](#extensions "Direct link to Extensions") note In this context "extensions" refer to modifications or additions to the existing schema, such as adding new attributes, relationships, or other schema elements. ### InterfaceLag[​](#interfacelag "Direct link to InterfaceLag") #### Relationships[​](#relationships-3 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ---------------- | ---------- | -------- | ----------- | --------- | | mlag\_peer\_link | MlagDomain | True | one | Attribute | ### DcimDevice[​](#dcimdevice "Direct link to DcimDevice") #### Relationships[​](#relationships-4 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------ | ---------- | -------- | ----------- | --------- | | mlag\_domain | MlagDomain | True | one | Attribute | ## Code[​](#code "Direct link to Code") ``` version: '1.0' generics: - name: MlagDomain namespace: Generic include_in_menu: false label: MLAG Domain display_label: domain_id__value human_friendly_id: - domain_id__value attributes: - name: domain_id label: MLAG Domain ID kind: Text unique: true description: Identifier for the MLAG domain order_weight: 900 - name: reload_delay label: Reload Delay kind: Number description: Time in seconds to wait before bringing up the MLAG after a reload default_value: 300 order_weight: 1650 relationships: - name: peer_links label: Peer Links description: LAG interfaces used as peer-links between the MLAG peers peer: InterfaceLag cardinality: many kind: Attribute order_weight: 1500 - name: mlag_interfaces label: MLAG Interface(s) peer: MlagInterface cardinality: many kind: Component description: Bundle interfaces that are spread across all devices in this MLAG domain. order_weight: 1500 nodes: - name: Interface namespace: Mlag label: MLAG Interface description: Multi-Chassis Link Aggregation Group Interface inherit_from: - InterfaceLayer2 - GenericInterfaceBundle include_in_menu: false uniqueness_constraints: - - mlag_domain - name__value human_friendly_id: - mlag_domain__domain_id__value - name__value order_by: - name__value display_label: name__value attributes: - name: name kind: Text description: Name of the interface order_weight: 1000 - name: mlag_id label: MLAG ID kind: Number description: Identifier for the MLAG interface order_weight: 1050 - name: description kind: Text optional: true description: A brief description of the interface order_weight: 1100 - name: status kind: Dropdown description: The status of the interface choices: - name: provisioning label: Provisioning description: Interface is being provisioned. color: '#A9DFBF' - name: free label: Free description: Interface is unused. color: '#CDEACC' - name: active label: Active description: Interface is active and operational. color: '#A9CCE3' - name: maintenance label: Maintenance description: Interface is under maintenance. color: '#FFF2CC' - name: disabled label: Disabled description: Interface has been disabled. color: '#D3D3D3' - name: deleted label: Deleted description: Interface has been deleted. color: '#FAD7A0' - name: outage label: Outage description: Interface is currently experiencing an outage. color: '#F4CCCC' default_value: active order_weight: 1200 - name: role kind: Dropdown optional: true description: The role of the interface in the network choices: - name: server label: Server Interface description: Interface connecting servers color: '#A9DFBF' - name: router label: Router Interface description: Interface connecting routers color: '#B2D4E6' order_weight: 1250 relationships: - name: mlag_domain label: MLAG Domain peer: GenericMlagDomain optional: false cardinality: one kind: Parent description: MLAG Domain this MLAG Interface belongs to order_weight: 1750 - name: Domain namespace: Mlag label: MLAG Domain description: MLAG (Multi-Chassis Link Aggregation Group) Domain icon: mingcute:cloud-line inherit_from: - GenericMlagDomain relationships: - name: peers peer: DcimDevice optional: false cardinality: many kind: Generic identifier: device__mlag_domain max_count: 2 min_count: 2 order_weight: 950 extensions: nodes: - kind: InterfaceLag relationships: - name: mlag_peer_link label: MLAG Peer Link peer: MlagDomain optional: true cardinality: one kind: Attribute description: MLAG Domain this LAG is used as peer link order_weight: 1800 - kind: DcimDevice relationships: - name: mlag_domain label: MLAG Domain peer: MlagDomain optional: true cardinality: one kind: Attribute description: MLAG Domain this device belongs to order_weight: 1800 identifier: device__mlag_domain ``` --- # Modules This schema extension allows you to capture Device Modules related information like the serial number or the status. You can insert the Module into a Dcim Physical Device. NOTE: This extension doesn't contain any Nodes, you can use the extension module\_linecards or modules\_routing\_engine to use it ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) ## Generics[​](#generics "Direct link to Generics") ### GenericModule[​](#genericmodule "Direct link to GenericModule") * **Label:** Module * **Description:** A generic module, such as a Linecard or Routing Engine, installed in a device. * **Namespace:** Device * **Icon:** mdi :expansion-card * **Human Friendly ID:** serial\_number\_\_value #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | -------------- | ----------------------------------- | -------- | -------- | -------------- | --------------------------------------------------- | | serial\_number | Unique serial number of the module. | Text | | | | | description | | Text | True | | | | status | | Dropdown | | active | provisioning, active, maintenance, disabled, outage | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------ | ----------------------- | -------- | ----------- | --------- | | module\_type | DeviceGenericModuleType | False | one | Attribute | | device | DcimPhysicalDevice | True | one | Attribute | ### GenericModuleType[​](#genericmoduletype "Direct link to GenericModuleType") * **Label:** Module Type * **Description:** A generic module type, with common specifications like part number and manufacturer. * **Namespace:** Device * **Icon:** mdi :database-cog * **Uniqueness Constraints:** * name\_\_value, manufacturer * **Human Friendly ID:** name\_\_value #### Attributes[​](#attributes-1 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ------------ | ------------------------------- | ---- | -------- | -------------- | ------- | | name | Name of the module type. | Text | | | | | description | Description of the module type. | Text | True | | | | part\_number | Part number of the module. | Text | True | | | #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------ | ------------------------ | -------- | ----------- | --------- | | manufacturer | OrganizationManufacturer | False | one | Attribute | | tags | BuiltinTag | True | many | Attribute | ## Extensions[​](#extensions "Direct link to Extensions") note In this context "extensions" refer to modifications or additions to the existing schema, such as adding new attributes, relationships, or other schema elements. ### DcimPhysicalDevice[​](#dcimphysicaldevice "Direct link to DcimPhysicalDevice") #### Relationships[​](#relationships-2 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------- | ------------------- | -------- | ----------- | --------- | | modules | DeviceGenericModule | | many | Component | ## Code[​](#code "Direct link to Code") ``` version: '1.0' generics: - name: GenericModule namespace: Device description: A generic module, such as a Linecard or Routing Engine, installed in a device. label: Module icon: mdi:expansion-card human_friendly_id: - serial_number__value display_label: serial_number__value order_by: - serial_number__value attributes: - name: serial_number kind: Text unique: true description: Unique serial number of the module. order_weight: 1000 - name: description kind: Text optional: true order_weight: 1100 - name: status kind: Dropdown choices: - name: provisioning label: Provisioning description: Linecard is being provisioned. color: '#A9DFBF' - name: active label: Active description: Linecard is active and operational. color: '#A9CCE3' - name: maintenance label: Maintenance description: Linecard is under maintenance. color: '#FFF2CC' - name: disabled label: Disabled description: Linecard has been disabled. color: '#D3D3D3' - name: outage label: Outage description: Linecard is currently experiencing an outage. color: '#F4CCCC' default_value: active order_weight: 1300 relationships: - name: module_type peer: DeviceGenericModuleType optional: false cardinality: one kind: Attribute order_weight: 1150 - name: device label: Device peer: DcimPhysicalDevice identifier: device__modules optional: true cardinality: one kind: Attribute order_weight: 1000 - name: GenericModuleType namespace: Device description: A generic module type, with common specifications like part number and manufacturer. label: Module Type icon: mdi:database-cog menu_placement: DeviceGenericModule display_label: name__value uniqueness_constraints: - - name__value - manufacturer human_friendly_id: - name__value order_by: - manufacturer__name__value - name__value attributes: - name: name kind: Text unique: true description: Name of the module type. order_weight: 1000 - name: description kind: Text optional: true description: Description of the module type. order_weight: 1100 - name: part_number label: Part Number kind: Text optional: true unique: true description: Part number of the module. order_weight: 1200 relationships: - name: manufacturer peer: OrganizationManufacturer identifier: manufacturer__moduletype cardinality: one optional: false kind: Attribute description: Manufacturer of the module type. order_weight: 1250 - name: tags peer: BuiltinTag optional: true cardinality: many kind: Attribute description: Tags associated with the module type. order_weight: 3000 extensions: nodes: - kind: DcimPhysicalDevice relationships: - name: modules peer: DeviceGenericModule identifier: device__modules cardinality: many kind: Component ``` --- # Modules Linecards This schema extension allows you to capture Linecard related information like the version. You can insert the Linecard into a Dcim Physical Device and leverage the Linecard type model. The Linecard can accept PIC to help configure PORT information like breakout-capabilities and configurations. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) * [extensions/modules](/schema-library/reference/modules.md) ## Nodes[​](#nodes "Direct link to Nodes") ### LinecardType[​](#linecardtype "Direct link to LinecardType") * **Label:** Linecard Type * **Description:** Linecard Type information, detailing specifications such as part number and manufacturer. * **Namespace:** Device * **Icon:** mdi :poll * **Inherit From:** DeviceGenericModuleType #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | --------- | -------------- | -------- | ----------- | ------- | | linecards | DeviceLinecard | | many | Generic | ### Linecard[​](#linecard "Direct link to Linecard") * **Label:** Linecard * **Description:** A Linecard installed in a device, specifying slot, power status, and functionalities. * **Namespace:** Device * **Icon:** bi :pci-card * **Inherit From:** DeviceGenericModule #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ------------ | ----------------------------------------------------------------- | ------- | -------- | -------------- | ------- | | slot | The slot number where the Linecard is installed within the device | Number | | | | | bng\_enabled | BNG activated or deactivated on the Linecard | Boolean | True | False | | #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | -------------- | ------------------ | -------- | ----------- | --------- | | linecard\_type | DeviceLinecardType | False | one | Attribute | | pics | DevicePic | True | many | Attribute | ### Pic[​](#pic "Direct link to Pic") * **Label:** PIC * **Description:** Physical Interface Card (PIC) installed in the Linecard, containing multiple ports. * **Namespace:** Device * **Icon:** mdi :memory * **Uniqueness Constraints:** * linecard, slot\_\_value * **Human Friendly ID:** serial\_number\_\_value #### Attributes[​](#attributes-1 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | -------------- | ------------------------------------------ | ------ | -------- | -------------- | ------- | | serial\_number | Unique serial number of the PIC. | Text | | | | | slot | Slot number of the PIC within the Linecard | Number | | | | #### Relationships[​](#relationships-2 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | -------- | -------------- | -------- | ----------- | --------- | | linecard | DeviceLinecard | False | one | Parent | | ports | DevicePort | True | many | Component | ### Port[​](#port "Direct link to Port") * **Label:** Port * **Description:** A network port on a PIC, specifying speed and port number. * **Namespace:** Device * **Icon:** mdi :ethernet * **Uniqueness Constraints:** * pic, port\_number\_\_value * **Human Friendly ID:** pic\_\_serial\_number\_\_value, port\_number\_\_value #### Attributes[​](#attributes-2 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ------------ | ---------------------- | -------- | -------- | -------------- | --------- | | port\_number | Port number on the PIC | Number | | | | | speed | Speed of the port | Dropdown | | | 10g, 100g | #### Relationships[​](#relationships-3 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ---- | --------- | -------- | ----------- | ------ | | pic | DevicePic | False | one | Parent | ## Code[​](#code "Direct link to Code") ``` version: '1.0' nodes: - name: LinecardType namespace: Device menu_placement: DeviceLinecard description: Linecard Type information, detailing specifications such as part number and manufacturer. icon: mdi:poll label: Linecard Type inherit_from: - DeviceGenericModuleType relationships: - name: linecards peer: DeviceLinecard cardinality: many kind: Generic description: Linecards of this type. - name: Linecard namespace: Device description: A Linecard installed in a device, specifying slot, power status, and functionalities. label: Linecard icon: bi:pci-card menu_placement: DeviceGenericModule inherit_from: - DeviceGenericModule attributes: - name: slot kind: Number description: The slot number where the Linecard is installed within the device order_weight: 1050 - name: bng_enabled label: BNG Enabled description: BNG activated or deactivated on the Linecard kind: Boolean optional: true default_value: false order_weight: 1400 relationships: - name: linecard_type label: Linecard Type peer: DeviceLinecardType optional: false cardinality: one kind: Attribute order_weight: 1150 - name: pics label: PICs peer: DevicePic optional: true cardinality: many kind: Attribute order_weight: 1500 - name: Pic namespace: Device description: Physical Interface Card (PIC) installed in the Linecard, containing multiple ports. label: PIC menu_placement: DeviceLinecard icon: mdi:memory uniqueness_constraints: - - linecard - slot__value human_friendly_id: - serial_number__value display_label: serial_number__value order_by: - linecard__serial_number__value attributes: - name: serial_number kind: Text unique: true description: Unique serial number of the PIC. order_weight: 1000 - name: slot kind: Number description: Slot number of the PIC within the Linecard order_weight: 1200 relationships: - name: linecard label: Linecard peer: DeviceLinecard identifier: linecard__pics optional: false cardinality: one kind: Parent order_weight: 1000 - name: ports label: Ports peer: DevicePort optional: true cardinality: many kind: Component order_weight: 1100 - name: Port namespace: Device description: A network port on a PIC, specifying speed and port number. label: Port menu_placement: DeviceLinecard icon: mdi:ethernet uniqueness_constraints: - - pic - port_number__value human_friendly_id: - pic__serial_number__value - port_number__value display_label: port_number__value order_by: - pic__serial_number__value attributes: - name: port_number kind: Number description: Port number on the PIC order_weight: 1100 - name: speed kind: Dropdown description: Speed of the port choices: - name: 10g label: 10Gbps description: 10 Gigabit per second color: '#A9CCE3' - name: 100g label: 100Gbps description: 100 Gigabit per second color: '#9fbdf2' order_weight: 1200 relationships: - name: pic label: PIC peer: DevicePic optional: false cardinality: one kind: Parent order_weight: 1000 ``` --- # Modules Routing Engine This schema extension allows you to capture Routing Engine related information like the version. You can insert the Routing Engine into a Dcim Physical Device and leverage the Routing Engine type model. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) * [extensions/modules](/schema-library/reference/modules.md) ## Nodes[​](#nodes "Direct link to Nodes") ### RoutingEngineType[​](#routingenginetype "Direct link to RoutingEngineType") * **Label:** Routing Engine Type * **Description:** Routing Engine Type information, detailing specifications such as part number and manufacturer. * **Namespace:** Device * **Inherit From:** DeviceGenericModuleType #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ---------------- | ------------------- | -------- | ----------- | ------- | | routing\_engines | DeviceRoutingEngine | | many | Generic | ### RoutingEngine[​](#routingengine "Direct link to RoutingEngine") * **Label:** Routing Engine * **Description:** A Routing Engine (RE) installed in a device, responsible for routing functionalities. * **Namespace:** Device * **Icon:** mdi :cpu-64-bit * **Uniqueness Constraints:** * serial\_number\_\_value * **Human Friendly ID:** device\_\_name\_\_value, slot\_\_value * **Inherit From:** DeviceGenericModule #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ------- | ----------------------------------------------------------------------- | ------ | -------- | -------------- | ------- | | slot | The slot number where the Routing Engine is installed within the device | Number | | | | | version | Firmware version of the Routing Engine. | Text | True | | | #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | --------------------- | ----------------------- | -------- | ----------- | --------- | | routing\_engine\_type | DeviceRoutingEngineType | False | one | Attribute | ## Code[​](#code "Direct link to Code") ``` version: '1.0' nodes: - name: RoutingEngineType namespace: Device description: Routing Engine Type information, detailing specifications such as part number and manufacturer. label: Routing Engine Type inherit_from: - DeviceGenericModuleType relationships: - name: routing_engines peer: DeviceRoutingEngine cardinality: many kind: Generic description: Routing engines of this type. - name: RoutingEngine namespace: Device description: A Routing Engine (RE) installed in a device, responsible for routing functionalities. label: Routing Engine icon: mdi:cpu-64-bit inherit_from: - DeviceGenericModule uniqueness_constraints: - - serial_number__value human_friendly_id: - device__name__value - slot__value order_by: - device__name__value - slot__value display_label: serial_number__value attributes: - name: slot kind: Number description: The slot number where the Routing Engine is installed within the device order_weight: 1100 - name: version kind: Text label: Version description: Firmware version of the Routing Engine. optional: true order_weight: 1200 relationships: - name: routing_engine_type label: RE Type peer: DeviceRoutingEngineType optional: false cardinality: one kind: Attribute order_weight: 1150 ``` --- # Optical Transport Comprehensive optical transport network schemas for DWDM/WDM systems (ADVA FSP 3000 and similar platforms). Covers four layers: wavelength (ITU-T G.694.1 grid, optical bands, DWDM channels), topology (logical optical nodes, passive multiplexers, fiber links), equipment (transponder/amplifier/ROADM modules, ROADM degrees, WSS cross-connects), and service (end-to-end optical services, optical paths, path segments). Not designed to be loaded together with extensions/dwdm. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) * [extensions/modules](/schema-library/reference/modules.md) * [extensions/cable](/schema-library/reference/cable.md) ## Nodes[​](#nodes "Direct link to Nodes") ### ITUGrid[​](#itugrid "Direct link to ITUGrid") * **Label:** ITU-T DWDM Grid * **Description:** ITU-T G.694.1 frequency grid standards for DWDM * **Namespace:** Dcim * **Icon:** mdi :grid * **Uniqueness Constraints:** * name\_\_value * **Human Friendly ID:** name\_\_value #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ------------------------- | ----------------------------------------------------- | ------ | -------- | -------------- | ------- | | name | Grid name (e.g., 50GHz, 100GHz) | Text | False | | | | channel\_spacing\_ghz | Channel spacing in GHz (50, 100, etc.) | Number | False | | | | reference\_frequency\_thz | Reference frequency in THz (e.g., '193.1' for 1550nm) | Text | False | | | | description | Grid description and use cases | Text | True | | | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ----- | --------------- | -------- | ----------- | --------- | | bands | DcimOpticalBand | True | many | Component | ### OpticalBand[​](#opticalband "Direct link to OpticalBand") * **Label:** Optical Band * **Description:** Optical frequency band (C-Band, L-Band, S-Band) * **Namespace:** Dcim * **Icon:** mdi :sine-wave * **Uniqueness Constraints:** * grid, band\_name\_\_value * **Human Friendly ID:** grid\_\_name\_\_value, band\_name\_\_value #### Attributes[​](#attributes-1 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | --------------------- | ---------------------------------------------------- | -------- | -------- | -------------- | ---------------------------------- | | band\_name | Optical band designation | Dropdown | False | | c\_band, l\_band, s\_band, o\_band | | start\_frequency\_thz | Band start frequency in THz (e.g., '191.0') | Text | False | | | | end\_frequency\_thz | Band end frequency in THz (e.g., '196.1') | Text | False | | | | start\_wavelength\_nm | Band start wavelength in nm (e.g., '1530.0') | Text | True | | | | end\_wavelength\_nm | Band end wavelength in nm (e.g., '1565.0') | Text | True | | | | computed\_name | Auto-generated band identifier (e.g., 50GHz-C\_BAND) | Text | False | | | #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | -------- | --------------- | -------- | ----------- | --------- | | grid | DcimITUGrid | False | one | Parent | | channels | DcimDWDMChannel | True | many | Component | ### DWDMChannel[​](#dwdmchannel "Direct link to DWDMChannel") * **Label:** DWDM Channel * **Description:** Individual DWDM wavelength/channel (CH20, CH21, etc.) * **Namespace:** Dcim * **Icon:** mdi :wave * **Uniqueness Constraints:** * band, channel\_number\_\_value * channel\_name\_\_value * channel\_number\_\_value, frequency\_thz\_\_value, wavelength\_nm\_\_value * **Human Friendly ID:** channel\_name\_\_value #### Attributes[​](#attributes-2 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ------------------- | ------------------------------------------------------------------------------------- | ------ | -------- | -------------- | ------- | | channel\_name | ITU DWDM Channel identifier (computed as CH + channel number, e.g., CH20, CH21, CH40) | Text | False | | | | adva\_channel\_name | ADVA-specific channel name (e.g., D02, DC1, D32) | Text | True | | | | channel\_number | ITU channel number (e.g., 20, 21, 60) | Number | False | | | | frequency\_thz | Center frequency in THz (e.g., '192.1') | Text | False | | | | wavelength\_nm | Center wavelength in nm (e.g., '1560.61') | Text | False | | | | description | Channel notes and usage | Text | True | | | #### Relationships[​](#relationships-2 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ---------------- | ------------------------ | -------- | ----------- | --------- | | band | DcimOpticalBand | False | one | Parent | | mux\_assignments | DcimMuxChannelAssignment | True | many | Component | | mappings | DcimChannelMapping | True | many | Generic | ### MuxChannelAssignment[​](#muxchannelassignment "Direct link to MuxChannelAssignment") * **Label:** Mux Channel Assignment * **Description:** Maps DWDM channel to multiplexer port (equipment configuration) * **Namespace:** Dcim * **Icon:** mdi :cable-data * **Uniqueness Constraints:** * optical\_device, port\_number\_\_value * optical\_device, channel * **Human Friendly ID:** optical\_device\_\_computed\_name\_\_value, port\_number\_\_value #### Attributes[​](#attributes-3 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | -------------- | ------------------------------------------------ | -------- | -------- | -------------- | ------------------------------------ | | port\_number | Physical port number on multiplexer (e.g., 1-96) | Number | False | | | | tx\_power\_dbm | Configured transmit power in dBm (e.g., '0.0') | Text | True | | | | status | Channel assignment status | Dropdown | False | configured | configured, active, disabled, failed | | computed\_name | Auto-generated assignment identifier | Text | False | | | #### Relationships[​](#relationships-3 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | --------------- | ----------------- | -------- | ----------- | --------- | | channel | DcimDWDMChannel | False | one | Attribute | | optical\_device | DcimOpticalDevice | False | one | Parent | ### FiberMapping[​](#fibermapping "Direct link to FiberMapping") * **Label:** Fiber Mapping * **Description:** Long-haul fiber link between degrees on DIFFERENT ROADMs/sites (kilometers distance) * **Namespace:** Dcim * **Icon:** mdi :fiber-optic * **Uniqueness Constraints:** * fiber\_link, channel * **Human Friendly ID:** fiber\_link\_\_link\_id\_\_value, channel\_\_channel\_name\_\_value * **Inherit From:** DcimChannelMapping #### Attributes[​](#attributes-4 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ------------------ | -------------------------------------------------- | -------- | -------- | -------------- | ------------------------------------------------- | | allocation\_status | Allocation status | Dropdown | False | reserved | reserved, allocated, in\_use, maintenance, failed | | tx\_power\_dbm | Measured transmit power in dBm (e.g., '0.5') | Text | True | | | | rx\_power\_dbm | Measured receive power in dBm (e.g., '-2.3') | Text | True | | | | osnr\_db | Optical Signal-to-Noise Ratio in dB (e.g., '25.5') | Text | True | | | | allocated\_date | Date channel was allocated | DateTime | True | | | | computed\_name | Auto-generated mapping identifier | Text | False | | | | description | Additional fiber mapping details | Text | True | | | #### Relationships[​](#relationships-4 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ----------- | ------------- | -------- | ----------- | --------- | | fiber\_link | DcimFiberLink | False | one | Attribute | ### OpticalNode[​](#opticalnode "Direct link to OpticalNode") * **Label:** Optical Node * **Description:** Logical network endpoint in optical topology (graph node, not physical device) * **Namespace:** Dcim * **Icon:** mdi :lan * **Uniqueness Constraints:** * name\_\_value * **Human Friendly ID:** name\_\_value * **Inherit From:** DcimOpticalDevice #### Attributes[​](#attributes-5 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | -------------- | ----------------------------------------------------- | -------- | -------- | -------------- | --------------------------------- | | computed\_name | Computed name for display | Text | False | | | | name | Logical node name (e.g., GENESIS-OL-10, SKYLINE-OL-1) | Text | False | | | | node\_type | Topology role of this node | Dropdown | False | | endpoint, intermediate, branching | | description | Node notes and function | Text | True | | | #### Relationships[​](#relationships-5 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | -------------------- | --------------------- | -------- | ----------- | --------- | | device | DcimPhysicalDevice | True | one | Attribute | | transponder\_modules | DcimTransponderModule | True | many | Attribute | | links | DcimFiberLink | True | many | Component | ### PassiveMultiplexer[​](#passivemultiplexer "Direct link to PassiveMultiplexer") * **Label:** Passive Multiplexer * **Description:** Passive CWDM/DWDM multiplexer (fixed port count, no modules) * **Namespace:** Dcim * **Icon:** mdi :resistor-nodes * **Uniqueness Constraints:** * name\_\_value * **Human Friendly ID:** name\_\_value * **Inherit From:** DcimPhysicalDevice #### Attributes[​](#attributes-6 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | -------------- | ------------------------------------------- | -------- | -------- | -------------- | ---------- | | computed\_name | Computed name for display | Text | False | | | | name | Passive mux name (e.g., CHI-PMUX-01) | Text | False | | | | port\_count | Number of fixed ports (e.g., 8, 16, 32, 40) | Number | False | | | | mux\_type | Type of passive multiplexer | Dropdown | False | | cwdm, dwdm | | description | Device notes | Text | True | | | ### FiberLink[​](#fiberlink "Direct link to FiberLink") * **Label:** Fiber Link * **Description:** Fiber connecting two optical nodes (graph edge) * **Namespace:** Dcim * **Icon:** mdi :cable-data * **Uniqueness Constraints:** * link\_id\_\_value * **Human Friendly ID:** link\_id\_\_value #### Attributes[​](#attributes-7 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ------------------ | ----------------------------------------------- | -------- | -------- | -------------- | --------------------------------------------- | | link\_id | Unique link identifier (e.g., LINK-001) | Text | False | | | | distance\_km | Physical distance in kilometers (e.g., '920.5') | Text | True | | | | attenuation\_db | Total fiber attenuation in dB (e.g., '18.4') | Text | True | | | | status | Operational status of fiber link | Dropdown | False | planned | planned, active, standby, maintenance, failed | | commissioned\_date | Date link was commissioned | DateTime | True | | | | description | Additional link details | Text | True | | | #### Relationships[​](#relationships-6 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | --------- | ---------------- | -------- | ----------- | --------- | | endpoints | DcimOpticalNode | False | many | Attribute | | mappings | DcimFiberMapping | True | many | Component | ### OpticalModuleType[​](#opticalmoduletype "Direct link to OpticalModuleType") * **Label:** Optical Module Type * **Description:** Categorization of optical modules by function * **Namespace:** Dcim * **Icon:** mdi :expansion-card * **Inherit From:** DeviceGenericModuleType #### Attributes[​](#attributes-8 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ---------------- | --------------------------------- | -------- | -------- | -------------- | ------------------------------------------------------------------------ | | name | Module type name | Text | False | | | | description | Module type description | Text | True | | | | module\_category | Functional category of the module | Dropdown | False | | transponder, multiplexer, amplifier, monitoring, control, power, cooling | ### TransponderModule[​](#transpondermodule "Direct link to TransponderModule") * **Label:** Transponder Module * **Description:** Optical-electrical-optical conversion module (100G, 400G coherent) installed in chassis * **Namespace:** Dcim * **Icon:** mdi :chip * **Uniqueness Constraints:** * computed\_name\_\_value * **Human Friendly ID:** computed\_name\_\_value * **Inherit From:** DeviceGenericModule #### Attributes[​](#attributes-9 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ------------------ | ----------------------------------------------------- | -------- | -------- | -------------- | ---------------------------------------- | | computed\_name | Computed module name (e.g., ABC123-TPD-1/1) | Text | False | | | | slot\_number | Slot number in chassis (e.g., '1/1', '1/2', '2/1') | Text | False | | | | capacity\_gbps | Line rate capacity in Gbps (e.g., 100, 200, 400) | Number | False | | | | modulation\_format | Modulation format for optical transmission | Dropdown | False | | dp\_qpsk, dp\_16qam, dp\_8qam, dp\_64qam | | tunable\_range | Wavelength tuning capability | Dropdown | False | | c\_band, c\_l\_band, fixed | | fec\_type | Forward error correction type | Dropdown | True | | sd\_fec, hd\_fec, c\_fec, o\_fec | | client\_interface | Client-side interface (e.g., 100GE, 400GE, OTU4) | Text | True | | | | line\_interface | Line-side interface (e.g., CFP2-DCO, QSFP28, QSFP-DD) | Text | True | | | #### Relationships[​](#relationships-7 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ---------------------------- | --------------- | -------- | ----------- | --------- | | tuned\_channel | DcimDWDMChannel | True | one | Attribute | | connected\_to\_optical\_node | DcimOpticalNode | True | one | Attribute | ### OpticalMultiplexerModule[​](#opticalmultiplexermodule "Direct link to OpticalMultiplexerModule") * **Label:** Optical Multiplexer Module * **Description:** Multiplexer/Demultiplexer/ROADM module for wavelength management installed in chassis * **Namespace:** Dcim * **Icon:** mdi :router-network * **Human Friendly ID:** computed\_name\_\_value * **Inherit From:** DeviceGenericModule #### Attributes[​](#attributes-10 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------------- | ------------------------------------------------------------------------ | -------- | -------- | -------------- | ---------------------------------------------- | | computed\_name | Computed module name (e.g., ABC123-MUX-1/3) | Text | False | | | | slot\_number | Slot number in chassis (e.g., '1/3', '1/4', '2/3') | Text | False | | | | mux\_type | Type of multiplexer technology | Dropdown | False | | passive\_mux, passive\_demux, oadm, roadm, wss | | channel\_capacity | Number of supported DWDM channels (e.g., 40, 80, 96) | Number | False | | | | technology | Underlying multiplexer technology | Dropdown | True | | thin\_film\_filter, awg, wss\_lcos, wss\_mems | | degree\_count | Number of ROADM degrees (e.g., 2, 4, 8, 16) | Number | True | | | | colorless | Colorless add/drop capability (any channel on any port) | Boolean | True | False | | | directionless | Directionless add/drop capability (any port to any degree) | Boolean | True | False | | | contentionless | Contentionless add/drop (CDC - Colorless, Directionless, Contentionless) | Boolean | True | False | | #### Relationships[​](#relationships-8 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | -------- | ------------------ | -------- | ----------- | --------- | | degrees | DcimRoadmDegree | True | many | Component | | mappings | DcimChannelMapping | True | many | Attribute | ### RoadmDegree[​](#roadmdegree "Direct link to RoadmDegree") * **Label:** ROADM Degree * **Description:** ROADM degree with line port (OL-1, OL-2, etc.) * **Namespace:** Dcim * **Icon:** mdi :lan-connect * **Uniqueness Constraints:** * roadm, degree\_number\_\_value * roadm, line\_port\_\_value * **Human Friendly ID:** computed\_name\_\_value #### Attributes[​](#attributes-11 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | -------------------- | ---------------------------------------------- | -------- | -------- | -------------- | ---------------------------------------- | | computed\_name | Computed name (e.g., ROADM-SITE-A-OL-1) | Text | False | | | | degree\_number | Degree number (1-based, e.g., 1, 2, 3, 4) | Number | False | | | | line\_port | Line port designation (e.g., OL-1, OL-2, OL-7) | Text | False | | | | direction | Directional designation of degree | Dropdown | False | | north, south, east, west, express, local | | degree\_type | Type of ROADM degree | Dropdown | False | | line, express, add\_drop | | wavelength\_capacity | Number of wavelengths supported on this degree | Number | True | | | | description | Additional degree details | Text | True | | | #### Relationships[​](#relationships-9 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ----------------- | ---------------------------- | -------- | ----------- | --------- | | roadm | DcimOpticalMultiplexerModule | False | one | Parent | | connected\_fiber | DcimFiberLink | True | one | Attribute | | channel\_mappings | DcimChannelMapping | True | many | Attribute | ### WSSConnect[​](#wssconnect "Direct link to WSSConnect") * **Label:** WSS Connect * **Description:** Internal WSS cross-connect between degrees on SAME ROADM device (0m distance) * **Namespace:** Dcim * **Icon:** mdi :swap-horizontal * **Uniqueness Constraints:** * roadm, channel * **Human Friendly ID:** computed\_name\_\_value * **Inherit From:** DcimChannelMapping #### Attributes[​](#attributes-12 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | -------------- | -------------------------------- | ---- | -------- | -------------- | ------- | | computed\_name | Computed connection ID | Text | False | | | | description | Additional cross-connect details | Text | True | | | #### Relationships[​](#relationships-10 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ----- | ---------------------------- | -------- | ----------- | --------- | | roadm | DcimOpticalMultiplexerModule | False | one | Attribute | ### CableMapping[​](#cablemapping "Direct link to CableMapping") * **Label:** Cable Mapping * **Description:** Local patch cable between degrees on DIFFERENT ROADMs (meters distance, same site) * **Namespace:** Dcim * **Icon:** mdi :cable-data * **Uniqueness Constraints:** * cable, channel * **Human Friendly ID:** computed\_name\_\_value * **Inherit From:** DcimChannelMapping #### Attributes[​](#attributes-13 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | -------------- | -------------------------------------------- | ---- | -------- | -------------- | ------- | | computed\_name | Computed mapping name (e.g., CABLE-123-CH58) | Text | False | | | | description | Additional cable mapping details | Text | True | | | #### Relationships[​](#relationships-11 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ----- | --------- | -------- | ----------- | --------- | | cable | DcimCable | False | one | Attribute | ### OpticalAmplifierModule[​](#opticalamplifiermodule "Direct link to OpticalAmplifierModule") * **Label:** Optical Amplifier Module * **Description:** Optical signal amplifier module (EDFA, Raman, VGC) installed in chassis * **Namespace:** Dcim * **Icon:** mdi :amplifier * **Human Friendly ID:** computed\_name\_\_value * **Inherit From:** DeviceGenericModule #### Attributes[​](#attributes-14 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------------------- | -------------------------------------------------- | -------- | -------- | -------------- | ----------------------------- | | computed\_name | Computed module name (e.g., ABC123-AMP-1/5) | Text | False | | | | slot\_number | Slot number in chassis (e.g., '1/5', '1/6', '2/5') | Text | False | | | | amplifier\_type | Type of optical amplifier | Dropdown | False | | edfa, raman, soa, vgc, hybrid | | stage | Amplifier stage position | Dropdown | False | | booster, inline, preamplifier | | gain\_db | Typical gain in dB (e.g., '20.0', '17.5') | Text | True | | | | max\_output\_power\_dbm | Maximum output power in dBm (e.g., '17.0', '23.0') | Text | True | | | | noise\_figure\_db | Noise figure in dB (e.g., '5.5', '4.8') | Text | True | | | | operating\_band | Operating wavelength band | Dropdown | False | | c\_band, l\_band, c\_l\_band | #### Relationships[​](#relationships-12 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | --------------- | ------------- | -------- | ----------- | --------- | | amplified\_link | DcimFiberLink | True | one | Attribute | ### OpticalService[​](#opticalservice "Direct link to OpticalService") * **Label:** Optical Service * **Description:** End-to-end optical transport service (customer circuit) spanning multiple sites * **Namespace:** Dcim * **Icon:** mdi :transit-connection-variant * **Uniqueness Constraints:** * service\_id\_\_value * **Human Friendly ID:** service\_id\_\_value #### Attributes[​](#attributes-15 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | --------------------- | ----------------------------------------------------------- | -------- | -------- | -------------- | ------------------------------------------------- | | service\_id | Unique service identifier | Text | False | | | | service\_name | Customer service name (e.g., 'ATC 10G - Albion to Arco #2') | Text | False | | | | service\_type | Type of optical service | Dropdown | False | | wavelength, transport, ip\_transit | | bandwidth | Service bandwidth (e.g., '10G', '100G', '400G') | Text | False | | | | status | Service operational status | Dropdown | False | planned | planned, provisioned, active, maintenance, failed | | customer\_circuit\_id | Customer's circuit ID (e.g., '99/OKFS/000029//SYG') | Text | True | | | | provision\_date | Date service was provisioned | DateTime | True | | | | description | Additional service details | Text | True | | | #### Relationships[​](#relationships-13 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------- | --------------------- | -------- | ----------- | --------- | | transponders | DcimTransponderModule | False | many | Attribute | | channel | DcimDWDMChannel | False | one | Attribute | | primary\_path | DcimOpticalPath | False | one | Component | | backup\_path | DcimOpticalPath | True | one | Component | ### OpticalPath[​](#opticalpath "Direct link to OpticalPath") * **Label:** Optical Path * **Description:** Ordered sequence of segments forming an optical path through the network * **Namespace:** Dcim * **Icon:** mdi :map-marker-path * **Human Friendly ID:** name\_\_value #### Attributes[​](#attributes-16 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ------------------- | --------------------------------------- | -------- | -------- | -------------- | ------------------------ | | name | Path name (e.g., MS-W17D5-primary) | Text | False | | | | path\_type | Type of path (primary, backup, express) | Dropdown | False | | primary, backup, express | | total\_distance\_km | Total path distance in kilometers | Text | True | | | | total\_loss\_db | Total optical loss in dB | Text | True | | | | hop\_count | Number of ROADM hops in path | Number | True | | | | is\_active | Whether this path is currently active | Boolean | False | True | | #### Relationships[​](#relationships-14 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------------------ | ------------------ | -------- | ----------- | --------- | | segments | DcimPathSegment | True | many | Component | | is\_primary\_of\_service | DcimOpticalService | True | one | Attribute | | is\_backup\_of\_service | DcimOpticalService | True | one | Attribute | ### PathSegment[​](#pathsegment "Direct link to PathSegment") * **Label:** Path Segment * **Description:** One hop in optical path - references channel mapping (fiber, cable, or cross-connect) * **Namespace:** Dcim * **Icon:** mdi :ray-start-arrow * **Uniqueness Constraints:** * path, segment\_order\_\_value * **Human Friendly ID:** computed\_name\_\_value #### Attributes[​](#attributes-17 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | -------------- | --------------------------------------------------- | -------- | -------- | -------------- | --------------------------------------- | | computed\_name | Computed segment name (e.g., MS-W17D5-primary-SEG1) | Text | False | | | | segment\_order | Order of this segment in the path (1, 2, 3...) | Number | False | | | | segment\_type | Type of segment | Dropdown | False | | fiber, cross\_connect, cable, add, drop | | loss\_db | Optical loss for this segment in dB | Text | True | | | #### Relationships[​](#relationships-15 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ---------------- | ------------------ | -------- | ----------- | --------- | | path | DcimOpticalPath | False | one | Parent | | channel\_mapping | DcimChannelMapping | True | one | Attribute | ## Generics[​](#generics "Direct link to Generics") ### ChannelMapping[​](#channelmapping "Direct link to ChannelMapping") * **Label:** Channel Mapping * **Description:** Generic base for any channel allocation/routing (fiber, cable, or internal cross-connect) * **Namespace:** Dcim #### Attributes[​](#attributes-18 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ---------------- | ---------------------------------------- | -------- | -------- | -------------- | ------- | | is\_active | Whether this mapping is currently active | Boolean | False | True | | | configured\_date | Date this mapping was configured | DateTime | True | | | #### Relationships[​](#relationships-16 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------- | --------------- | -------- | ----------- | --------- | | segment | DcimPathSegment | False | one | Parent | | channel | DcimDWDMChannel | False | one | Attribute | | degrees | DcimRoadmDegree | False | many | Attribute | ### OpticalDevice[​](#opticaldevice "Direct link to OpticalDevice") * **Label:** Optical Multiplexer Device * **Description:** Generic interface for any device that can multiplex DWDM channels (active or passive) * **Namespace:** Dcim * **Uniqueness Constraints:** * computed\_name\_\_value #### Attributes[​](#attributes-19 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | -------------- | ----------- | ---- | -------- | -------------- | ------- | | computed\_name | | Text | False | | | ## Extensions[​](#extensions "Direct link to Extensions") note In this context "extensions" refer to modifications or additions to the existing schema, such as adding new attributes, relationships, or other schema elements. ### DcimPhysicalDevice[​](#dcimphysicaldevice "Direct link to DcimPhysicalDevice") #### Relationships[​](#relationships-17 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------- | --------------- | -------- | ----------- | ------- | | optical\_node | DcimOpticalNode | True | many | Generic | ## Code[​](#code "Direct link to Code") ``` version: '1.0' generics: - name: ChannelMapping namespace: Dcim description: Generic base for any channel allocation/routing (fiber, cable, or internal cross-connect) label: Channel Mapping include_in_menu: true attributes: - name: is_active kind: Boolean optional: false default_value: true description: Whether this mapping is currently active - name: configured_date kind: DateTime optional: true description: Date this mapping was configured relationships: - name: segment peer: DcimPathSegment cardinality: one kind: Parent optional: false identifier: channel_mapping__path_segments description: Optical Path segment associated with this mapping - name: channel peer: DcimDWDMChannel cardinality: one kind: Attribute optional: false identifier: dwdm_channel__mappings description: DWDM channel being mapped/routed - name: degrees peer: DcimRoadmDegree cardinality: many max_count: 2 kind: Attribute optional: false identifier: roadm_degree__channel_mappings description: Two ROADM degrees involved in this mapping (bidirectional) - name: OpticalDevice namespace: Dcim description: Generic interface for any device that can multiplex DWDM channels (active or passive) label: Optical Multiplexer Device include_in_menu: true uniqueness_constraints: - - computed_name__value attributes: - name: computed_name kind: Text optional: false nodes: - name: ITUGrid namespace: Dcim include_in_menu: true label: ITU-T DWDM Grid description: ITU-T G.694.1 frequency grid standards for DWDM icon: mdi:grid display_label: name__value human_friendly_id: - name__value uniqueness_constraints: - - name__value attributes: - name: name kind: Text optional: false description: Grid name (e.g., 50GHz, 100GHz) - name: channel_spacing_ghz kind: Number optional: false description: Channel spacing in GHz (50, 100, etc.) - name: reference_frequency_thz kind: Text optional: false description: Reference frequency in THz (e.g., '193.1' for 1550nm) - name: description kind: Text optional: true description: Grid description and use cases relationships: - name: bands peer: DcimOpticalBand cardinality: many kind: Component optional: true description: Optical bands using this grid - name: OpticalBand namespace: Dcim include_in_menu: true label: Optical Band description: Optical frequency band (C-Band, L-Band, S-Band) icon: mdi:sine-wave display_label: computed_name__value human_friendly_id: - grid__name__value - band_name__value uniqueness_constraints: - - grid - band_name__value attributes: - name: band_name kind: Dropdown optional: false choices: - name: c_band label: C-Band description: Conventional Band (1530-1565 nm) color: '#ff6b6b' - name: l_band label: L-Band description: Long Band (1565-1625 nm) color: '#4ecdc4' - name: s_band label: S-Band description: Short Band (1460-1530 nm) color: '#95e1d3' - name: o_band label: O-Band description: Original Band (1260-1360 nm) color: '#f38181' description: Optical band designation - name: start_frequency_thz kind: Text optional: false description: Band start frequency in THz (e.g., '191.0') - name: end_frequency_thz kind: Text optional: false description: Band end frequency in THz (e.g., '196.1') - name: start_wavelength_nm kind: Text optional: true description: Band start wavelength in nm (e.g., '1530.0') - name: end_wavelength_nm kind: Text optional: true description: Band end wavelength in nm (e.g., '1565.0') - name: computed_name kind: Text read_only: true optional: false computed_attribute: kind: Jinja2 jinja2_template: '{{ grid__name__value }}-{{ band_name__value|upper }}' description: Auto-generated band identifier (e.g., 50GHz-C_BAND) relationships: - name: grid peer: DcimITUGrid cardinality: one kind: Parent optional: false description: Parent ITU grid - name: channels peer: DcimDWDMChannel cardinality: many kind: Component optional: true description: DWDM channels in this band - name: DWDMChannel namespace: Dcim include_in_menu: true label: DWDM Channel description: Individual DWDM wavelength/channel (CH20, CH21, etc.) icon: mdi:wave display_label: channel_name__value human_friendly_id: - channel_name__value order_by: - band__computed_name__value - channel_number__value uniqueness_constraints: - - band - channel_number__value - - channel_name__value - - channel_number__value - frequency_thz__value - wavelength_nm__value attributes: - name: channel_name kind: Text read_only: true optional: false computed_attribute: kind: Jinja2 jinja2_template: CH{{ channel_number__value }} description: ITU DWDM Channel identifier (computed as CH + channel number, e.g., CH20, CH21, CH40) - name: adva_channel_name kind: Text optional: true description: ADVA-specific channel name (e.g., D02, DC1, D32) - name: channel_number kind: Number optional: false description: ITU channel number (e.g., 20, 21, 60) - name: frequency_thz kind: Text optional: false description: Center frequency in THz (e.g., '192.1') - name: wavelength_nm kind: Text optional: false description: Center wavelength in nm (e.g., '1560.61') - name: description kind: Text optional: true description: Channel notes and usage relationships: - name: band peer: DcimOpticalBand cardinality: one kind: Parent optional: false description: Parent optical band - name: mux_assignments peer: DcimMuxChannelAssignment cardinality: many kind: Component optional: true description: Mux port assignments for this channel - name: mappings peer: DcimChannelMapping cardinality: many kind: Generic optional: true description: Fiber allocations for this channel - name: MuxChannelAssignment namespace: Dcim include_in_menu: true label: Mux Channel Assignment description: Maps DWDM channel to multiplexer port (equipment configuration) icon: mdi:cable-data display_label: computed_name__value human_friendly_id: - optical_device__computed_name__value - port_number__value uniqueness_constraints: - - optical_device - port_number__value - - optical_device - channel attributes: - name: port_number kind: Number optional: false description: Physical port number on multiplexer (e.g., 1-96) - name: tx_power_dbm kind: Text optional: true description: Configured transmit power in dBm (e.g., '0.0') - name: status kind: Dropdown optional: false default_value: configured choices: - name: configured label: Configured description: Channel is configured on port color: '#f5a623' - name: active label: Active description: Channel is actively transmitting color: '#00cc00' - name: disabled label: Disabled description: Channel is disabled color: '#cccccc' - name: failed label: Failed description: Channel has failed color: '#cc0000' description: Channel assignment status - name: computed_name kind: Text read_only: true optional: false computed_attribute: kind: Jinja2 jinja2_template: '{{ optical_device__computed_name__value }}-P{{ ''{:02d}''.format(port_number__value) }}-{{ channel__channel_name__value }}' description: Auto-generated assignment identifier relationships: - name: channel peer: DcimDWDMChannel cardinality: one kind: Attribute optional: false description: DWDM channel assigned to this port - name: optical_device peer: DcimOpticalDevice cardinality: one kind: Parent optional: false identifier: mux_channel_assignments description: Multiplexer device (OpticalNode or PassiveMultiplexer) - name: FiberMapping namespace: Dcim inherit_from: - DcimChannelMapping include_in_menu: true label: Fiber Mapping description: Long-haul fiber link between degrees on DIFFERENT ROADMs/sites (kilometers distance) icon: mdi:fiber-optic display_label: computed_name__value human_friendly_id: - fiber_link__link_id__value - channel__channel_name__value uniqueness_constraints: - - fiber_link - channel attributes: - name: allocation_status kind: Dropdown optional: false default_value: reserved choices: - name: reserved label: Reserved description: Channel reserved but not in use color: '#f5a623' - name: allocated label: Allocated description: Channel allocated to service color: '#0099cc' - name: in_use label: In Use description: Channel actively carrying traffic color: '#00cc00' - name: maintenance label: Maintenance description: Channel under maintenance color: '#ff9800' - name: failed label: Failed description: Channel has failed color: '#cc0000' description: Allocation status - name: tx_power_dbm kind: Text optional: true description: Measured transmit power in dBm (e.g., '0.5') - name: rx_power_dbm kind: Text optional: true description: Measured receive power in dBm (e.g., '-2.3') - name: osnr_db kind: Text optional: true description: Optical Signal-to-Noise Ratio in dB (e.g., '25.5') - name: allocated_date kind: DateTime optional: true description: Date channel was allocated - name: computed_name kind: Text read_only: true optional: false computed_attribute: kind: Jinja2 jinja2_template: '{{ fiber_link__link_id__value }}-{{ channel__channel_name__value }}' description: Auto-generated mapping identifier - name: description kind: Text optional: true description: Additional fiber mapping details relationships: - name: fiber_link peer: DcimFiberLink cardinality: one kind: Attribute optional: false identifier: fiber_channel_mappings description: Long-haul fiber link connecting the degrees - name: OpticalNode namespace: Dcim include_in_menu: true label: Optical Node description: Logical network endpoint in optical topology (graph node, not physical device) icon: mdi:lan inherit_from: - DcimOpticalDevice display_label: computed_name__value human_friendly_id: - name__value uniqueness_constraints: - - name__value attributes: - name: computed_name kind: Text read_only: true optional: false computed_attribute: kind: Jinja2 jinja2_template: '{{ name__value }}' description: Computed name for display - name: name kind: Text optional: false description: Logical node name (e.g., GENESIS-OL-10, SKYLINE-OL-1) - name: node_type kind: Dropdown optional: false choices: - name: endpoint label: Endpoint description: Edge node (traffic originates/terminates) color: '#2196f3' - name: intermediate label: Intermediate description: Pass-through node (ROADM only) color: '#9c27b0' - name: branching label: Branching description: Multiple fiber paths meet (hub) color: '#ff9800' description: Topology role of this node - name: description kind: Text optional: true description: Node notes and function relationships: - name: device peer: DcimPhysicalDevice cardinality: one kind: Attribute optional: true identifier: optical_nodes__physical_device description: Physical chassis that implements this logical node - name: transponder_modules peer: DcimTransponderModule cardinality: many kind: Attribute optional: true identifier: optical_node__transponder_modules description: Transponder modules connected to this optical node (for cross-connects) - name: links peer: DcimFiberLink cardinality: many kind: Component optional: true identifier: optical_node__links description: Fiber links connected to this node - name: PassiveMultiplexer namespace: Dcim include_in_menu: true label: Passive Multiplexer description: Passive CWDM/DWDM multiplexer (fixed port count, no modules) icon: mdi:resistor-nodes inherit_from: - DcimPhysicalDevice display_label: computed_name__value human_friendly_id: - name__value uniqueness_constraints: - - name__value attributes: - name: computed_name kind: Text read_only: true optional: false computed_attribute: kind: Jinja2 jinja2_template: '{{ name__value }}' description: Computed name for display - name: name kind: Text optional: false description: Passive mux name (e.g., CHI-PMUX-01) - name: port_count kind: Number optional: false description: Number of fixed ports (e.g., 8, 16, 32, 40) - name: mux_type kind: Dropdown optional: false choices: - name: cwdm label: CWDM description: Coarse Wavelength Division Multiplexing color: '#4a90e2' - name: dwdm label: DWDM description: Dense Wavelength Division Multiplexing color: '#9933cc' description: Type of passive multiplexer - name: description kind: Text optional: true description: Device notes - name: FiberLink namespace: Dcim include_in_menu: true label: Fiber Link description: Fiber connecting two optical nodes (graph edge) icon: mdi:cable-data display_label: link_id__value human_friendly_id: - link_id__value uniqueness_constraints: - - link_id__value attributes: - name: link_id kind: Text optional: false description: Unique link identifier (e.g., LINK-001) - name: distance_km kind: Text optional: true description: Physical distance in kilometers (e.g., '920.5') - name: attenuation_db kind: Text optional: true description: Total fiber attenuation in dB (e.g., '18.4') - name: status kind: Dropdown optional: false default_value: planned choices: - name: planned label: Planned description: Link is planned but not yet installed color: '#95a5a6' - name: active label: Active description: Link is in service color: '#00cc00' - name: standby label: Standby description: Link is installed but not active color: '#f5a623' - name: maintenance label: Maintenance description: Link is under maintenance color: '#ff9800' - name: failed label: Failed description: Link has failed color: '#cc0000' description: Operational status of fiber link - name: commissioned_date kind: DateTime optional: true description: Date link was commissioned - name: description kind: Text optional: true description: Additional link details relationships: - name: endpoints peer: DcimOpticalNode identifier: optical_node__links cardinality: many kind: Attribute optional: false max_count: 2 description: Port-level endpoints participating in this link - name: mappings peer: DcimFiberMapping cardinality: many kind: Component identifier: fiber_channel_mappings optional: true description: DWDM channels allocated on this link - name: OpticalModuleType namespace: Dcim include_in_menu: true label: Optical Module Type description: Categorization of optical modules by function icon: mdi:expansion-card inherit_from: - DeviceGenericModuleType attributes: - name: name kind: Text optional: false description: Module type name - name: description kind: Text optional: true description: Module type description - name: module_category kind: Dropdown optional: false description: Functional category of the module choices: - name: transponder label: Transponder description: Optical-electrical-optical conversion color: '#2196f3' - name: multiplexer label: Multiplexer/ROADM description: Wavelength multiplexing and switching color: '#9c27b0' - name: amplifier label: Amplifier description: Optical signal amplification color: '#ff9800' - name: monitoring label: Monitoring description: Optical performance monitoring color: '#4caf50' - name: control label: Control description: System control and management color: '#607d8b' - name: power label: Power Supply description: Power management modules color: '#795548' - name: cooling label: Cooling description: Thermal management color: '#00bcd4' - name: TransponderModule namespace: Dcim include_in_menu: true label: Transponder Module description: Optical-electrical-optical conversion module (100G, 400G coherent) installed in chassis icon: mdi:chip inherit_from: - DeviceGenericModule display_label: computed_name__value uniqueness_constraints: - - computed_name__value human_friendly_id: - computed_name__value attributes: - name: computed_name kind: Text read_only: true optional: false computed_attribute: kind: Jinja2 jinja2_template: '{{ device__serial__value }}-TPD-{{ slot_number__value }}' description: Computed module name (e.g., ABC123-TPD-1/1) - name: slot_number kind: Text optional: false description: Slot number in chassis (e.g., '1/1', '1/2', '2/1') - name: capacity_gbps kind: Number optional: false description: Line rate capacity in Gbps (e.g., 100, 200, 400) - name: modulation_format kind: Dropdown optional: false choices: - name: dp_qpsk label: DP-QPSK description: Dual-polarization QPSK (100G) color: '#2196f3' - name: dp_16qam label: DP-16QAM description: Dual-polarization 16QAM (200G) color: '#9c27b0' - name: dp_8qam label: DP-8QAM description: Dual-polarization 8QAM (150G) color: '#ff9800' - name: dp_64qam label: DP-64QAM description: Dual-polarization 64QAM (400G) color: '#4caf50' description: Modulation format for optical transmission - name: tunable_range kind: Dropdown optional: false choices: - name: c_band label: C-Band (1530-1565nm) description: Full C-Band tunable color: '#9933cc' - name: c_l_band label: C+L-Band (1530-1625nm) description: Extended C+L-Band tunable color: '#cc33cc' - name: fixed label: Fixed Wavelength description: Non-tunable, fixed wavelength color: '#95a5a6' description: Wavelength tuning capability - name: fec_type kind: Dropdown optional: true choices: - name: sd_fec label: SD-FEC description: Soft-decision FEC (7% overhead) color: '#2196f3' - name: hd_fec label: HD-FEC description: Hard-decision FEC (25% overhead) color: '#ff9800' - name: c_fec label: C-FEC description: Concatenated FEC color: '#4caf50' - name: o_fec label: O-FEC description: OpenFEC color: '#9c27b0' description: Forward error correction type - name: client_interface kind: Text optional: true description: Client-side interface (e.g., 100GE, 400GE, OTU4) - name: line_interface kind: Text optional: true description: Line-side interface (e.g., CFP2-DCO, QSFP28, QSFP-DD) relationships: - name: tuned_channel peer: DcimDWDMChannel cardinality: one kind: Attribute optional: true identifier: dwdm_channel__transponder_modules description: DWDM channel currently tuned/configured - name: connected_to_optical_node peer: DcimOpticalNode cardinality: one kind: Attribute optional: true identifier: optical_node__transponder_modules description: Optical node this transponder module connects to (for cross-connects) - name: OpticalMultiplexerModule namespace: Dcim include_in_menu: true label: Optical Multiplexer Module description: Multiplexer/Demultiplexer/ROADM module for wavelength management installed in chassis icon: mdi:router-network inherit_from: - DeviceGenericModule display_label: computed_name__value human_friendly_id: - computed_name__value attributes: - name: computed_name kind: Text read_only: true optional: false computed_attribute: kind: Jinja2 jinja2_template: '{{ device__serial__value }}-MUX-{{ slot_number__value }}' description: Computed module name (e.g., ABC123-MUX-1/3) - name: slot_number kind: Text optional: false description: Slot number in chassis (e.g., '1/3', '1/4', '2/3') - name: mux_type kind: Dropdown optional: false choices: - name: passive_mux label: Passive Mux description: Passive wavelength multiplexer (fixed) color: '#95a5a6' - name: passive_demux label: Passive Demux description: Passive wavelength demultiplexer (fixed) color: '#7f8c8d' - name: oadm label: OADM description: Optical Add-Drop Multiplexer color: '#3498db' - name: roadm label: ROADM description: Reconfigurable Optical Add-Drop Multiplexer color: '#9b59b6' - name: wss label: WSS description: Wavelength Selective Switch color: '#e74c3c' description: Type of multiplexer technology - name: channel_capacity kind: Number optional: false description: Number of supported DWDM channels (e.g., 40, 80, 96) - name: technology kind: Dropdown optional: true choices: - name: thin_film_filter label: Thin Film Filter description: Passive thin film filter technology color: '#95a5a6' - name: awg label: AWG description: Arrayed Waveguide Grating color: '#3498db' - name: wss_lcos label: WSS (LCOS) description: Wavelength Selective Switch (Liquid Crystal on Silicon) color: '#9b59b6' - name: wss_mems label: WSS (MEMS) description: Wavelength Selective Switch (Micro-Electro-Mechanical Systems) color: '#e74c3c' description: Underlying multiplexer technology - name: degree_count kind: Number optional: true description: Number of ROADM degrees (e.g., 2, 4, 8, 16) - name: colorless kind: Boolean optional: true default_value: false description: Colorless add/drop capability (any channel on any port) - name: directionless kind: Boolean optional: true default_value: false description: Directionless add/drop capability (any port to any degree) - name: contentionless kind: Boolean optional: true default_value: false description: Contentionless add/drop (CDC - Colorless, Directionless, Contentionless) relationships: - name: degrees peer: DcimRoadmDegree cardinality: many kind: Component optional: true identifier: optical_multiplexer_module__degrees description: ROADM degrees (line ports) on this multiplexer module - name: mappings peer: DcimChannelMapping cardinality: many kind: Attribute optional: true identifier: roadm_module__channel_mappings description: Channel mappings (DirectConnect, CableMapping, FiberMapping) associated with this module - name: RoadmDegree namespace: Dcim include_in_menu: true label: ROADM Degree description: ROADM degree with line port (OL-1, OL-2, etc.) icon: mdi:lan-connect display_label: computed_name__value human_friendly_id: - computed_name__value uniqueness_constraints: - - roadm - degree_number__value - - roadm - line_port__value attributes: - name: computed_name kind: Text read_only: true optional: false computed_attribute: kind: Jinja2 jinja2_template: '{{ roadm__computed_name__value }}-{{ line_port__value }}' description: Computed name (e.g., ROADM-SITE-A-OL-1) - name: degree_number kind: Number optional: false description: Degree number (1-based, e.g., 1, 2, 3, 4) - name: line_port kind: Text optional: false description: Line port designation (e.g., OL-1, OL-2, OL-7) - name: direction kind: Dropdown optional: false choices: - name: north label: North description: Northbound direction color: '#3498db' - name: south label: South description: Southbound direction color: '#e74c3c' - name: east label: East description: Eastbound direction color: '#2ecc71' - name: west label: West description: Westbound direction color: '#f39c12' - name: express label: Express description: Express port (bypass) color: '#9b59b6' - name: local label: Local description: Local add/drop color: '#95a5a6' description: Directional designation of degree - name: degree_type kind: Dropdown optional: false choices: - name: line label: Line description: Line port (connects to fiber link) color: '#2196f3' - name: express label: Express description: Express port (R-x ports, bypass) color: '#9c27b0' - name: add_drop label: Add/Drop description: Local add/drop for transponders color: '#4caf50' description: Type of ROADM degree - name: wavelength_capacity kind: Number optional: true description: Number of wavelengths supported on this degree - name: description kind: Text optional: true description: Additional degree details relationships: - name: roadm peer: DcimOpticalMultiplexerModule cardinality: one kind: Parent optional: false identifier: optical_multiplexer_module__degrees description: Parent ROADM module containing this degree - name: connected_fiber peer: DcimFiberLink cardinality: one kind: Attribute optional: true identifier: fiber_link__roadm_degree description: Fiber link connected to this line port - name: channel_mappings peer: DcimChannelMapping cardinality: many kind: Attribute optional: true identifier: roadm_degree__channel_mappings description: Channel mappings involving this degree - name: WSSConnect namespace: Dcim inherit_from: - DcimChannelMapping include_in_menu: true label: WSS Connect description: Internal WSS cross-connect between degrees on SAME ROADM device (0m distance) icon: mdi:swap-horizontal display_label: computed_name__value human_friendly_id: - computed_name__value uniqueness_constraints: - - roadm - channel attributes: - name: computed_name kind: Text read_only: true optional: false computed_attribute: kind: Jinja2 jinja2_template: '{{ roadm__computed_name__value }}-{{ channel__channel_name__value }}' description: Computed connection ID - name: description kind: Text optional: true description: Additional cross-connect details relationships: - name: roadm peer: DcimOpticalMultiplexerModule cardinality: one kind: Attribute optional: false identifier: roadm_module__channel_mappings description: Parent ROADM device - name: CableMapping namespace: Dcim inherit_from: - DcimChannelMapping include_in_menu: true label: Cable Mapping description: Local patch cable between degrees on DIFFERENT ROADMs (meters distance, same site) icon: mdi:cable-data display_label: computed_name__value human_friendly_id: - computed_name__value uniqueness_constraints: - - cable - channel attributes: - name: computed_name kind: Text read_only: true optional: false computed_attribute: kind: Jinja2 jinja2_template: '{{ cable__label__value }}-{{ channel__channel_name__value }}' description: Computed mapping name (e.g., CABLE-123-CH58) - name: description kind: Text optional: true description: Additional cable mapping details relationships: - name: cable peer: DcimCable cardinality: one kind: Attribute optional: false identifier: cable__channel_mappings description: Physical patch cable connecting the degrees - name: OpticalAmplifierModule namespace: Dcim include_in_menu: true label: Optical Amplifier Module description: Optical signal amplifier module (EDFA, Raman, VGC) installed in chassis icon: mdi:amplifier inherit_from: - DeviceGenericModule display_label: computed_name__value human_friendly_id: - computed_name__value attributes: - name: computed_name kind: Text read_only: true optional: false computed_attribute: kind: Jinja2 jinja2_template: '{{ device__serial__value }}-AMP-{{ slot_number__value }}' description: Computed module name (e.g., ABC123-AMP-1/5) - name: slot_number kind: Text optional: false description: Slot number in chassis (e.g., '1/5', '1/6', '2/5') - name: amplifier_type kind: Dropdown optional: false choices: - name: edfa label: EDFA description: Erbium-Doped Fiber Amplifier color: '#2196f3' - name: raman label: Raman description: Raman Amplifier (distributed) color: '#9c27b0' - name: soa label: SOA description: Semiconductor Optical Amplifier color: '#ff9800' - name: vgc label: VGC description: Variable Gain Controller color: '#4caf50' - name: hybrid label: Hybrid description: Hybrid amplifier (e.g., EDFA + Raman) color: '#607d8b' description: Type of optical amplifier - name: stage kind: Dropdown optional: false choices: - name: booster label: Booster description: Power amplifier (transmit side) color: '#e74c3c' - name: inline label: Inline description: In-line amplifier (along fiber span) color: '#3498db' - name: preamplifier label: Pre-Amplifier description: Low-noise amplifier (receive side) color: '#2ecc71' description: Amplifier stage position - name: gain_db kind: Text optional: true description: Typical gain in dB (e.g., '20.0', '17.5') - name: max_output_power_dbm kind: Text optional: true description: Maximum output power in dBm (e.g., '17.0', '23.0') - name: noise_figure_db kind: Text optional: true description: Noise figure in dB (e.g., '5.5', '4.8') - name: operating_band kind: Dropdown optional: false choices: - name: c_band label: C-Band (1530-1565nm) description: C-Band operation color: '#9933cc' - name: l_band label: L-Band (1565-1625nm) description: L-Band operation color: '#cc33cc' - name: c_l_band label: C+L-Band description: Dual-band operation color: '#ff33cc' description: Operating wavelength band relationships: - name: amplified_link peer: DcimFiberLink cardinality: one kind: Attribute optional: true identifier: fiber_link__amplifier_modules description: Fiber link being amplified (for inline amplifiers) - name: OpticalService namespace: Dcim include_in_menu: true label: Optical Service description: End-to-end optical transport service (customer circuit) spanning multiple sites icon: mdi:transit-connection-variant display_label: service_name__value human_friendly_id: - service_id__value uniqueness_constraints: - - service_id__value order_by: - service_id__value attributes: - name: service_id kind: Text optional: false description: Unique service identifier - name: service_name kind: Text optional: false description: 'Customer service name (e.g., ''ATC 10G - Albion to Arco #2'')' - name: service_type kind: Dropdown optional: false choices: - name: wavelength label: Wavelength Service description: Dedicated wavelength end-to-end color: '#9c27b0' - name: transport label: Transport Service description: Layer 1 optical transport color: '#2196f3' - name: ip_transit label: IP Transit description: Layer 3 IP service over optical color: '#4caf50' description: Type of optical service - name: bandwidth kind: Text optional: false description: Service bandwidth (e.g., '10G', '100G', '400G') - name: status kind: Dropdown optional: false default_value: planned choices: - name: planned label: Planned description: Service planned but not provisioned color: '#95a5a6' - name: provisioned label: Provisioned description: Service configured but not active color: '#f5a623' - name: active label: Active description: Service in production color: '#00cc00' - name: maintenance label: Maintenance description: Service under maintenance color: '#ff9800' - name: failed label: Failed description: Service down color: '#cc0000' description: Service operational status - name: customer_circuit_id kind: Text optional: true description: Customer's circuit ID (e.g., '99/OKFS/000029//SYG') - name: provision_date kind: DateTime optional: true description: Date service was provisioned - name: description kind: Text optional: true description: Additional service details relationships: - name: transponders peer: DcimTransponderModule cardinality: many min_count: 2 max_count: 2 kind: Attribute optional: false identifier: transponder_module__optical_services description: Two transponders (endpoints) for this service (bidirectional) - name: channel peer: DcimDWDMChannel cardinality: one kind: Attribute optional: false identifier: dwdm_channel__services description: DWDM channel used for this service - name: primary_path peer: DcimOpticalPath cardinality: one kind: Component optional: false identifier: optical_service__primary_path description: Primary optical path for this service - name: backup_path peer: DcimOpticalPath cardinality: one kind: Component optional: true identifier: optical_service__backup_path description: Backup/protection path for this service - name: OpticalPath namespace: Dcim include_in_menu: true label: Optical Path description: Ordered sequence of segments forming an optical path through the network icon: mdi:map-marker-path display_label: name__value human_friendly_id: - name__value attributes: - name: name kind: Text optional: false unique: true description: Path name (e.g., MS-W17D5-primary) - name: path_type kind: Dropdown optional: false choices: - name: primary label: Primary description: Primary working path color: '#2196f3' - name: backup label: Backup description: Backup/protection path color: '#ff9800' - name: express label: Express description: Express bypass path color: '#9c27b0' description: Type of path (primary, backup, express) - name: total_distance_km kind: Text optional: true description: Total path distance in kilometers - name: total_loss_db kind: Text optional: true description: Total optical loss in dB - name: hop_count kind: Number optional: true description: Number of ROADM hops in path - name: is_active kind: Boolean optional: false default_value: true description: Whether this path is currently active relationships: - name: segments peer: DcimPathSegment identifier: optical_path__segments cardinality: many kind: Component optional: true description: Ordered segments forming this path - name: is_primary_of_service peer: DcimOpticalService identifier: optical_service__primary_path cardinality: one kind: Attribute optional: true description: Optical service using this path - name: is_backup_of_service peer: DcimOpticalService identifier: optical_service__backup_path cardinality: one kind: Attribute optional: true description: Optical service using this path as backup - name: PathSegment namespace: Dcim include_in_menu: true label: Path Segment description: One hop in optical path - references channel mapping (fiber, cable, or cross-connect) icon: mdi:ray-start-arrow display_label: computed_name__value human_friendly_id: - computed_name__value uniqueness_constraints: - - path - segment_order__value attributes: - name: computed_name kind: Text read_only: true optional: false computed_attribute: kind: Jinja2 jinja2_template: '{{ path__name__value }}-SEG{{ segment_order__value }}' description: Computed segment name (e.g., MS-W17D5-primary-SEG1) - name: segment_order kind: Number optional: false description: Order of this segment in the path (1, 2, 3...) - name: segment_type kind: Dropdown optional: false choices: - name: fiber label: Fiber description: Segment traverses a long-haul fiber link color: '#2196f3' - name: cross_connect label: Cross-Connect description: Segment crosses through ROADM degrees (internal WSS) color: '#9c27b0' - name: cable label: Cable description: Segment traverses a local patch cable color: '#ff9800' - name: add label: Add description: Service added at this point (A-end) color: '#4caf50' - name: drop label: Drop description: Service dropped at this point (Z-end) color: '#ff5722' description: Type of segment - name: loss_db kind: Text optional: true description: Optical loss for this segment in dB relationships: - name: path peer: DcimOpticalPath cardinality: one kind: Parent optional: false identifier: optical_path__segments description: Parent path containing this segment - name: channel_mapping peer: DcimChannelMapping cardinality: one kind: Attribute on_delete: cascade optional: true identifier: channel_mapping__path_segments description: Channel mapping (WSS Connect, Cable Mapping, or Fiber Mapping) extensions: nodes: - kind: DcimPhysicalDevice relationships: - name: optical_node peer: DcimOpticalNode kind: Generic optional: true cardinality: many identifier: optical_nodes__physical_device ``` --- # Organization Basic Organization schema to capture organizations, vendors, and related information. ## Details[​](#details "Direct link to Details") * **Dependencies:** No dependencies ## Nodes[​](#nodes "Direct link to Nodes") ### Manufacturer[​](#manufacturer "Direct link to Manufacturer") * **Description:** Device Manufacturer * **Namespace:** Organization * **Icon:** mdi :domain * **Inherit From:** OrganizationGeneric #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------ | -------------- | -------- | ----------- | ---- | | device\_type | DcimDeviceType | True | many | | | platform | DcimPlatform | True | many | | ### Provider[​](#provider "Direct link to Provider") * **Description:** Circuit or Location Provider * **Namespace:** Organization * **Icon:** mdi :domain * **Inherit From:** OrganizationGeneric ## Generics[​](#generics "Direct link to Generics") ### Generic[​](#generic "Direct link to Generic") * **Label:** Organization * **Description:** An organization represent a legal entity, a company. * **Namespace:** Organization * **Icon:** mdi :domain * **Human Friendly ID:** name\_\_value #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------- | ----------- | ---- | -------- | -------------- | ------- | | name | | Text | | | | | description | | Text | True | | | #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ---- | ---------- | -------- | ----------- | --------- | | tags | BuiltinTag | True | many | Attribute | ## Code[​](#code "Direct link to Code") ``` version: '1.0' generics: - name: Generic namespace: Organization label: Organization description: An organization represent a legal entity, a company. human_friendly_id: - name__value order_by: - name__value display_label: name__value icon: mdi:domain include_in_menu: true attributes: - name: name kind: Text unique: true order_weight: 1000 - name: description kind: Text optional: true order_weight: 1200 relationships: - name: tags peer: BuiltinTag cardinality: many kind: Attribute optional: true order_weight: 3000 nodes: - name: Manufacturer namespace: Organization description: Device Manufacturer icon: mdi:domain inherit_from: - OrganizationGeneric include_in_menu: true menu_placement: OrganizationGeneric relationships: - name: device_type peer: DcimDeviceType cardinality: many optional: true - name: platform peer: DcimPlatform cardinality: many optional: true - name: Provider namespace: Organization description: Circuit or Location Provider icon: mdi:domain inherit_from: - OrganizationGeneric include_in_menu: true menu_placement: OrganizationGeneric ``` --- # Patch Panel This schema extension allows you to capture patch panel related information like rear/front interfaces and mapping between them. You can insert the patch panel into a rack and leverage the device type model. Finally you can also capture information about potential modules you would insert into your patch panel. NOTE: This extension is compatible with all sort of connectors, meaning you can plug cable, circuits, cross-connect to front & rear interfaces! ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) ## Nodes[​](#nodes "Direct link to Nodes") ### PatchPanel[​](#patchpanel "Direct link to PatchPanel") * **Label:** Patch Panel * **Description:** A Patch Panel used for managing network cable connections in a data center or telecom setup. * **Namespace:** Dcim * **Icon:** ic :round-device-hub * **Human Friendly ID:** name\_\_value * **Inherit From:** DcimPhysicalDevice #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ---------------- | ------------------------------------------------------------------------- | ------ | -------- | -------------- | ------- | | name | | Text | | | | | module\_capacity | The maximum number of modules that can be housed within this patch panel. | Number | True | | | | description | | Text | True | | | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | --------------------- | ---------------------------- | -------- | ----------- | --------- | | front\_interfaces | DcimFrontPatchPanelInterface | True | many | Component | | rear\_interfaces | DcimRearPatchPanelInterface | True | many | Component | | patch\_panel\_modules | DcimPatchPanelModule | True | many | Component | ### FrontPatchPanelInterface[​](#frontpatchpanelinterface "Direct link to FrontPatchPanelInterface") * **Label:** Patch Panel Front Interfaces * **Namespace:** Dcim * **Uniqueness Constraints:** * patch\_panel, name\_\_value * **Human Friendly ID:** patch\_panel\_\_name\_\_value, name\_\_value * **Inherit From:** DcimEndpoint, DcimGenericPatchPanelInterface #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | -------------------------- | --------------------------- | -------- | ----------- | --------- | | corresponding\_front\_rear | DcimRearPatchPanelInterface | True | one | Attribute | | patch\_panel | DcimPatchPanel | False | one | Parent | ### RearPatchPanelInterface[​](#rearpatchpanelinterface "Direct link to RearPatchPanelInterface") * **Label:** Patch Panel Rear Interfaces * **Namespace:** Dcim * **Uniqueness Constraints:** * patch\_panel, name\_\_value * **Human Friendly ID:** patch\_panel\_\_name\_\_value, name\_\_value * **Inherit From:** DcimEndpoint, DcimGenericPatchPanelInterface #### Relationships[​](#relationships-2 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | -------------------------- | ---------------------------- | -------- | ----------- | --------- | | corresponding\_front\_rear | DcimFrontPatchPanelInterface | True | many | Attribute | | patch\_panel | DcimPatchPanel | False | one | Parent | ### PatchPanelModule[​](#patchpanelmodule "Direct link to PatchPanelModule") * **Label:** Patch Panel Module * **Namespace:** Dcim * **Icon:** mdi :expansion-card * **Uniqueness Constraints:** * patch\_panel, position\_\_value * **Human Friendly ID:** patch\_panel\_\_name\_\_value, name\_\_value #### Attributes[​](#attributes-1 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ------------ | ----------------------------------------------- | -------- | -------- | -------------- | ------------------ | | name | | Text | | | | | position | Position for the module inside the Patch Panel. | Number | | | | | module\_type | Type of module inserted into the patch panel. | Dropdown | True | | 3\_mpo\_24\_fo\_lc | | serial | | Text | True | | | | description | | Text | True | | | #### Relationships[​](#relationships-3 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------ | -------------- | -------- | ----------- | ------ | | patch\_panel | DcimPatchPanel | False | one | Parent | ## Generics[​](#generics "Direct link to Generics") ### GenericPatchPanelInterface[​](#genericpatchpanelinterface "Direct link to GenericPatchPanelInterface") * **Label:** Patch Panel Interfaces * **Namespace:** Dcim * **Icon:** mdi :ethernet #### Attributes[​](#attributes-2 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | --------------- | ----------- | -------- | -------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | name | | Text | | | | | description | | Text | True | | | | connector\_type | | Dropdown | | | FC, LC, LC\_PC, LC\_UPC, LC\_APC, LSH, LSH\_PC, LSH\_UPC, LSH\_APC, LX\_5, LX\_5\_PC, LX\_5\_UPC, LX\_5\_APC, MPO, MTRJ, SC, SC\_PC, SC\_UPC, SC\_APC, ST, CS, SN, SMA\_905, SMA\_906, URM\_P2 | ## Code[​](#code "Direct link to Code") ``` version: '1.0' generics: - name: GenericPatchPanelInterface label: Patch Panel Interfaces menu_placement: DcimPatchPanel namespace: Dcim icon: mdi:ethernet attributes: - name: name kind: Text order_weight: 1000 - name: description kind: Text optional: true order_weight: 2000 - name: connector_type kind: Dropdown order_weight: 1100 choices: - name: FC label: FC description: Standardized fiber optic connector used primarily in datacom and telecom applications. - name: LC label: LC description: Compact fiber optic connector with a push-pull mechanism. - name: LC_PC label: LC/PC description: Polished LC connector providing physical contact (PC). - name: LC_UPC label: LC/UPC description: Ultra-Physical Contact (UPC) variant of the LC connector with enhanced polish. - name: LC_APC label: LC/APC description: Angled Physical Contact (APC) version of the LC connector with a slanted fiber end-face. - name: LSH label: LSH description: European fiber optic connector offering high durability. - name: LSH_PC label: LSH/PC description: Physical Contact version of LSH with standard polish. - name: LSH_UPC label: LSH/UPC description: Ultra-Physical Contact variant of LSH, minimizing return loss with a superior polish. - name: LSH_APC label: LSH/APC description: Angled Physical Contact version of LSH, designed to reduce back reflections. - name: LX_5 label: LX.5 description: Miniaturized fiber optic connector similar to LC but with an additional shutter mechanism. - name: LX_5_PC label: LX.5/PC description: Physical Contact version of LX.5. - name: LX_5_UPC label: LX.5/UPC description: Ultra-Physical Contact variant of LX.5. - name: LX_5_APC label: LX.5/APC description: Angled Physical Contact version of LX.5. - name: MPO label: MPO description: Multi-fiber Push-On connector typically used in data centers for high-speed applications. - name: MTRJ label: MTRJ description: Male-to-female fiber optic connector with two fibers. - name: SC label: SC description: Square fiber optic connector with push-pull lock. - name: SC_PC label: SC/PC description: Physical Contact SC connector with a polished end-face. - name: SC_UPC label: SC/UPC description: Ultra-Physical Contact variant of SC. - name: SC_APC label: SC/APC description: Angled Physical Contact version of SC. - name: ST label: ST description: Bayonet-style fiber optic connector primarily used in industrial and military applications. - name: CS label: CS description: Compact connector with a high-density duplex configuration. - name: SN label: SN description: Small-form connector with dual fibers. - name: SMA_905 label: SMA 905 description: Stainless steel fiber optic connector. - name: SMA_906 label: SMA 906 description: Variant of SMA 905 with similar durability, frequently used in high-vibration settings. - name: URM_P2 label: URM-P2 description: Specialized fiber optic connector for industrial and harsh environments. nodes: - name: PatchPanel label: Patch Panel description: A Patch Panel used for managing network cable connections in a data center or telecom setup. icon: ic:round-device-hub namespace: Dcim human_friendly_id: - name__value order_by: - name__value display_label: name__value inherit_from: - DcimPhysicalDevice attributes: - name: name kind: Text unique: true order_weight: 1000 - name: module_capacity label: Module Capacity kind: Number optional: true description: The maximum number of modules that can be housed within this patch panel. - name: description kind: Text optional: true order_weight: 2000 relationships: - name: front_interfaces peer: DcimFrontPatchPanelInterface identifier: front_interfaces optional: true cardinality: many kind: Component - name: rear_interfaces peer: DcimRearPatchPanelInterface identifier: rear_interfaces optional: true cardinality: many kind: Component - name: patch_panel_modules label: Modules peer: DcimPatchPanelModule optional: true cardinality: many kind: Component - name: FrontPatchPanelInterface label: Patch Panel Front Interfaces menu_placement: DcimGenericPatchPanelInterface namespace: Dcim human_friendly_id: - patch_panel__name__value - name__value display_label: '{{ patch_panel__name__value }} > {{ name__value }}' order_by: - corresponding_front_rear__name__value - name__value uniqueness_constraints: - - patch_panel - name__value inherit_from: - DcimEndpoint - DcimGenericPatchPanelInterface relationships: - name: corresponding_front_rear label: Corresponding rear interface peer: DcimRearPatchPanelInterface order_weight: 1200 optional: true cardinality: one kind: Attribute - name: patch_panel peer: DcimPatchPanel order_weight: 900 identifier: front_interfaces optional: false cardinality: one kind: Parent - name: RearPatchPanelInterface label: Patch Panel Rear Interfaces menu_placement: DcimGenericPatchPanelInterface namespace: Dcim human_friendly_id: - patch_panel__name__value - name__value display_label: '{{ patch_panel__name__value }} > {{ name__value }}' order_by: - patch_panel__name__value - name__value uniqueness_constraints: - - patch_panel - name__value inherit_from: - DcimEndpoint - DcimGenericPatchPanelInterface relationships: - name: corresponding_front_rear label: Corresponding front interfaces peer: DcimFrontPatchPanelInterface order_weight: 1200 optional: true cardinality: many kind: Attribute - name: patch_panel peer: DcimPatchPanel order_weight: 900 identifier: rear_interfaces optional: false cardinality: one kind: Parent - name: PatchPanelModule namespace: Dcim label: Patch Panel Module menu_placement: DcimPatchPanel icon: mdi:expansion-card human_friendly_id: - patch_panel__name__value - name__value display_label: '{{ patch_panel__name__value }} > {{ name__value }}' order_by: - patch_panel__name__value - position__value uniqueness_constraints: - - patch_panel - position__value attributes: - name: name kind: Text order_weight: 1000 - name: position kind: Number description: Position for the module inside the Patch Panel. order_weight: 1100 - name: module_type label: Module Type kind: Dropdown description: Type of module inserted into the patch panel. order_weight: 1200 optional: true choices: - name: 3_mpo_24_fo_lc label: 24 FO LC 3 MPO description: High-density MPO cassette with 24 fibers to front and 3 MPO to rear. color: '#7f7fff' - name: serial kind: Text optional: true order_weight: 1500 - name: description kind: Text optional: true order_weight: 2000 relationships: - name: patch_panel peer: DcimPatchPanel optional: false order_weight: 900 cardinality: one kind: Parent ``` --- # Peering IXP This schema extension contains all you need to model anything revolving around internet peering (Exchange points ...)! ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) * [extensions/routing](/schema-library/reference/routing.md) * [extensions/routing\_bgp](/schema-library/reference/routing_bgp.md) * [extensions/routing\_bgp\_community](/schema-library/reference/routing_bgp_community.md) ## Nodes[​](#nodes "Direct link to Nodes") ### IXP[​](#ixp "Direct link to IXP") * **Label:** Internet Exchange * **Description:** An Internet Exchange Point (IXP) for peering * **Namespace:** Peering * **Icon:** mdi :network * **Human Friendly ID:** name\_\_value #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------- | ------------------------------------------------ | -------- | -------- | -------------- | ----------------- | | name | Name of the Internet Exchange | Text | | | | | description | An optional description of the Internet Exchange | Text | True | | | | status | | Dropdown | | enabled | enabled, disabled | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ---------------- | ----------------------- | -------- | ----------- | --------- | | connections | PeeringIXPConnection | True | many | Component | | local\_as | RoutingAutonomousSystem | True | one | Attribute | | import\_policies | RoutingPolicyBGP | True | many | Generic | | export\_policies | RoutingPolicyBGP | True | many | Generic | | bgp\_communities | RoutingBGPCommunity | True | many | Generic | | tags | BuiltinTag | True | many | Attribute | ### IXPConnection[​](#ixpconnection "Direct link to IXPConnection") * **Label:** IXP Connection * **Description:** A connection to an Internet Exchange Point (IXP) * **Namespace:** Peering * **Icon:** mdi :lan-connect * **Uniqueness Constraints:** * name\_\_value * **Human Friendly ID:** name\_\_value #### Attributes[​](#attributes-1 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ------------------- | ------------------------------------------ | ---------- | -------- | -------------- | ----------------------------------------------------------------- | | name | Name of the IXP Connection | Text | | | | | description | Description of the IXP Connection | Text | True | | | | peeringdb\_netixlan | PeeringDB ID for the IXP connection | Number | True | | | | status | | Dropdown | True | enabled | enabled, pre-maintenance, maintenance, post-maintenance, disabled | | vlan | VLAN ID for the connection | Number | True | | | | mac\_address | MAC address associated with the connection | MacAddress | True | | | #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------------------- | ------------- | -------- | ----------- | --------- | | ipv6\_address | IpamIPAddress | | one | Attribute | | ipv4\_address | IpamIPAddress | | one | Attribute | | internet\_exchange\_point | PeeringIXP | False | one | Parent | | router | DcimDevice | | one | Attribute | | tags | BuiltinTag | | many | Attribute | ## Code[​](#code "Direct link to Code") ``` version: '1.0' nodes: - name: IXP namespace: Peering description: An Internet Exchange Point (IXP) for peering label: Internet Exchange icon: mdi:network human_friendly_id: - name__value order_by: - name__value display_label: name__value attributes: - name: name kind: Text unique: true description: Name of the Internet Exchange order_weight: 1000 - name: description kind: Text description: An optional description of the Internet Exchange optional: true order_weight: 1100 - name: status kind: Dropdown choices: - name: enabled label: Enabled description: Internet Exchange is active and operational color: '#a8dadc' - name: disabled label: Disabled description: Internet Exchange is not operational color: '#b0bec5' default_value: enabled order_weight: 1200 relationships: - name: connections peer: PeeringIXPConnection optional: true cardinality: many kind: Component description: IXP connections associated with this Internet Exchange - name: local_as peer: RoutingAutonomousSystem optional: true cardinality: one kind: Attribute order_weight: 1300 - name: import_policies label: Import Routing Policies peer: RoutingPolicyBGP identifier: ixp__import_bgppolicies optional: true cardinality: many kind: Generic description: Import routing policies applied to the session - name: export_policies label: Export Routing Policies peer: RoutingPolicyBGP identifier: ixp__export_bgppolicies optional: true cardinality: many kind: Generic description: Export routing policies applied to the session - name: bgp_communities label: BGP Communities peer: RoutingBGPCommunity optional: true cardinality: many kind: Generic description: BGP communities associated with the session - name: tags peer: BuiltinTag optional: true cardinality: many kind: Attribute order_weight: 3000 - name: IXPConnection namespace: Peering description: A connection to an Internet Exchange Point (IXP) label: IXP Connection icon: mdi:lan-connect menu_placement: PeeringIXP uniqueness_constraints: - - name__value human_friendly_id: - name__value order_by: - name__value display_label: name__value attributes: - name: name kind: Text unique: true description: Name of the IXP Connection order_weight: 1000 - name: description kind: Text optional: true description: Description of the IXP Connection order_weight: 1100 - name: peeringdb_netixlan label: PeeringDB Netixlan kind: Number optional: true description: PeeringDB ID for the IXP connection order_weight: 1150 - name: status kind: Dropdown optional: true choices: - name: enabled label: Enabled description: The system is fully operational and functioning as expected. color: '#a8dadc' - name: pre-maintenance label: Pre-Maintenance description: Preparation stage before performing maintenance tasks. color: '#f4a261' - name: maintenance label: Maintenance description: Currently undergoing maintenance tasks. color: '#e9c46a' - name: post-maintenance label: Post-Maintenance description: Final checks and verifications after maintenance tasks. color: '#f0e5de' - name: disabled label: Disabled description: The system is not operational and cannot be used. color: '#b0bec5' default_value: enabled order_weight: 1200 - name: vlan kind: Number description: VLAN ID for the connection optional: true order_weight: 1300 - name: mac_address kind: MacAddress label: MAC Address optional: true description: MAC address associated with the connection order_weight: 1350 relationships: - name: ipv6_address label: IPv6 Address description: IPv6 address assigned to the connection peer: IpamIPAddress identifier: ixpconn__ipv6_address cardinality: one kind: Attribute order_weight: 1400 - name: ipv4_address label: IPv4 Address description: IPv4 address assigned to the connection peer: IpamIPAddress identifier: ixpconn__ipv4_address cardinality: one kind: Attribute order_weight: 1375 - name: internet_exchange_point label: IXP peer: PeeringIXP optional: false cardinality: one kind: Parent description: The Internet Exchange Point this connection is part of - name: router peer: DcimDevice cardinality: one kind: Attribute description: The router this IXP connection is connected to order_weight: 1400 - name: tags peer: BuiltinTag cardinality: many kind: Attribute order_weight: 3000 ``` --- # Physical Disk Simple schema allowing you to capture physical disks information for the sake of inventory and lifecycle management. NOTE: This extension is compatible with all sort of device. You can apply the generic "DeviceWithPhysicalDisks" to particular model to enable disks tracking. You might also link that schema to location for instance to capture spares. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) ## Nodes[​](#nodes "Direct link to Nodes") ### PhysicalDisk[​](#physicaldisk "Direct link to PhysicalDisk") * **Label:** Physical Disk * **Description:** Physical Disk * **Namespace:** Dcim * **Icon:** carbon :vmdk-disk * **Uniqueness Constraints:** * device, name\_\_value * **Human Friendly ID:** name\_\_value #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | -------------- | ------------------------------------------- | -------- | -------- | -------------- | ----------------------------------------------- | | name | | Text | False | | | | disk\_type | Specifies the type of disk | Dropdown | True | | ssd, nvme, hdd, hybrid | | status | Lifecycle status of the hardware component. | Dropdown | | | in\_inventory, active, decommissioned, disposed | | size | Disk capacity (in GB). | Number | False | | | | serial\_number | Serial number of the disk | Text | True | | | | description | | Text | True | | | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------ | --------------------------- | -------- | ----------- | ------ | | device | DcimDeviceWithPhysicalDisks | False | one | Parent | ## Generics[​](#generics "Direct link to Generics") ### DeviceWithPhysicalDisks[​](#devicewithphysicaldisks "Direct link to DeviceWithPhysicalDisks") * **Description:** Generic that hold relationship toward physical disks. To apply on device that can have physical disks. * **Namespace:** Dcim #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | --------------- | ---------------- | -------- | ----------- | --------- | | physical\_disks | DcimPhysicalDisk | True | many | Component | ## Code[​](#code "Direct link to Code") ``` version: '1.0' generics: - name: DeviceWithPhysicalDisks namespace: Dcim description: Generic that hold relationship toward physical disks. To apply on device that can have physical disks. include_in_menu: false relationships: - name: physical_disks cardinality: many peer: DcimPhysicalDisk optional: true kind: Component nodes: - name: PhysicalDisk namespace: Dcim description: Physical Disk label: Physical Disk icon: carbon:vmdk-disk order_by: - name__value display_label: name__value human_friendly_id: - name__value uniqueness_constraints: - - device - name__value attributes: - name: name kind: Text optional: false order_weight: 900 - name: disk_type kind: Dropdown description: Specifies the type of disk order_weight: 950 optional: true choices: - name: ssd label: SSD description: Solid State Drive color: '#a6c1ff' - name: nvme label: NVMe description: Non-Volatile Memory Express color: '#ffbf80' - name: hdd label: HDD description: Hard Disk Drive color: '#80c7a6' - name: hybrid label: Hybrid description: Hybrid Drive color: '#ffcc80' - name: status kind: Dropdown description: Lifecycle status of the hardware component. choices: - name: in_inventory label: In Inventory description: The disk is newly acquired and held in inventory. color: '#6c757d' - name: active label: Active description: The disk is currently in use within the infrastructure. color: '#28a745' - name: decommissioned label: Decommissioned description: The disk is retired from active use but still stored for potential reuse or auditing. color: '#17a2b8' - name: disposed label: Disposed description: The disk has been securely erased and disposed of. color: '#dc3545' - name: size label: Size (GB) kind: Number optional: false order_weight: 1000 description: Disk capacity (in GB). - name: serial_number kind: Text description: Serial number of the disk unique: true order_weight: 1100 optional: true - name: description kind: Text unique: false optional: true order_weight: 1500 relationships: - name: device peer: DcimDeviceWithPhysicalDisks optional: false cardinality: one kind: Parent order_weight: 800 ``` --- # QinQ This schema extension brings extensions to VLAN model in order to support QinQ. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) * [extensions/vlan](/schema-library/reference/vlan.md) ## Extensions[​](#extensions "Direct link to Extensions") note In this context "extensions" refer to modifications or additions to the existing schema, such as adding new attributes, relationships, or other schema elements. ### IpamVLAN[​](#ipamvlan "Direct link to IpamVLAN") #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ---------- | ----------- | -------- | -------- | -------------- | ----------------- | | qinq\_role | | Dropdown | True | | suplier, customer | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ----- | -------- | -------- | ----------- | --------- | | svlan | IpamVLAN | True | one | Attribute | | cvlan | IpamVLAN | True | many | Attribute | ## Code[​](#code "Direct link to Code") ``` version: '1.0' extensions: nodes: - kind: IpamVLAN attributes: - name: qinq_role kind: Dropdown optional: true choices: - name: suplier label: Suplier description: A VLAN used to encapsulate multiple CVLANs. - name: customer label: Customer description: A VLAN assigned to customer traffic. relationships: - name: svlan label: Supplier vlan peer: IpamVLAN optional: true direction: inbound identifier: vlan__qinq cardinality: one kind: Attribute - name: cvlan label: Customer vlan(s) peer: IpamVLAN optional: true direction: outbound identifier: vlan__qinq cardinality: many kind: Attribute ``` --- # QoS This schema extension contains models for Quality of Service (QoS) ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) ## Nodes[​](#nodes "Direct link to Nodes") ### ForwardingClass[​](#forwardingclass "Direct link to ForwardingClass") * **Label:** Forwarding Class * **Description:** Represents a forwarding class in QoS with distinct loss priorities. * **Namespace:** Qos * **Uniqueness Constraints:** * name\_\_value * **Human Friendly ID:** name\_\_value #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | -------------------------- | ------------------------------------------- | ---- | -------- | -------------- | ------- | | name | Name of the forwarding class. | Text | | | | | high\_loss\_priority\_code | List of code points for high loss priority. | List | True | | | | low\_loss\_priority\_code | List of code points for low loss priority. | List | True | | | ### ClassOfService[​](#classofservice "Direct link to ClassOfService") * **Label:** Class of Service * **Description:** Defines a Class of Service configuration. * **Namespace:** Qos * **Uniqueness Constraints:** * name\_\_value * **Human Friendly ID:** name\_\_value #### Attributes[​](#attributes-1 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ---- | ----------------------------- | ---- | -------- | -------------- | ------- | | name | Name of the Class of Service. | Text | | | | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | -------------------------- | ------------------------ | -------- | ----------- | ---- | | traffic\_control\_profiles | QosTrafficControlProfile | True | many | | ### TrafficControlProfile[​](#trafficcontrolprofile "Direct link to TrafficControlProfile") * **Label:** Traffic Control Profile * **Description:** Defines a traffic control profile with an active/inactive state. * **Namespace:** Qos * **Uniqueness Constraints:** * name\_\_value * **Human Friendly ID:** name\_\_value #### Attributes[​](#attributes-2 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ------ | -------------------------------------------------------- | -------- | -------- | -------------- | ---------------- | | name | Name of the traffic control profile. | Text | | | | | status | Status of the traffic control profile (active/inactive). | Dropdown | | inactive | active, inactive | ### Classifier[​](#classifier "Direct link to Classifier") * **Label:** Classifier * **Description:** Represents a classifier mapping DSCP or EXP values to forwarding classes. * **Namespace:** Qos * **Uniqueness Constraints:** * name\_\_value * **Human Friendly ID:** name\_\_value #### Attributes[​](#attributes-3 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ---------------- | ------------------------------------- | -------- | -------- | -------------- | -------------------- | | name | Name of the classifier. | Text | | | | | classifier\_type | Type of classifier (DSCP, EXP, etc.). | Dropdown | | | dscp, exp, dscp-ipv6 | #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------------- | ------------------ | -------- | ----------- | ---- | | forwarding\_classes | QosForwardingClass | True | many | | ### Scheduler[​](#scheduler "Direct link to Scheduler") * **Label:** Scheduler * **Description:** Represents a scheduler configuration. * **Namespace:** Qos * **Uniqueness Constraints:** * name\_\_value * **Human Friendly ID:** name\_\_value #### Attributes[​](#attributes-4 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ---------------- | -------------------------------- | -------- | -------- | -------------- | ---------------------- | | name | Name of the scheduler. | Text | | | | | transmit\_rate | Transmit rate in percentage. | Number | | | | | buffer\_size | Buffer size in percentage. | Number | | | | | priority | Priority of the scheduler. | Dropdown | True | | low, high, strict-high | | excess\_priority | Excess priority when applicable. | Dropdown | True | | low, high | ### SchedulerMap[​](#schedulermap "Direct link to SchedulerMap") * **Label:** Scheduler Map * **Description:** Defines mappings of schedulers to forwarding classes. * **Namespace:** Qos * **Uniqueness Constraints:** * name\_\_value * **Human Friendly ID:** name\_\_value #### Attributes[​](#attributes-5 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ---- | -------------------------- | ---- | -------- | -------------- | ------- | | name | Name of the scheduler map. | Text | | | | #### Relationships[​](#relationships-2 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------------- | ------------------ | -------- | ----------- | ---- | | schedulers | QosScheduler | True | many | | | forwarding\_classes | QosForwardingClass | True | many | | ## Code[​](#code "Direct link to Code") ``` version: '1.0' nodes: - name: ForwardingClass namespace: Qos label: Forwarding Class description: Represents a forwarding class in QoS with distinct loss priorities. display_label: name__value order_by: - name__value human_friendly_id: - name__value uniqueness_constraints: - - name__value menu_placement: QosClassOfService attributes: - name: name kind: Text description: Name of the forwarding class. unique: true order_weight: 1000 - name: high_loss_priority_code kind: List description: List of code points for high loss priority. optional: true order_weight: 1200 - name: low_loss_priority_code kind: List description: List of code points for low loss priority. optional: true order_weight: 1300 - name: ClassOfService namespace: Qos label: Class of Service description: Defines a Class of Service configuration. display_label: name__value order_by: - name__value human_friendly_id: - name__value uniqueness_constraints: - - name__value attributes: - name: name kind: Text description: Name of the Class of Service. unique: true order_weight: 1000 relationships: - name: traffic_control_profiles peer: QosTrafficControlProfile description: List of traffic control profiles. cardinality: many optional: true order_weight: 1200 - name: TrafficControlProfile namespace: Qos label: Traffic Control Profile description: Defines a traffic control profile with an active/inactive state. display_label: name__value order_by: - name__value human_friendly_id: - name__value uniqueness_constraints: - - name__value menu_placement: QosClassOfService attributes: - name: name kind: Text description: Name of the traffic control profile. unique: true order_weight: 1000 - name: status kind: Dropdown description: Status of the traffic control profile (active/inactive). choices: - name: active label: Active - name: inactive label: Inactive default_value: inactive order_weight: 1200 - name: Classifier namespace: Qos label: Classifier description: Represents a classifier mapping DSCP or EXP values to forwarding classes. display_label: name__value order_by: - name__value human_friendly_id: - name__value uniqueness_constraints: - - name__value menu_placement: QosClassOfService attributes: - name: name kind: Text description: Name of the classifier. unique: true order_weight: 1000 - name: classifier_type kind: Dropdown description: Type of classifier (DSCP, EXP, etc.). choices: - name: dscp label: DSCP Classifier - name: exp label: EXP Classifier - name: dscp-ipv6 label: DSCP-IPv6 Classifier order_weight: 1200 relationships: - name: forwarding_classes peer: QosForwardingClass description: List of forwarding classes defined in the classifier. cardinality: many optional: true order_weight: 1300 - name: Scheduler namespace: Qos label: Scheduler description: Represents a scheduler configuration. display_label: name__value order_by: - name__value human_friendly_id: - name__value uniqueness_constraints: - - name__value menu_placement: QosClassOfService attributes: - name: name kind: Text description: Name of the scheduler. unique: true order_weight: 1000 - name: transmit_rate label: Transmit Rate (%) kind: Number description: Transmit rate in percentage. order_weight: 1200 - name: buffer_size label: Buffer Size (%) kind: Number description: Buffer size in percentage. order_weight: 1300 - name: priority kind: Dropdown description: Priority of the scheduler. choices: - name: low label: Low Priority - name: high label: High Priority - name: strict-high label: Strict High Priority optional: true order_weight: 1400 - name: excess_priority kind: Dropdown description: Excess priority when applicable. choices: - name: low label: Low Excess Priority - name: high label: High Excess Priority optional: true order_weight: 1500 - name: SchedulerMap namespace: Qos label: Scheduler Map description: Defines mappings of schedulers to forwarding classes. display_label: name__value order_by: - name__value human_friendly_id: - name__value uniqueness_constraints: - - name__value menu_placement: QosClassOfService attributes: - name: name kind: Text description: Name of the scheduler map. unique: true order_weight: 1000 relationships: - name: schedulers peer: QosScheduler description: List of schedulers defined in the map. cardinality: many optional: true order_weight: 1200 - name: forwarding_classes peer: QosForwardingClass description: List of forwarding classes associated with schedulers. cardinality: many optional: true order_weight: 1300 ``` --- # Routing This schema extension contains generics to create Routing Protocol "Instance". The idea is to create one Routing Protocol instance per IpamVRF + DcimDevice pair. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) * [extensions/vlan](/schema-library/reference/vlan.md) ## Generics[​](#generics "Direct link to Generics") ### Protocol[​](#protocol "Direct link to Protocol") * **Label:** Protocol * **Description:** Generic protocol model for routing protocols * **Namespace:** Routing * **Icon:** carbon :router #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------- | ------------------------------------- | -------- | -------- | -------------- | ------------------------- | | description | Description of the protocol | Text | False | | | | status | Status of the Protocol Configuration. | Dropdown | | | active, disabled, deleted | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------ | ---------- | -------- | ----------- | --------- | | device | DcimDevice | False | one | Parent | | vrf | IpamVRF | False | one | Attribute | ## Code[​](#code "Direct link to Code") ``` version: '1.0' generics: - name: Protocol namespace: Routing description: Generic protocol model for routing protocols label: Protocol icon: carbon:router include_in_menu: false attributes: - name: description kind: Text optional: false unique: true description: Description of the protocol order_weight: 1100 - name: status kind: Dropdown choices: - name: active label: Active description: Configuration is active and operational. color: '#A9CCE3' - name: disabled label: Disabled description: Configuration has been disabled. color: '#D3D3D3' - name: deleted label: Deleted description: Configuration has been deleted. color: '#FAD7A0' description: Status of the Protocol Configuration. order_weight: 1150 relationships: - name: device peer: DcimDevice optional: false cardinality: one kind: Parent order_weight: 1050 - name: vrf label: VRF peer: IpamVRF optional: false cardinality: one kind: Attribute order_weight: 1075 ``` --- # Routing Aggregate This schema extension contains all you need to model the Aggregate Routing Protocol. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) * [extensions/routing](/schema-library/reference/routing.md) ## Nodes[​](#nodes "Direct link to Nodes") ### AggregateRoute[​](#aggregateroute "Direct link to AggregateRoute") * **Label:** Aggregate Routes * **Description:** Aggregate Protocol with action and BGP communities * **Namespace:** Routing * **Icon:** grommet-icons :aggregate * **Inherit From:** RoutingProtocol #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ---------------- | ----------- | ------- | -------- | -------------- | ------- | | discard | | Boolean | True | False | | | import\_policies | | Text | True | | | | export\_policies | | Text | True | | | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ----------- | ---------- | -------- | ----------- | --------- | | destination | IpamPrefix | | | Attribute | ## Code[​](#code "Direct link to Code") ``` version: '1.0' nodes: - name: AggregateRoute namespace: Routing description: Aggregate Protocol with action and BGP communities label: Aggregate Routes include_in_menu: false icon: grommet-icons:aggregate inherit_from: - RoutingProtocol attributes: - name: discard label: Discard kind: Boolean optional: true default_value: false order_weight: 1275 - name: import_policies kind: Text optional: true order_weight: 1300 - name: export_policies kind: Text optional: true order_weight: 1350 relationships: - name: destination kind: Attribute peer: IpamPrefix description: Destination network for the static route. order_weight: 1200 ``` --- # Routing BGP This schema extension contains all you need to model your BGP platform. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) * [extensions/routing](/schema-library/reference/routing.md) ## Nodes[​](#nodes "Direct link to Nodes") ### AutonomousSystem[​](#autonomoussystem "Direct link to AutonomousSystem") * **Label:** Autonomous System * **Description:** An Autonomous System (AS) is a set of Internet routable IP prefixes belonging to a network * **Namespace:** Routing * **Icon:** mdi :bank-circle-outline * **Human Friendly ID:** asn\_\_value, name\_\_value #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------- | ------------------------------------ | ------ | -------- | -------------- | ------- | | name | Name of the Autonomous System | Text | | | | | asn | Autonomous System Number | Number | | | | | description | Description of the Autonomous System | Text | True | | | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------ | ------------------- | -------- | ----------- | --------- | | organization | OrganizationGeneric | False | one | Attribute | | location | LocationGeneric | True | one | Attribute | | devices | DcimDevice | True | many | Attribute | ### BGPPeerGroup[​](#bgppeergroup "Direct link to BGPPeerGroup") * **Label:** BGP Peer Group * **Description:** A BGP Peer Group is used to regroup parameters that are shared across multiple peers * **Namespace:** Routing * **Icon:** mdi :view-grid-plus-outline * **Human Friendly ID:** name\_\_value, description\_\_value * **Inherit From:** RoutingProtocol #### Attributes[​](#attributes-1 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ---------------- | ---------------------------------------------------------------------------- | -------- | -------- | -------------- | ---------- | | name | Name of the BGP Group | Text | False | | | | import\_policies | | Text | True | | | | export\_policies | | Text | True | | | | maximum\_routes | Maximum routes for the BGP Group. | Number | True | | | | local\_pref | Force Local Pref for this BGP Peer Group. | Number | True | | | | send\_community | Whether to send community attributes. | Checkbox | True | | | | address\_family | The address family for the routing policy indicating the type of IP address. | Dropdown | | ipv4 | ipv4, ipv6 | #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ---------- | ----------------------- | -------- | ----------- | --------- | | local\_as | RoutingAutonomousSystem | True | one | Attribute | | remote\_as | RoutingAutonomousSystem | True | one | Attribute | ### BGPSession[​](#bgpsession "Direct link to BGPSession") * **Label:** BGP Session * **Description:** A BGP Session represent a point to point connection between two routers * **Namespace:** Routing * **Icon:** mdi :router * **Inherit From:** RoutingProtocol #### Attributes[​](#attributes-2 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ---------------- | ------------------------------------------- | -------- | -------- | -------------- | --------------------------- | | import\_policies | | Text | True | | | | export\_policies | | Text | True | | | | session\_type | Type of BGP Session | Text | | | | | role | Role of the BGP Session | Dropdown | | | backbone, upstream, peering | | local\_pref | Force Local Pref for this BGP Peer Session. | Number | True | | | #### Relationships[​](#relationships-2 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------- | ----------------------- | -------- | ----------- | --------- | | local\_as | RoutingAutonomousSystem | True | one | Attribute | | remote\_as | RoutingAutonomousSystem | True | one | Attribute | | local\_ip | IpamIPAddress | True | one | Attribute | | remote\_ip | IpamIPAddress | True | one | Attribute | | device | DcimDevice | True | one | | | peer\_group | RoutingBGPPeerGroup | True | one | Attribute | | peer\_session | RoutingBGPSession | True | one | Attribute | ## Extensions[​](#extensions "Direct link to Extensions") note In this context "extensions" refer to modifications or additions to the existing schema, such as adding new attributes, relationships, or other schema elements. ### DcimGenericDevice[​](#dcimgenericdevice "Direct link to DcimGenericDevice") #### Relationships[​](#relationships-3 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ---- | ----------------------- | -------- | ----------- | --------- | | asn | RoutingAutonomousSystem | True | one | Attribute | ### OrganizationGeneric[​](#organizationgeneric "Direct link to OrganizationGeneric") #### Relationships[​](#relationships-4 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ---- | ----------------------- | -------- | ----------- | ---- | | asn | RoutingAutonomousSystem | True | many | | ## Code[​](#code "Direct link to Code") ``` version: '1.0' nodes: - name: AutonomousSystem namespace: Routing description: An Autonomous System (AS) is a set of Internet routable IP prefixes belonging to a network label: Autonomous System icon: mdi:bank-circle-outline menu_placement: RoutingBGPSession human_friendly_id: - asn__value - name__value order_by: - asn__value - name__value display_label: 'AS {{ asn__value }}: {{ name__value }}' attributes: - name: name kind: Text description: Name of the Autonomous System order_weight: 1000 unique: true - name: asn kind: Number description: Autonomous System Number order_weight: 1050 unique: true - name: description kind: Text optional: true description: Description of the Autonomous System order_weight: 1100 relationships: - name: organization peer: OrganizationGeneric optional: false cardinality: one kind: Attribute - name: location peer: LocationGeneric optional: true cardinality: one kind: Attribute - name: devices peer: DcimDevice optional: true cardinality: many kind: Attribute - name: BGPPeerGroup namespace: Routing description: A BGP Peer Group is used to regroup parameters that are shared across multiple peers label: BGP Peer Group icon: mdi:view-grid-plus-outline menu_placement: RoutingBGPSession inherit_from: - RoutingProtocol human_friendly_id: - name__value - description__value order_by: - name__value display_label: '{{ name__value }} - {{ description__value }}' attributes: - name: name kind: Text optional: false unique: true description: Name of the BGP Group order_weight: 1000 - name: import_policies kind: Text optional: true order_weight: 1300 - name: export_policies kind: Text optional: true order_weight: 1350 - name: maximum_routes kind: Number optional: true description: Maximum routes for the BGP Group. order_weight: 1400 regex: ^[0-9]+$ - name: local_pref kind: Number optional: true description: Force Local Pref for this BGP Peer Group. order_weight: 1450 regex: ^[0-9]+$ - name: send_community kind: Checkbox optional: true description: Whether to send community attributes. order_weight: 1500 - name: address_family description: The address family for the routing policy indicating the type of IP address. kind: Dropdown choices: - name: ipv4 label: IPv4 description: Policy applies to IPv4 addresses. color: '#E6E6FA' - name: ipv6 label: IPv6 description: Policy applies to IPv6 addresses. color: '#E6E6FA' default_value: ipv4 order_weight: 1150 relationships: - name: local_as identifier: bgppeergroup__local_as peer: RoutingAutonomousSystem optional: true cardinality: one kind: Attribute - name: remote_as identifier: bgppeergroup__remote_as peer: RoutingAutonomousSystem optional: true cardinality: one kind: Attribute - name: BGPSession namespace: Routing description: A BGP Session represent a point to point connection between two routers label: BGP Session icon: mdi:router inherit_from: - RoutingProtocol order_by: - remote_as__asn__value display_label: description__value attributes: - name: import_policies kind: Text optional: true - name: export_policies kind: Text optional: true - name: session_type kind: Text enum: - EXTERNAL - INTERNAL description: Type of BGP Session order_weight: 1200 - name: role kind: Dropdown choices: - name: backbone label: Backbone description: Provide main data routes. color: '#E6E6FA' - name: upstream label: Upstream description: Connect to Internet service provider. color: '#E6E6FA' - name: peering label: Peering description: Connect with other networks via IX. color: '#E6E6FA' description: Role of the BGP Session order_weight: 1600 - name: local_pref kind: Number optional: true description: Force Local Pref for this BGP Peer Session. order_weight: 1450 regex: ^[0-9]+$ relationships: - name: local_as identifier: bgpsession__local_as peer: RoutingAutonomousSystem optional: true cardinality: one kind: Attribute - name: remote_as identifier: bgpsession__remote_as peer: RoutingAutonomousSystem optional: true cardinality: one kind: Attribute - name: local_ip identifier: bgpsession__local_ip peer: IpamIPAddress optional: true cardinality: one kind: Attribute - name: remote_ip identifier: bgpsession__remote_ip peer: IpamIPAddress optional: true cardinality: one kind: Attribute - name: device peer: DcimDevice optional: true cardinality: one - name: peer_group peer: RoutingBGPPeerGroup optional: true cardinality: one kind: Attribute - name: peer_session peer: RoutingBGPSession optional: true cardinality: one kind: Attribute extensions: nodes: - kind: DcimGenericDevice relationships: - name: asn peer: RoutingAutonomousSystem optional: true cardinality: one kind: Attribute order_weight: 1600 - kind: OrganizationGeneric relationships: - name: asn label: Autonomous System cardinality: many optional: true peer: RoutingAutonomousSystem order_weight: 2000 ``` --- # Routing BGP Community This schema extension adds the BGP Communities models. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) * [extensions/routing](/schema-library/reference/routing.md) ## Nodes[​](#nodes "Direct link to Nodes") ### BGPCommunity[​](#bgpcommunity "Direct link to BGPCommunity") * **Label:** BGP Community * **Description:** Defines a BGP community. * **Namespace:** Routing * **Icon:** iconoir :community * **Human Friendly ID:** name\_\_value #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------- | ----------------------------------------------------------- | ---- | -------- | -------------- | ------- | | name | The name of the BGP community. | Text | | | | | description | An optional description of the BGP community. | Text | True | | | | community | The value of the BGP community (RFC1997, RFC4360, RFC8092). | Text | | | | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | --------------- | ------------- | -------- | ----------- | --------- | | routing\_policy | RoutingPolicy | | many | Generic | | tags | BuiltinTag | True | many | Attribute | ## Code[​](#code "Direct link to Code") ``` version: '1.0' nodes: - name: BGPCommunity namespace: Routing icon: iconoir:community label: BGP Community description: Defines a BGP community. human_friendly_id: - name__value order_by: - name__value display_label: '{{ name__value }} ({{ community__value }})' attributes: - name: name kind: Text description: The name of the BGP community. order_weight: 1000 unique: true - name: description kind: Text description: An optional description of the BGP community. optional: true order_weight: 1100 - name: community kind: Text description: The value of the BGP community (RFC1997, RFC4360, RFC8092). order_weight: 1200 unique: true relationships: - name: routing_policy label: Routing Policies peer: RoutingPolicy description: The BGP Policies using this BGP Community. kind: Generic cardinality: many - name: tags peer: BuiltinTag kind: Attribute optional: true cardinality: many order_weight: 3000 ``` --- # Routing BGP RR This schema extension extend the BGP extension to add BGP Route Reflector Clustering. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) * [extensions/routing](/schema-library/reference/routing.md) * [extensions/routing\_bgp](/schema-library/reference/routing_bgp.md) ## Nodes[​](#nodes "Direct link to Nodes") ### BGPRRCluster[​](#bgprrcluster "Direct link to BGPRRCluster") * **Label:** Route Reflector Cluster * **Description:** A Route Reflector (RR) Cluster used for grouping internal peers * **Namespace:** Routing * **Icon:** mdi :router-network * **Human Friendly ID:** name\_\_value #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------- | --------------------------------------------------- | ---- | -------- | -------------- | ------- | | name | Name of the Route Reflector Cluster | Text | | | | | description | Optional description of the Route Reflector Cluster | Text | True | | | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------ | ------------------- | -------- | ----------- | --------- | | cluster\_id | IpamIPAddress | False | one | Attribute | | peer\_groups | RoutingBGPPeerGroup | True | many | Generic | ## Extensions[​](#extensions "Direct link to Extensions") note In this context "extensions" refer to modifications or additions to the existing schema, such as adding new attributes, relationships, or other schema elements. ### RoutingBGPPeerGroup[​](#routingbgppeergroup "Direct link to RoutingBGPPeerGroup") #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ----------- | ------------------- | -------- | ----------- | --------- | | rr\_cluster | RoutingBGPRRCluster | | one | Attribute | ## Code[​](#code "Direct link to Code") ``` version: '1.0' nodes: - name: BGPRRCluster namespace: Routing description: A Route Reflector (RR) Cluster used for grouping internal peers label: Route Reflector Cluster icon: mdi:router-network include_in_menu: false human_friendly_id: - name__value display_label: name__value attributes: - name: name kind: Text description: Name of the Route Reflector Cluster order_weight: 1000 unique: true - name: description kind: Text description: Optional description of the Route Reflector Cluster optional: true order_weight: 1100 relationships: - name: cluster_id label: Cluster ID peer: IpamIPAddress description: Cluster ID represented as a reference to an IP Address cardinality: one kind: Attribute optional: false order_weight: 1200 - name: peer_groups label: BGP Peer Groups peer: RoutingBGPPeerGroup cardinality: many kind: Generic optional: true extensions: nodes: - kind: RoutingBGPPeerGroup relationships: - name: rr_cluster label: RR Cluster peer: RoutingBGPRRCluster cardinality: one kind: Attribute order_weight: 1600 ``` --- # Routing OSPF This schema extension contains all you need to model the OSPF Routing Protocol. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) * [extensions/routing](/schema-library/reference/routing.md) ## Nodes[​](#nodes "Direct link to Nodes") ### OSPF[​](#ospf "Direct link to OSPF") * **Label:** OSPF * **Description:** OSPF (Open Shortest Path First) instance on a Virtual Router. * **Namespace:** Routing * **Icon:** mdi :network-outline * **Uniqueness Constraints:** * device, vrf, version\_\_value * **Human Friendly ID:** device\_\_name\_\_value, vrf\_\_name\_\_value, version\_\_value * **Inherit From:** RoutingProtocol #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | -------------------- | ------------------------------------------------ | -------- | -------- | -------------- | ------------ | | reference\_bandwidth | Reference bandwidth for OSPF instance (in Mbps). | Number | True | 1000 | | | version | Version of the OSPF protocol. | Dropdown | | ospf | ospf, ospfv3 | | import\_policies | | Text | True | | | | export\_policies | | Text | True | | | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ---------------- | -------------------- | -------- | ----------- | --------- | | router\_id | IpamIPAddress | True | one | Attribute | | ospf\_interfaces | RoutingOSPFInterface | True | many | Component | ### OSPFInterface[​](#ospfinterface "Direct link to OSPFInterface") * **Label:** OSPF Interface * **Description:** Pivot table linking OSPF configuration to an interface. * **Namespace:** Routing * **Icon:** mdi :ethernet * **Uniqueness Constraints:** * ospf, interface * **Human Friendly ID:** description\_\_value #### Attributes[​](#attributes-1 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | -------------------- | ------------------------------------------------------------------------------------------- | -------- | -------- | -------------- | ------------------------------- | | description | Description of the OSPF interface. | Text | False | | | | metric | OSPF metric for the interface. | Number | True | | | | mode | Mode of the OSPF interface. | Dropdown | | normal | normal, passive, peer\_to\_peer | | authentication\_key | Shared secret used to authenticate and secure routing messages between neighboring routers. | Password | True | | | | authentication\_mode | | Dropdown | True | | md5, sha1 | | area | OSPF area associated with the interface. | Text | | | | #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | --------- | --------------- | -------- | ----------- | --------- | | ospf | RoutingOSPF | False | one | Parent | | interface | InterfaceLayer3 | False | one | Attribute | ## Code[​](#code "Direct link to Code") ``` version: '1.0' nodes: - name: OSPF namespace: Routing description: OSPF (Open Shortest Path First) instance on a Virtual Router. label: OSPF icon: mdi:network-outline include_in_menu: false order_by: - router_id__address__value - vrf__name__value - device__name__value uniqueness_constraints: - - device - vrf - version__value human_friendly_id: - device__name__value - vrf__name__value - version__value display_label: description__value inherit_from: - RoutingProtocol attributes: - name: reference_bandwidth kind: Number optional: true default_value: 1000 description: Reference bandwidth for OSPF instance (in Mbps). order_weight: 1150 - name: version kind: Dropdown choices: - name: ospf label: OSPFv2 description: Open Shortest Path First version 2. color: '#E6E6FA' - name: ospfv3 label: OSPFv3 description: Open Shortest Path First version 3. color: '#E6E6FA' default_value: ospf description: Version of the OSPF protocol. order_weight: 1100 - name: import_policies kind: Text optional: true order_weight: 1300 - name: export_policies kind: Text optional: true order_weight: 1350 relationships: - name: router_id peer: IpamIPAddress optional: true cardinality: one kind: Attribute - name: ospf_interfaces label: OSPF Interfaces peer: RoutingOSPFInterface identifier: ospf__ospfinterfaces optional: true cardinality: many kind: Component - name: OSPFInterface namespace: Routing description: Pivot table linking OSPF configuration to an interface. label: OSPF Interface icon: mdi:ethernet include_in_menu: false order_by: - description__value display_label: description__value uniqueness_constraints: - - ospf - interface human_friendly_id: - description__value attributes: - name: description kind: Text optional: false unique: true description: Description of the OSPF interface. order_weight: 1500 - name: metric kind: Number optional: true description: OSPF metric for the interface. order_weight: 1400 - name: mode kind: Dropdown choices: - name: normal label: Normal description: Standard OSPF interface mode. color: '#E6E6FA' - name: passive label: Passive description: Interface will not send OSPF hello packets. color: '#E6E6FA' - name: peer_to_peer label: Peer-to-Peer description: OSPF peer-to-peer interface mode. color: '#E6E6FA' default_value: normal description: Mode of the OSPF interface. order_weight: 1300 - name: authentication_key kind: Password description: Shared secret used to authenticate and secure routing messages between neighboring routers. optional: true order_weight: 1250 - name: authentication_mode kind: Dropdown choices: - name: md5 label: MD5 color: '#E6E6FA' - name: sha1 label: SHA1 color: '#E6E6FA' optional: true order_weight: 1225 - name: area kind: Text description: OSPF area associated with the interface. order_weight: 1200 relationships: - name: ospf label: OSPF peer: RoutingOSPF identifier: ospf__ospfinterfaces optional: false cardinality: one kind: Parent order_weight: 1100 - name: interface peer: InterfaceLayer3 optional: false cardinality: one kind: Attribute order_weight: 1200 ``` --- # Routing PIM This schema extension contains all you need to model the PIM Protocol. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) * [extensions/routing](/schema-library/reference/routing.md) ## Nodes[​](#nodes "Direct link to Nodes") ### PIM[​](#pim "Direct link to PIM") * **Label:** PIM * **Description:** Protocol Independent Multicast (PIM) instance on a Virtual Router. * **Namespace:** Routing * **Icon:** mdi :network-outline * **Uniqueness Constraints:** * device, vrf * **Human Friendly ID:** device\_\_name\_\_value, vrf\_\_name\_\_value * **Inherit From:** RoutingProtocol #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ---------------- | --------------------------- | ------ | -------- | -------------- | ------- | | dr\_priority | Designated Router priority. | Number | True | 1 | | | import\_policies | | Text | True | | | | export\_policies | | Text | True | | | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | --------------- | ------------------- | -------- | ----------- | --------- | | rp\_address | IpamIPAddress | True | one | Attribute | | pim\_interfaces | RoutingPIMInterface | True | many | Component | ### PIMInterface[​](#piminterface "Direct link to PIMInterface") * **Label:** PIM Interface * **Description:** Interface configuration for PIM. * **Namespace:** Routing * **Icon:** mdi :ethernet * **Uniqueness Constraints:** * pim, interface * **Human Friendly ID:** description\_\_value #### Attributes[​](#attributes-1 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | --------------- | ------------------------------------------------------ | -------- | -------- | -------------- | ---------------------------- | | description | Description of the OSPF interface. | Text | False | | | | pim\_mode | PIM mode used for multicast routing on this interface. | Dropdown | | | sparse, dense, bidirectional | | hello\_interval | Interval for PIM hello messages (in seconds). | Number | True | 30 | | | dr\_priority | Designated Router priority on the interface. | Number | True | 1 | | #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | --------- | --------------- | -------- | ----------- | --------- | | pim | RoutingPIM | False | one | Parent | | interface | InterfaceLayer3 | False | one | Attribute | ## Code[​](#code "Direct link to Code") ``` version: '1.0' nodes: - name: PIM namespace: Routing description: Protocol Independent Multicast (PIM) instance on a Virtual Router. label: PIM icon: mdi:network-outline include_in_menu: false inherit_from: - RoutingProtocol order_by: - vrf__name__value - device__name__value uniqueness_constraints: - - device - vrf human_friendly_id: - device__name__value - vrf__name__value display_label: description__value attributes: - name: dr_priority kind: Number optional: true default_value: 1 description: Designated Router priority. order_weight: 1250 - name: import_policies kind: Text optional: true order_weight: 1300 - name: export_policies kind: Text optional: true order_weight: 1350 relationships: - name: rp_address peer: IpamIPAddress optional: true description: Rendezvous Point (RP) address for PIM. cardinality: one kind: Attribute - name: pim_interfaces label: PIM Interfaces peer: RoutingPIMInterface identifier: pim__piminterfaces optional: true cardinality: many kind: Component - name: PIMInterface namespace: Routing description: Interface configuration for PIM. label: PIM Interface icon: mdi:ethernet include_in_menu: false order_by: - description__value display_label: description__value uniqueness_constraints: - - pim - interface human_friendly_id: - description__value attributes: - name: description kind: Text optional: false unique: true description: Description of the OSPF interface. order_weight: 1100 - name: pim_mode kind: Dropdown choices: - name: sparse label: Sparse Mode description: Sparse Mode for efficient multicast forwarding. color: '#E6E6FA' - name: dense label: Dense Mode description: Dense Mode for heavy multicast traffic. color: '#E6E6FA' - name: bidirectional label: Bidirectional Mode description: Bidirectional PIM for efficient traffic forwarding. color: '#E6E6FA' description: PIM mode used for multicast routing on this interface. order_weight: 1150 - name: hello_interval kind: Number optional: true default_value: 30 description: Interval for PIM hello messages (in seconds). order_weight: 1300 - name: dr_priority kind: Number optional: true default_value: 1 description: Designated Router priority on the interface. order_weight: 1250 relationships: - name: pim label: PIM peer: RoutingPIM identifier: pim__piminterfaces optional: false cardinality: one kind: Parent - name: interface peer: InterfaceLayer3 optional: false cardinality: one kind: Attribute order_weight: 1200 ``` --- # Routing Policies This schema extension contains a generic to create Routing Policies. This Generic can be extend for each Routing Protocols you may want to use. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) ## Generics[​](#generics "Direct link to Generics") ### Policy[​](#policy "Direct link to Policy") * **Description:** Policy defining the rules for routing traffic in a network. * **Namespace:** Routing * **Icon:** carbon :deployment-policy * **Human Friendly ID:** name\_\_value #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | --------------- | -------------------------------------------------------------------------------- | -------- | -------- | -------------- | -------------------------------------------------- | | name | The name of the routing policy. | Text | False | | | | description | An optional description of the routing policy. | Text | True | | | | policy\_type | The type of routing policy which specifies the direction of route advertisement. | Dropdown | | | import-policy, export-policy, import-export-policy | | weight | Priority of the routing policy. The higher the number, the higher the priority. | Number | True | 1000 | | | address\_family | The address family for the routing policy indicating the type of IP address. | Dropdown | | all | ipv4, ipv6, all | ## Code[​](#code "Direct link to Code") ``` version: '1.0' generics: - name: Policy namespace: Routing icon: carbon:deployment-policy include_in_menu: false description: Policy defining the rules for routing traffic in a network. human_friendly_id: - name__value order_by: - name__value display_label: name__value attributes: - name: name kind: Text description: The name of the routing policy. unique: true optional: false order_weight: 1000 - name: description kind: Text description: An optional description of the routing policy. optional: true order_weight: 1100 - name: policy_type label: Type kind: Dropdown description: The type of routing policy which specifies the direction of route advertisement. choices: - name: import-policy label: Import description: Policy for incoming routes. color: '#E6E6FA' - name: export-policy label: Export description: Policy for outgoing routes. color: '#E6E6FA' - name: import-export-policy label: Import + Export description: Policy for both incoming and outgoing routes. color: '#E6E6FA' order_weight: 1200 - name: weight kind: Number description: Priority of the routing policy. The higher the number, the higher the priority. optional: true default_value: 1000 order_weight: 1400 - name: address_family description: The address family for the routing policy indicating the type of IP address. kind: Dropdown choices: - name: ipv4 label: IPv4 description: Policy applies to IPv4 addresses. color: '#E6E6FA' - name: ipv6 label: IPv6 description: Policy applies to IPv6 addresses. color: '#E6E6FA' - name: all label: All description: Policy applies to both IPv4 and IPv6 addresses. color: '#E6E6FA' default_value: all order_weight: 1150 ``` --- # Routing Policies Aggregate This extension is using the Routing Policies extensions and the Routing Aggregate one together. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) * [extensions/routing](/schema-library/reference/routing.md) * [extensions/routing\_policies](/schema-library/reference/routing_policies.md) * [extensions/routing\_aggregate](/schema-library/reference/routing_aggregate.md) ## Nodes[​](#nodes "Direct link to Nodes") ### PolicyAggregate[​](#policyaggregate "Direct link to PolicyAggregate") * **Label:** Aggregate Routing Policies * **Description:** A routing policiers for Aggregate * **Namespace:** Routing * **Icon:** carbon :deployment-policy * **Inherit From:** RoutingPolicy ## Code[​](#code "Direct link to Code") ``` version: '1.0' nodes: - name: PolicyAggregate namespace: Routing description: A routing policiers for Aggregate label: Aggregate Routing Policies icon: carbon:deployment-policy include_in_menu: false inherit_from: - RoutingPolicy ``` --- # Routing Policies BGP This extension is using the Routing Policies extensions and the Routing BGP one together. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) * [extensions/routing](/schema-library/reference/routing.md) * [extensions/routing\_policies](/schema-library/reference/routing_policies.md) * [extensions/routing\_bgp](/schema-library/reference/routing_bgp.md) ## Nodes[​](#nodes "Direct link to Nodes") ### PolicyBGP[​](#policybgp "Direct link to PolicyBGP") * **Label:** BGP Routing Policies * **Description:** A routing policiers for BGP * **Namespace:** Routing * **Icon:** carbon :deployment-policy * **Inherit From:** RoutingPolicy ## Extensions[​](#extensions "Direct link to Extensions") note In this context "extensions" refer to modifications or additions to the existing schema, such as adding new attributes, relationships, or other schema elements. ### RoutingBGPPeerGroup[​](#routingbgppeergroup "Direct link to RoutingBGPPeerGroup") #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ---------------- | ----------- | ---- | -------- | -------------- | ------- | | import\_policies | | Text | | | | | export\_policies | | Text | | | | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------------------- | ---------------- | -------- | ----------- | ------- | | import\_routing\_policies | RoutingPolicyBGP | | many | Generic | | export\_routing\_policies | RoutingPolicyBGP | | many | Generic | ### RoutingBGPSession[​](#routingbgpsession "Direct link to RoutingBGPSession") #### Attributes[​](#attributes-1 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ---------------- | ----------- | ---- | -------- | -------------- | ------- | | import\_policies | | Text | | | | | export\_policies | | Text | | | | #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------------------- | ------------- | -------- | ----------- | ------- | | import\_routing\_policies | RoutingPolicy | | many | Generic | | export\_routing\_policies | RoutingPolicy | | many | Generic | ## Code[​](#code "Direct link to Code") ``` version: '1.0' nodes: - name: PolicyBGP namespace: Routing description: A routing policiers for BGP label: BGP Routing Policies icon: carbon:deployment-policy include_in_menu: false inherit_from: - RoutingPolicy extensions: nodes: - kind: RoutingBGPPeerGroup attributes: - name: import_policies kind: Text state: absent - name: export_policies kind: Text state: absent relationships: - name: import_routing_policies label: Import Routing Policies peer: RoutingPolicyBGP identifier: bgp__import_policies description: The routing-policies used by this instance for import. kind: Generic cardinality: many - name: export_routing_policies label: Export Routing Policies peer: RoutingPolicyBGP identifier: bgp__export_policies description: The routing-policies used by this instance for export. kind: Generic cardinality: many - kind: RoutingBGPSession attributes: - name: import_policies kind: Text state: absent - name: export_policies kind: Text state: absent relationships: - name: import_routing_policies label: Import Routing Policies peer: RoutingPolicy identifier: bgp__import_policies description: The routing-policies used by this instance for import. kind: Generic cardinality: many - name: export_routing_policies label: Export Routing Policies peer: RoutingPolicy identifier: bgp__export_policies description: The routing-policies used by this instance for export. kind: Generic cardinality: many ``` --- # Routing Policies OSPF This extension is using the Routing Policies extensions and the Routing OSPF one together. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) * [extensions/routing](/schema-library/reference/routing.md) * [extensions/routing\_policies](/schema-library/reference/routing_policies.md) * [extensions/routing\_ospf](/schema-library/reference/routing_ospf.md) ## Nodes[​](#nodes "Direct link to Nodes") ### PolicyOSPF[​](#policyospf "Direct link to PolicyOSPF") * **Label:** OSPF Routing Policies * **Description:** A routing policiers for OSPF * **Namespace:** Routing * **Icon:** carbon :deployment-policy * **Inherit From:** RoutingPolicy ## Extensions[​](#extensions "Direct link to Extensions") note In this context "extensions" refer to modifications or additions to the existing schema, such as adding new attributes, relationships, or other schema elements. ### RoutingOSPF[​](#routingospf "Direct link to RoutingOSPF") #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ---------------- | ----------- | ---- | -------- | -------------- | ------- | | import\_policies | | Text | | | | | export\_policies | | Text | | | | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------------------- | ----------------- | -------- | ----------- | ------- | | import\_routing\_policies | RoutingPolicyOSPF | | many | Generic | | export\_routing\_policies | RoutingPolicyOSPF | | many | Generic | ## Code[​](#code "Direct link to Code") ``` version: '1.0' nodes: - name: PolicyOSPF namespace: Routing description: A routing policiers for OSPF label: OSPF Routing Policies icon: carbon:deployment-policy include_in_menu: false inherit_from: - RoutingPolicy extensions: nodes: - kind: RoutingOSPF attributes: - name: import_policies kind: Text state: absent - name: export_policies kind: Text state: absent relationships: - name: import_routing_policies label: Import Routing Policies peer: RoutingPolicyOSPF identifier: ospf__import_policies description: The routing-policies used by this instance for import. kind: Generic cardinality: many - name: export_routing_policies label: Export Routing Policies peer: RoutingPolicyOSPF identifier: ospf__export_policies description: The routing-policies used by this instance for export. kind: Generic cardinality: many ``` --- # Routing Policies (PIM) This schema inherits the `RoutingPolicy` schema and removes `import_policies` and `export_policies` attributes. However it adds a number of relationships to `RoutingPIM`. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) * [extensions/routing](/schema-library/reference/routing.md) * [extensions/routing\_policies](/schema-library/reference/routing_policies.md) * [extensions/routing\_pim](/schema-library/reference/routing_pim.md) ## Nodes[​](#nodes "Direct link to Nodes") ### PolicyPIM[​](#policypim "Direct link to PolicyPIM") * **Label:** PIM Routing Policies * **Description:** A routing policiers for PIM * **Namespace:** Routing * **Icon:** carbon :deployment-policy * **Inherit From:** RoutingPolicy ## Extensions[​](#extensions "Direct link to Extensions") note In this context "extensions" refer to modifications or additions to the existing schema, such as adding new attributes, relationships, or other schema elements. ### RoutingPIM[​](#routingpim "Direct link to RoutingPIM") #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ---------------- | ----------- | ---- | -------- | -------------- | ------- | | import\_policies | | Text | | | | | export\_policies | | Text | | | | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------------------- | ---------------- | -------- | ----------- | ------- | | import\_routing\_policies | RoutingPolicyPIM | | many | Generic | | export\_routing\_policies | RoutingPolicyPIM | | many | Generic | ## Code[​](#code "Direct link to Code") ``` version: '1.0' nodes: - name: PolicyPIM namespace: Routing description: A routing policiers for PIM label: PIM Routing Policies icon: carbon:deployment-policy include_in_menu: false inherit_from: - RoutingPolicy extensions: nodes: - kind: RoutingPIM attributes: - name: import_policies kind: Text state: absent - name: export_policies kind: Text state: absent relationships: - name: import_routing_policies label: Import Routing Policies peer: RoutingPolicyPIM identifier: pim__import_policies description: The routing-policies used by this instance for import. kind: Generic cardinality: many - name: export_routing_policies label: Export Routing Policies peer: RoutingPolicyPIM identifier: pim__export_policies description: The routing-policies used by this instance for export. kind: Generic cardinality: many ``` --- # Security This schema extension contains models for implementing detailed security. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) ## Nodes[​](#nodes "Direct link to Nodes") ### Zone[​](#zone "Direct link to Zone") * **Label:** Security zone * **Description:** Security zones * **Namespace:** Security * **Icon:** game-icons :fire-zone * **Human Friendly ID:** name\_\_value #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ---- | ----------- | ---- | -------- | -------------- | ------- | | name | | Text | False | | | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ---------- | ------------------------- | -------- | ----------- | --------- | | interfaces | SecurityFirewallInterface | True | | Attribute | ### IPAMIPAddress[​](#ipamipaddress "Direct link to IPAMIPAddress") * **Label:** IPAM IP Address * **Description:** Infrahub IPv4/6 address * **Namespace:** Security * **Icon:** mdi :ip-outline * **Human Friendly ID:** name\_\_value * **Inherit From:** SecurityGenericAddress #### Attributes[​](#attributes-1 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------- | ----------- | ---- | -------- | -------------- | ------- | | description | | Text | True | | | #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ----------- | -------------- | -------- | ----------- | --------- | | ip\_address | InfraIPAddress | False | one | Attribute | ### IPAMIPPrefix[​](#ipamipprefix "Direct link to IPAMIPPrefix") * **Label:** IPAM IP Prefix * **Description:** Infrahub IPv4/6 prefix * **Namespace:** Security * **Icon:** mdi :ip-network-outline * **Human Friendly ID:** name\_\_value * **Inherit From:** SecurityGenericAddress #### Attributes[​](#attributes-2 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------- | ----------- | ---- | -------- | -------------- | ------- | | description | | Text | True | | | #### Relationships[​](#relationships-2 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ---------- | ----------- | -------- | ----------- | --------- | | ip\_prefix | InfraPrefix | False | one | Attribute | ### IPAddress[​](#ipaddress "Direct link to IPAddress") * **Label:** IP Address * **Description:** IPv4/6 address * **Namespace:** Security * **Icon:** mdi :ip-outline * **Human Friendly ID:** name\_\_value * **Inherit From:** SecurityGenericAddress #### Attributes[​](#attributes-3 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------- | ----------- | ------ | -------- | -------------- | ------- | | address | | IPHost | | | | | description | | Text | True | | | ### Prefix[​](#prefix "Direct link to Prefix") * **Label:** Prefix * **Description:** IPv4/6 prefix * **Namespace:** Security * **Icon:** mdi :ip-network-outline * **Human Friendly ID:** name\_\_value * **Inherit From:** SecurityGenericAddress #### Attributes[​](#attributes-4 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------- | ----------- | --------- | -------- | -------------- | ------- | | prefix | | IPNetwork | False | | | | description | | Text | True | | | ### IPRange[​](#iprange "Direct link to IPRange") * **Label:** IP Range * **Description:** IPv4/6 Range * **Namespace:** Security * **Icon:** mdi :ip-outline * **Human Friendly ID:** name\_\_value * **Inherit From:** SecurityGenericAddress #### Attributes[​](#attributes-5 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----- | ----------- | ------ | -------- | -------------- | ------- | | start | | IPHost | False | | | | end | | IPHost | False | | | ### FQDN[​](#fqdn "Direct link to FQDN") * **Label:** FQDN * **Description:** Full Qualified Domain Name * **Namespace:** Security * **Icon:** eos-icons :dns * **Human Friendly ID:** name\_\_value * **Inherit From:** SecurityGenericAddress #### Attributes[​](#attributes-6 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ---- | ----------- | ---- | -------- | -------------- | ------- | | fqdn | | Text | False | | | ### AddressGroup[​](#addressgroup "Direct link to AddressGroup") * **Label:** Address Group * **Description:** Group of addresses * **Namespace:** Security * **Icon:** material-symbols :menu-book-outline-rounded * **Human Friendly ID:** name\_\_value * **Inherit From:** SecurityGenericAddressGroup ### IPProtocol[​](#ipprotocol "Direct link to IPProtocol") * **Label:** IP Protocols * **Description:** IP protocol * **Namespace:** Security * **Icon:** mdi :protocol * **Human Friendly ID:** name\_\_value * **Inherit From:** SecurityGenericService #### Attributes[​](#attributes-7 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | -------- | ----------- | ------ | -------- | -------------- | ------- | | protocol | | Number | True | | | ### Service[​](#service "Direct link to Service") * **Label:** Service * **Description:** Service * **Namespace:** Security * **Icon:** eos-icons :application-outlined * **Human Friendly ID:** name\_\_value * **Inherit From:** SecurityGenericService #### Attributes[​](#attributes-8 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ---- | ----------- | ------ | -------- | -------------- | ------- | | port | | Number | | | | #### Relationships[​](#relationships-3 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------ | ------------------ | -------- | ----------- | --------- | | ip\_protocol | SecurityIPProtocol | True | one | Attribute | ### ServiceRange[​](#servicerange "Direct link to ServiceRange") * **Label:** Service range * **Description:** Service range * **Namespace:** Security * **Icon:** eos-icons :application-outlined * **Human Friendly ID:** name\_\_value * **Inherit From:** SecurityGenericService #### Attributes[​](#attributes-9 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----- | ----------- | ------ | -------- | -------------- | ------- | | start | | Number | False | | | | end | | Number | False | | | #### Relationships[​](#relationships-4 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------ | ------------------ | -------- | ----------- | --------- | | ip\_protocol | SecurityIPProtocol | False | one | Attribute | ### ServiceGroup[​](#servicegroup "Direct link to ServiceGroup") * **Label:** Service group * **Description:** Group of services * **Namespace:** Security * **Icon:** material-symbols :menu-book-outline-rounded * **Human Friendly ID:** name\_\_value * **Inherit From:** SecurityGenericServiceGroup ### Policy[​](#policy "Direct link to Policy") * **Label:** Security Policy * **Namespace:** Security * **Human Friendly ID:** name\_\_value #### Attributes[​](#attributes-10 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------- | ----------- | ---- | -------- | -------------- | ------- | | name | | Text | False | | | | description | | Text | True | | | #### Relationships[​](#relationships-5 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ---------------- | ------------------ | -------- | ----------- | --------- | | rules | SecurityPolicyRule | | many | Component | | location\_target | LocationGeneric | True | one | Attribute | | device\_target | SecurityFirewall | True | one | Attribute | ### PolicyRule[​](#policyrule "Direct link to PolicyRule") * **Label:** Policy rule * **Description:** Policy rule * **Namespace:** Security * **Icon:** material-symbols :policy * **Uniqueness Constraints:** * index\_\_value, source\_zone, destination\_zone, policy #### Attributes[​](#attributes-11 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ------ | ----------- | ------- | -------- | -------------- | ------- | | index | | Number | False | | | | name | | Text | False | | | | action | | Text | False | permit | | | log | | Boolean | True | False | | #### Relationships[​](#relationships-6 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ---------------------------- | --------------------------- | -------- | ----------- | --------- | | policy | SecurityPolicy | False | one | Attribute | | source\_zone | SecurityZone | False | one | Attribute | | destination\_zone | SecurityZone | False | one | Attribute | | source\_address | SecurityGenericAddress | True | many | Attribute | | source\_groups | SecurityGenericAddressGroup | True | many | Attribute | | source\_services | SecurityGenericService | True | many | Attribute | | source\_service\_groups | SecurityGenericServiceGroup | True | many | Attribute | | destination\_address | SecurityGenericAddress | True | many | Attribute | | destination\_groups | SecurityGenericAddressGroup | True | many | Attribute | | destination\_services | SecurityGenericService | True | many | Attribute | | destination\_service\_groups | SecurityGenericServiceGroup | True | many | Attribute | ### Firewall[​](#firewall "Direct link to Firewall") * **Namespace:** Security * **Icon:** mdi :firewall * **Human Friendly ID:** name\_\_value * **Inherit From:** InfraGenericDevice, CoreArtifactTarget, SecurityPolicyAssignment #### Attributes[​](#attributes-12 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ---- | ----------- | -------- | -------- | -------------- | -------------- | | role | | Dropdown | True | | edge\_firewall | #### Relationships[​](#relationships-7 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------ | -------------- | -------- | ----------- | --------- | | policy | SecurityPolicy | | one | Attribute | ### RenderedPolicyRule[​](#renderedpolicyrule "Direct link to RenderedPolicyRule") * **Label:** Policy rule * **Description:** Policy rule * **Namespace:** Security #### Attributes[​](#attributes-13 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ------ | ----------- | ------- | -------- | -------------- | ------- | | index | | Number | False | | | | name | | Text | False | | | | action | | Text | False | permit | | | log | | Boolean | True | False | | #### Relationships[​](#relationships-8 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ---------------------------- | --------------------------- | -------- | ----------- | --------- | | source\_policy | SecurityPolicy | False | one | Attribute | | source\_zone | SecurityZone | False | one | Attribute | | destination\_zone | SecurityZone | False | one | Attribute | | source\_address | SecurityGenericAddress | True | many | Attribute | | source\_groups | SecurityGenericAddressGroup | True | many | Attribute | | source\_services | SecurityGenericService | True | many | Attribute | | source\_service\_groups | SecurityGenericServiceGroup | True | many | Attribute | | destination\_address | SecurityGenericAddress | True | many | Attribute | | destination\_groups | SecurityGenericAddressGroup | True | many | Attribute | | destination\_services | SecurityGenericService | True | many | Attribute | | destination\_service\_groups | SecurityGenericServiceGroup | True | many | Attribute | ### FirewallInterface[​](#firewallinterface "Direct link to FirewallInterface") * **Label:** Firewall Interface * **Namespace:** Security * **Icon:** mdi :ethernet * **Inherit From:** InfraInterface, InfraEndpoint #### Relationships[​](#relationships-9 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | -------------- | -------------- | -------- | ----------- | --------- | | ip\_addresses | InfraIPAddress | True | many | Component | | security\_zone | SecurityZone | False | one | Attribute | ## Generics[​](#generics "Direct link to Generics") ### PolicyAssignment[​](#policyassignment "Direct link to PolicyAssignment") * **Label:** Security Policy * **Namespace:** Security #### Relationships[​](#relationships-10 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ----- | -------------------------- | -------- | ----------- | --------- | | rules | SecurityRenderedPolicyRule | True | many | Component | ### GenericAddressGroup[​](#genericaddressgroup "Direct link to GenericAddressGroup") * **Namespace:** Security #### Attributes[​](#attributes-14 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------- | ----------- | ---- | -------- | -------------- | ------- | | name | | Text | False | | | | description | | Text | True | | | #### Relationships[​](#relationships-11 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | --------- | ---------------------- | -------- | ----------- | --------- | | addresses | SecurityGenericAddress | True | many | Component | ### GenericAddress[​](#genericaddress "Direct link to GenericAddress") * **Namespace:** Security #### Attributes[​](#attributes-15 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ---- | ----------- | ---- | -------- | -------------- | ------- | | name | | Text | False | | | #### Relationships[​](#relationships-12 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | --------------- | --------------------------- | -------- | ----------- | ---- | | address\_groups | SecurityGenericAddressGroup | True | many | | ### GenericServiceGroup[​](#genericservicegroup "Direct link to GenericServiceGroup") * **Namespace:** Security #### Attributes[​](#attributes-16 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------- | ----------- | ---- | -------- | -------------- | ------- | | name | | Text | False | | | | description | | Text | True | | | #### Relationships[​](#relationships-13 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | -------- | ---------------------- | -------- | ----------- | --------- | | services | SecurityGenericService | True | many | Component | ### GenericService[​](#genericservice "Direct link to GenericService") * **Namespace:** Security #### Attributes[​](#attributes-17 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------- | ----------- | ---- | -------- | -------------- | ------- | | name | | Text | False | | | | description | | Text | True | | | #### Relationships[​](#relationships-14 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | --------------- | --------------------------- | -------- | ----------- | ---- | | service\_groups | SecurityGenericServiceGroup | True | many | | ## Extensions[​](#extensions "Direct link to Extensions") note In this context "extensions" refer to modifications or additions to the existing schema, such as adding new attributes, relationships, or other schema elements. ### LocationGeneric[​](#locationgeneric "Direct link to LocationGeneric") #### Relationships[​](#relationships-15 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------ | -------------- | -------- | ----------- | --------- | | policy | SecurityPolicy | | one | Attribute | ## Code[​](#code "Direct link to Code") ``` version: '1.0' generics: - name: PolicyAssignment namespace: Security label: Security Policy include_in_menu: false relationships: - name: rules label: Policy peer: SecurityRenderedPolicyRule kind: Component cardinality: many optional: true - name: GenericAddressGroup namespace: Security include_in_menu: false display_label: name__value hierarchical: true attributes: - name: name kind: Text label: Name optional: false unique: true - name: description label: Description kind: Text optional: true relationships: - name: addresses peer: SecurityGenericAddress cardinality: many kind: Component optional: true - name: GenericAddress namespace: Security display_label: name__value include_in_menu: false attributes: - name: name kind: Text optional: false unique: true relationships: - name: address_groups label: Address Groups peer: SecurityGenericAddressGroup cardinality: many optional: true - name: GenericServiceGroup namespace: Security include_in_menu: false hierarchical: true display_label: name__value attributes: - name: name label: Name kind: Text optional: false - name: description label: Description kind: Text optional: true relationships: - name: services peer: SecurityGenericService label: Services cardinality: many kind: Component optional: true - name: GenericService include_in_menu: false namespace: Security display_label: name__value attributes: - name: name label: Name kind: Text optional: false - name: description kind: Text label: Description optional: true relationships: - name: service_groups label: Service Groups peer: SecurityGenericServiceGroup cardinality: many optional: true nodes: - name: Zone namespace: Security menu_placement: SecurityPolicy human_friendly_id: - name__value display_label: name__value include_in_menu: true icon: game-icons:fire-zone description: Security zones label: Security zone attributes: - name: name kind: Text unique: true optional: false relationships: - name: interfaces label: Interfaces kind: Attribute optional: true peer: SecurityFirewallInterface - name: IPAMIPAddress namespace: Security menu_placement: SecurityPolicy include_in_menu: true icon: mdi:ip-outline description: Infrahub IPv4/6 address label: IPAM IP Address human_friendly_id: - name__value inherit_from: - SecurityGenericAddress attributes: - name: description kind: Text optional: true relationships: - name: ip_address peer: InfraIPAddress cardinality: one kind: Attribute optional: false - name: IPAMIPPrefix namespace: Security menu_placement: SecurityPolicy include_in_menu: true icon: mdi:ip-network-outline description: Infrahub IPv4/6 prefix label: IPAM IP Prefix human_friendly_id: - name__value inherit_from: - SecurityGenericAddress attributes: - name: description kind: Text optional: true relationships: - name: ip_prefix peer: InfraPrefix cardinality: one kind: Attribute optional: false - name: IPAddress namespace: Security menu_placement: SecurityPolicy include_in_menu: true description: IPv4/6 address human_friendly_id: - name__value label: IP Address icon: mdi:ip-outline inherit_from: - SecurityGenericAddress order_by: - address__value display_label: '{{ name__value }} ({{ address__value }})' attributes: - name: address kind: IPHost - name: description kind: Text optional: true - name: Prefix namespace: Security menu_placement: SecurityPolicy include_in_menu: true icon: mdi:ip-network-outline description: IPv4/6 prefix label: Prefix human_friendly_id: - name__value inherit_from: - SecurityGenericAddress order_by: - name__value display_label: '{{ name__value }} ({{ prefix__value }})' attributes: - name: prefix kind: IPNetwork optional: false unique: true - name: description kind: Text optional: true - name: IPRange namespace: Security menu_placement: SecurityPolicy include_in_menu: true icon: mdi:ip-outline description: IPv4/6 Range label: IP Range human_friendly_id: - name__value display_label: name__value inherit_from: - SecurityGenericAddress order_by: - name__value attributes: - name: start label: Start IP Address kind: IPHost optional: false - name: end label: End IP Address kind: IPHost optional: false - name: FQDN namespace: Security description: Full Qualified Domain Name include_in_menu: true icon: eos-icons:dns menu_placement: SecurityPolicy label: FQDN human_friendly_id: - name__value display_label: name__value inherit_from: - SecurityGenericAddress order_by: - name__value - fqdn__value attributes: - name: fqdn label: FQDN kind: Text optional: false regex: (?=^.{1,253}$)(^(((?!-)[a-zA-Z0-9-]{1,63}(? :gpu * **Inherit From:** DcimGenericSFP ### BidiSFP[​](#bidisfp "Direct link to BidiSFP") * **Label:** Bidirectional SFP * **Description:** Bidirectional SFP supporting two wavelengths for single-fiber operation. * **Namespace:** Dcim * **Icon:** lineicons :arrow-both-direction-vertical-1 * **Inherit From:** DcimGenericSFP #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | -------------- | -------------------------- | ------ | -------- | -------------- | ------- | | wavelength\_tx | Transmit wavelength in nm. | Number | False | | | | wavelength\_rx | Receive wavelength in nm. | Number | False | | | ## Generics[​](#generics "Direct link to Generics") ### GenericSFP[​](#genericsfp "Direct link to GenericSFP") * **Label:** SFP * **Description:** Generic base for all Small Form-factor Pluggable (SFP) transceivers. * **Namespace:** Dcim * **Icon:** mdi :gpu #### Attributes[​](#attributes-1 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ------------ | ------------------------------------------- | -------- | -------- | -------------- | --------------------------------------------------------------------------------------------- | | serial | | Text | True | | | | sfp\_type | Type of SFP, such as LR, SR, T. | Dropdown | False | | lr, sr, lrm, t, sr4, lr4, zr, er, dac, aoc | | status | | Dropdown | False | plugged | plugged, spare, decommissioned | | form\_factor | The physical form factor of the SFP module. | Dropdown | False | | sfp, sfp\_plus, qsfp, qsfp\_plus, qsfp28, qsfp\_dd, cfp, cfp2, cfp4, xfp, sfp56, qsfp56, osfp | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | --------------- | ------------------------ | -------- | ----------- | --------- | | interface | DcimInterface | True | one | Attribute | | spare\_location | LocationHosting | True | one | Attribute | | manufacturer | OrganizationManufacturer | True | one | Attribute | ## Extensions[​](#extensions "Direct link to Extensions") note In this context "extensions" refer to modifications or additions to the existing schema, such as adding new attributes, relationships, or other schema elements. ### DcimInterface[​](#dciminterface "Direct link to DcimInterface") #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------ | -------------- | -------- | ----------- | ---- | | plugged\_sfp | DcimGenericSFP | True | one | | ### LocationHosting[​](#locationhosting "Direct link to LocationHosting") #### Relationships[​](#relationships-2 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ----------- | -------------- | -------- | ----------- | ---- | | spare\_sfps | DcimGenericSFP | True | many | | ### OrganizationManufacturer[​](#organizationmanufacturer "Direct link to OrganizationManufacturer") #### Relationships[​](#relationships-3 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ---- | -------------- | -------- | ----------- | ---- | | sfps | DcimGenericSFP | True | many | | ## Code[​](#code "Direct link to Code") ``` version: '1.0' generics: - name: GenericSFP namespace: Dcim description: Generic base for all Small Form-factor Pluggable (SFP) transceivers. label: SFP icon: mdi:gpu include_in_menu: true display_label: '{{ form_factor__value }} {{ sfp_type__value }}' attributes: - name: serial kind: Text optional: true order_weight: 1400 - name: sfp_type kind: Dropdown description: Type of SFP, such as LR, SR, T. order_weight: 1100 optional: false choices: - name: lr label: LR (Long Reach) description: Long Reach SFP, suitable for long-distance fiber connections. color: '#009933' - name: sr label: SR (Short Reach) description: Short Reach SFP, typically used for short-distance fiber. color: '#cc66ff' - name: lrm label: LRM (Long Reach Multimode) description: SFP for multimode fiber over longer distances. color: '#3366ff' - name: t label: T (Twisted Pair) description: Copper-based SFP, often used for twisted pair Ethernet. color: '#ff9900' - name: sr4 label: SR4 (Short Range 4-lane) description: Short Range 4-lane SFP, used for parallel optics. color: '#6666ff' - name: lr4 label: LR4 (Long Range 4-lane) description: Long Range 4-lane SFP, typically used for longer distances over parallel optics. color: '#336699' - name: zr label: ZR (Extended Reach) description: Extended Reach SFP, suitable for distances up to 80km. color: '#cc3300' - name: er label: ER (Extended Reach) description: Extended Reach SFP, typically used for 40km distances. color: '#ff6600' - name: dac label: DAC (Direct Attach Copper) description: Direct Attach Copper, used for short connections over copper. color: '#b35900' - name: aoc label: AOC (Active Optical Cable) description: Active Optical Cable, used for short connections over fiber. color: '#6699ff' - name: status kind: Dropdown optional: false order_weight: 1300 default_value: plugged choices: - name: plugged label: Plugged description: Plugged into a device's interface. color: '#7fbf7f' - name: spare label: Spare description: Stored somewhere as a spare. color: '#ffff7f' - name: decommissioned label: Decommissioned description: Decommissioned might be broken or not used anymore. color: '#ffd27f' - name: form_factor kind: Dropdown description: The physical form factor of the SFP module. order_weight: 1000 optional: false choices: - name: sfp label: SFP color: '#009933' - name: sfp_plus label: SFP+ color: '#cc66ff' - name: qsfp label: QSFP color: '#6666ff' - name: qsfp_plus label: QSFP+ color: '#3366ff' - name: qsfp28 label: QSFP28 color: '#336699' - name: qsfp_dd label: QSFP-DD color: '#ff9900' - name: cfp label: CFP color: '#cc3300' - name: cfp2 label: CFP2 color: '#ff6600' - name: cfp4 label: CFP4 color: '#b35900' - name: xfp label: XFP color: '#6699ff' - name: sfp56 label: SFP56 color: '#9966ff' - name: qsfp56 label: QSFP56 color: '#9933cc' - name: osfp label: OSFP color: '#0099cc' relationships: - name: interface peer: DcimInterface kind: Attribute optional: true cardinality: one order_weight: 1200 - name: spare_location peer: LocationHosting kind: Attribute optional: true cardinality: one order_weight: 1500 - name: manufacturer peer: OrganizationManufacturer cardinality: one kind: Attribute optional: true order_weight: 1350 nodes: - name: StandardSFP namespace: Dcim inherit_from: - DcimGenericSFP description: Standard SFP module for various types (e.g., LR, SR, T). label: Standard SFP icon: mdi:gpu menu_placement: DcimGenericSFP - name: BidiSFP namespace: Dcim inherit_from: - DcimGenericSFP description: Bidirectional SFP supporting two wavelengths for single-fiber operation. label: Bidirectional SFP icon: lineicons:arrow-both-direction-vertical-1 menu_placement: DcimGenericSFP attributes: - name: wavelength_tx label: Transmit Wavelength (nm) kind: Number optional: false description: Transmit wavelength in nm. order_weight: 1175 - name: wavelength_rx label: Receive Wavelength (nm) kind: Number optional: false description: Receive wavelength in nm. order_weight: 1150 extensions: nodes: - kind: DcimInterface relationships: - name: plugged_sfp peer: DcimGenericSFP cardinality: one optional: true - kind: LocationHosting relationships: - name: spare_sfps peer: DcimGenericSFP cardinality: many optional: true - kind: OrganizationManufacturer relationships: - name: sfps label: SFPs peer: DcimGenericSFP cardinality: many optional: true ``` --- # SNMP This schema extension contains models for SNMP Communities and SNMP Clients. As you can see this extension is not linked to Tenancy or Device, as you could decide to link the Community to different models based on your use case. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) ## Nodes[​](#nodes "Direct link to Nodes") ### CommunityV2[​](#communityv2 "Direct link to CommunityV2") * **Label:** SNMP v1/v2c * **Description:** SNMP v1/v2c community configuration. * **Namespace:** Snmp * **Icon:** iconoir :community * **Human Friendly ID:** name\_\_value * **Inherit From:** SnmpCommunity #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------------- | ----------- | -------- | -------- | -------------- | ------- | | community\_string | | Password | | | | | access | | Text | | | | ### CommunityV3[​](#communityv3 "Direct link to CommunityV3") * **Label:** SNMP v3 * **Description:** SNMP version 3 configuration with enhanced security. * **Namespace:** Snmp * **Icon:** iconoir :community * **Human Friendly ID:** name\_\_value * **Inherit From:** SnmpCommunity #### Attributes[​](#attributes-1 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------------- | ----------- | -------- | -------- | -------------- | ---------------------------------- | | username | | Text | | | | | auth\_protocol | | Text | | | | | auth\_password | | Password | True | | | | privacy\_protocol | | Text | | | | | privacy\_password | | Password | True | | | | security\_level | | Dropdown | | | noAuthNoPriv, authNoPriv, authPriv | ### Client[​](#client "Direct link to Client") * **Label:** SNMP Client * **Description:** Represents an SNMP client that interacts with SNMP Community. * **Namespace:** Snmp * **Icon:** ph :user-list-light * **Human Friendly ID:** name\_\_value #### Attributes[​](#attributes-2 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ------------------- | ----------- | ---- | -------- | -------------- | ------- | | name | | Text | | | | | client\_description | | Text | True | | | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | --------- | ------------- | -------- | ----------- | ---- | | community | SnmpCommunity | | many | | ## Generics[​](#generics "Direct link to Generics") ### Community[​](#community "Direct link to Community") * **Label:** SNMP Community * **Description:** Generic model for SNMP community configurations. * **Namespace:** Snmp * **Icon:** iconoir :community * **Human Friendly ID:** name\_\_value #### Attributes[​](#attributes-3 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------- | ----------- | ---- | -------- | -------------- | ------- | | name | | Text | | | | | description | | Text | True | | | #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------- | ---------- | -------- | ----------- | --------- | | clients | SnmpClient | | many | Component | ## Code[​](#code "Direct link to Code") ``` version: '1.0' generics: - name: Community namespace: Snmp description: Generic model for SNMP community configurations. label: SNMP Community icon: iconoir:community human_friendly_id: - name__value display_label: name__value order_by: - name__value attributes: - name: name kind: Text order_weight: 1000 - name: description kind: Text order_weight: 1100 optional: true relationships: - name: clients peer: SnmpClient cardinality: many kind: Component nodes: - name: CommunityV2 namespace: Snmp description: SNMP v1/v2c community configuration. label: SNMP v1/v2c icon: iconoir:community menu_placement: SnmpCommunity human_friendly_id: - name__value display_label: name__value order_by: - name__value inherit_from: - SnmpCommunity attributes: - name: community_string kind: Password order_weight: 1300 - name: access kind: Text enum: - Read-Only - Read-Write order_weight: 1200 - name: CommunityV3 namespace: Snmp description: SNMP version 3 configuration with enhanced security. label: SNMP v3 icon: iconoir:community menu_placement: SnmpCommunity human_friendly_id: - name__value display_label: name__value order_by: - name__value inherit_from: - SnmpCommunity attributes: - name: username kind: Text order_weight: 1300 - name: auth_protocol label: Authentication Protocol kind: Text enum: - None - MD5 - SHA order_weight: 1400 - name: auth_password label: Authentication Password kind: Password order_weight: 1500 optional: true - name: privacy_protocol label: Privacy Protocol kind: Text enum: - None - DES - AES order_weight: 1600 - name: privacy_password label: Privacy Password kind: Password order_weight: 1700 optional: true - name: security_level label: Security Level kind: Dropdown choices: - name: noAuthNoPriv label: NoAuthNoPriv description: No authentication and no privacy. - name: authNoPriv label: AuthNoPriv description: Authentication but no privacy. - name: authPriv label: AuthPriv description: Both authentication and privacy. order_weight: 1200 - name: Client namespace: Snmp description: Represents an SNMP client that interacts with SNMP Community. label: SNMP Client icon: ph:user-list-light menu_placement: SnmpCommunity human_friendly_id: - name__value display_label: name__value order_by: - name__value attributes: - name: name kind: Text order_weight: 1000 - name: client_description label: Description kind: Text order_weight: 1100 optional: true relationships: - name: community peer: SnmpCommunity cardinality: many order_weight: 1200 ``` --- # Tenancy This schema extension introduces tenancy for some of the schema nodes (circuits...) ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) * [extensions/circuit](/schema-library/reference/circuit.md) ## Nodes[​](#nodes "Direct link to Nodes") ### Tenant[​](#tenant "Direct link to Tenant") * **Description:** A tenant is owning the corresponding entity * **Namespace:** Tenancy * **Icon:** mdi :domain * **Inherit From:** OrganizationGeneric #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | -------- | ---------------- | -------- | ----------- | --------- | | tags | BuiltinTag | True | many | Attribute | | location | LocationBuilding | True | many | | | circuit | DcimCircuit | True | many | | ## Code[​](#code "Direct link to Code") ``` version: '1.0' nodes: - name: Tenant namespace: Tenancy description: A tenant is owning the corresponding entity icon: mdi:domain include_in_menu: true inherit_from: - OrganizationGeneric relationships: - name: tags peer: BuiltinTag cardinality: many kind: Attribute optional: true order_weight: 3000 - name: location peer: LocationBuilding cardinality: many optional: true - name: circuit peer: DcimCircuit cardinality: many optional: true ``` --- # Topology This schema extension introduces abstract network pods and services running in the pods, such as MPLS and EVPN. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) ## Generics[​](#generics "Direct link to Generics") ### Generic[​](#generic "Direct link to Generic") * **Label:** Topology * **Description:** Generic model for topology. * **Namespace:** Topology * **Icon:** carbon :network-3 #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------- | ----------- | ---- | -------- | -------------- | ------- | | description | | Text | True | | | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | -------- | --------------- | -------- | ----------- | --------- | | location | LocationGeneric | True | one | Attribute | ## Code[​](#code "Direct link to Code") ``` version: '1.0' generics: - name: Generic namespace: Topology description: Generic model for topology. label: Topology icon: carbon:network-3 attributes: - name: description kind: Text order_weight: 1300 optional: true relationships: - name: location peer: LocationGeneric optional: true cardinality: one kind: Attribute ``` --- # Users This schema extension contains models for account management. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) ## Nodes[​](#nodes "Direct link to Nodes") ### Group[​](#group "Direct link to Group") * **Label:** User Groups * **Description:** User Group * **Namespace:** User * **Icon:** iconoir :group * **Human Friendly ID:** name\_\_value #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ------------- | ----------- | -------- | -------- | -------------- | -------------------------- | | name | | Text | | | | | description | | Text | True | | | | idle\_timeout | | Number | | | | | permissions | | Dropdown | False | | admin, operator, read-only | ### Account[​](#account "Direct link to Account") * **Label:** User Account * **Description:** User login and authentication * **Namespace:** User * **Icon:** mdi :account-key * **Human Friendly ID:** name\_\_value #### Attributes[​](#attributes-1 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ------------ | ---------------------------------------------- | -------- | -------- | -------------- | ------- | | name | The login username | Text | | | | | full\_name | Full name of the account | Text | True | | | | ssh\_key | SSH key for secure access | Password | True | | | | password | Password for login (alternative to SSH key) | Password | True | | | | mfa\_enabled | Whether multi-factor authentication is enabled | Boolean | | False | | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------ | ------------------- | -------- | ----------- | --------- | | user\_group | UserGroup | False | one | Attribute | | organization | OrganizationGeneric | False | one | Parent | ## Extensions[​](#extensions "Direct link to Extensions") note In this context "extensions" refer to modifications or additions to the existing schema, such as adding new attributes, relationships, or other schema elements. ### OrganizationGeneric[​](#organizationgeneric "Direct link to OrganizationGeneric") #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | -------- | ----------- | -------- | ----------- | --------- | | accounts | UserAccount | | many | Component | ## Code[​](#code "Direct link to Code") ``` version: '1.0' nodes: - name: Group namespace: User description: User Group label: User Groups icon: iconoir:group display_label: name__value order_by: - name__value human_friendly_id: - name__value attributes: - name: name kind: Text order_weight: 1000 unique: true - name: description kind: Text optional: true order_weight: 1100 - name: idle_timeout label: Idle Timeout (s) kind: Number order_weight: 1300 - name: permissions kind: Dropdown optional: false choices: - name: admin description: All rights on device. color: '#E6E6FA' - name: operator description: Operator right on configuration. color: '#E6E6FA' - name: read-only description: Read only right on configuration. color: '#E6E6FA' order_weight: 1200 - name: Account namespace: User description: User login and authentication label: User Account icon: mdi:account-key display_label: name__value order_by: - name__value human_friendly_id: - name__value attributes: - name: name label: Username kind: Text description: The login username order_weight: 1000 unique: true - name: full_name kind: Text optional: true description: Full name of the account order_weight: 1100 - name: ssh_key kind: Password optional: true description: SSH key for secure access order_weight: 1300 - name: password kind: Password optional: true description: Password for login (alternative to SSH key) order_weight: 1400 - name: mfa_enabled kind: Boolean default_value: false description: Whether multi-factor authentication is enabled order_weight: 1500 relationships: - name: user_group peer: UserGroup cardinality: one optional: false kind: Attribute order_weight: 1200 - name: organization peer: OrganizationGeneric optional: false cardinality: one kind: Parent extensions: nodes: - kind: OrganizationGeneric relationships: - name: accounts kind: Component peer: UserAccount description: List of Accounts under this organization cardinality: many ``` --- # VLAN This schema extension contains models to support VLANs in you network. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) ## Nodes[​](#nodes "Direct link to Nodes") ### VLAN[​](#vlan "Direct link to VLAN") * **Label:** VLAN * **Description:** A VLAN is isolated layer two domain * **Namespace:** Ipam * **Icon:** mdi :lan-pending * **Uniqueness Constraints:** * vlan\_id\_\_value, l2domain * **Human Friendly ID:** name\_\_value #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------- | ----------- | -------- | -------- | -------------- | ------------------------------------------ | | name | | Text | | | | | description | | Text | True | | | | vlan\_id | | Number | | | | | status | | Dropdown | | | active, provisioning, maintenance, drained | | role | | Dropdown | True | | server, management, user | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | -------- | --------------- | -------- | ----------- | --------- | | location | LocationHosting | True | many | | | prefixes | IpamPrefix | True | many | | | l2domain | IpamL2Domain | False | one | Attribute | ### L2Domain[​](#l2domain "Direct link to L2Domain") * **Label:** Layer 2 Domain * **Description:** Represents layer 2 domain. * **Namespace:** Ipam * **Icon:** mdi :domain-switch * **Human Friendly ID:** name\_\_value #### Attributes[​](#attributes-1 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ---- | ----------- | ---- | -------- | -------------- | ------- | | name | | Text | | | | #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ----- | -------- | -------- | ----------- | --------- | | vlans | IpamVLAN | True | many | Component | ## Extensions[​](#extensions "Direct link to Extensions") note In this context "extensions" refer to modifications or additions to the existing schema, such as adding new attributes, relationships, or other schema elements. ### IpamPrefix[​](#ipamprefix "Direct link to IpamPrefix") #### Relationships[​](#relationships-2 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ---- | -------- | -------- | ----------- | --------- | | vlan | IpamVLAN | True | one | Attribute | ### InterfaceLayer2[​](#interfacelayer2 "Direct link to InterfaceLayer2") #### Relationships[​](#relationships-3 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | -------------- | -------- | -------- | ----------- | ------- | | untagged\_vlan | IpamVLAN | True | one | Generic | | tagged\_vlan | IpamVLAN | True | many | Generic | ### LocationHosting[​](#locationhosting "Direct link to LocationHosting") #### Relationships[​](#relationships-4 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ----- | -------- | -------- | ----------- | ---- | | vlans | IpamVLAN | True | many | | ## Code[​](#code "Direct link to Code") ``` version: '1.0' nodes: - name: VLAN namespace: Ipam description: A VLAN is isolated layer two domain label: VLAN icon: mdi:lan-pending menu_placement: IpamL2Domain uniqueness_constraints: - - vlan_id__value - l2domain human_friendly_id: - name__value order_by: - name__value display_label: name__value attributes: - name: name kind: Text - name: description kind: Text optional: true - name: vlan_id kind: Number - name: status kind: Dropdown choices: - name: active label: Active description: Fully operational and currently in service. color: '#7fbf7f' - name: provisioning label: Provisioning description: In the process of being set up and configured. color: '#ffff7f' - name: maintenance label: Maintenance description: Undergoing routine maintenance or repairs. color: '#ffd27f' - name: drained label: Drained description: Temporarily taken out of service. color: '#bfbfbf' - name: role kind: Dropdown optional: true choices: - name: server label: Server description: Dedicated systems for managing networked resources. color: '#c4bed7' - name: management label: Management description: Network segments for administrative and control tasks. color: '#9af1e1' - name: user label: User description: Segments designed for end-user access and activities. color: '#a0b78d' relationships: - name: location peer: LocationHosting optional: true cardinality: many - name: prefixes peer: IpamPrefix optional: true cardinality: many - name: l2domain peer: IpamL2Domain optional: false cardinality: one kind: Attribute order_weight: 1200 - name: L2Domain namespace: Ipam description: Represents layer 2 domain. label: Layer 2 Domain icon: mdi:domain-switch human_friendly_id: - name__value display_label: name__value attributes: - name: name kind: Text order_weight: 1000 relationships: - name: vlans peer: IpamVLAN optional: true cardinality: many kind: Component extensions: nodes: - kind: IpamPrefix relationships: - name: vlan peer: IpamVLAN optional: true cardinality: one kind: Attribute order_weight: 1400 - kind: InterfaceLayer2 relationships: - name: untagged_vlan label: Untagged VLAN peer: IpamVLAN optional: true cardinality: one kind: Generic identifier: interface_l2__untagged_vlan - name: tagged_vlan label: Tagged VLANs peer: IpamVLAN optional: true cardinality: many kind: Generic identifier: interface_l2__tagged_vlan - kind: LocationHosting relationships: - name: vlans label: VLANs peer: IpamVLAN cardinality: many optional: true ``` --- # VLAN Translation This schema extension is based on Juniper VLAN MAP, and not yet test out for other vendors. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) ## Nodes[​](#nodes "Direct link to Nodes") ### MapInOut[​](#mapinout "Direct link to MapInOut") * **Label:** VLAN Map In/Out * **Description:** VLAN Mapping for In/Out operations * **Namespace:** Network * **Icon:** ph :swap * **Uniqueness Constraints:** * interface, direction\_\_value #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ------------------------ | ------------------------------------------------------- | -------- | -------- | -------------- | ------------------------------------------------------------------------ | | direction | Direction of the mapping | Dropdown | True | | input, output | | operation | Operation type | Dropdown | True | | pop, pop\_pop, pop\_swap, push, push\_push, swap, swap\_push, swap\_swap | | vlan\_id\_swap | VLAN ID to swap to during SWAP operations | Number | True | | | | inner\_vlan\_id | Inner VLAN ID for operations involving double VLAN tags | Number | True | | | | inner\_tag\_protocol\_id | Inner tag protocol ID (TPID) | Number | True | | | | tag\_protocol\_id | Tag protocol ID (TPID) for outer VLAN operations | Number | True | | | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | --------- | ------------- | -------- | ----------- | ------ | | interface | DcimInterface | False | one | Parent | ## Extensions[​](#extensions "Direct link to Extensions") note In this context "extensions" refer to modifications or additions to the existing schema, such as adding new attributes, relationships, or other schema elements. ### DcimInterface[​](#dciminterface "Direct link to DcimInterface") #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ------------- | --------------- | -------- | ----------- | --------- | | network\_maps | NetworkMapInOut | | many | Component | ## Code[​](#code "Direct link to Code") ``` version: '1.0' nodes: - name: MapInOut namespace: Network label: VLAN Map In/Out icon: ph:swap menu_placement: DcimInterface include_in_menu: true description: VLAN Mapping for In/Out operations order_by: - interface__name__value - direction__value - operation__value uniqueness_constraints: - - interface - direction__value attributes: - name: direction kind: Dropdown description: Direction of the mapping label: Map Direction order_weight: 1050 choices: - name: input label: Input description: Input direction color: '#D2B4DE' - name: output label: Output description: Output direction color: '#A9CCE3' optional: true - name: operation kind: Dropdown description: Operation type label: Map Operation order_weight: 1100 choices: - name: pop label: POP description: Single POP operation color: '#B2D4E6' - name: pop_pop label: POP-POP description: Double POP operation color: '#AED6F1' - name: pop_swap label: POP-SWAP description: POP then SWAP operation color: '#A9DFBF' - name: push label: PUSH description: Single PUSH operation color: '#CDEACC' - name: push_push label: PUSH-PUSH description: Double PUSH operation color: '#9FA8DA' - name: swap label: SWAP description: Single SWAP operation color: '#D2B4DE' - name: swap_push label: SWAP-PUSH description: SWAP then PUSH operation color: '#C4B7E6' - name: swap_swap label: SWAP-SWAP description: Double SWAP operation color: '#CBC3E3' optional: true - name: vlan_id_swap label: VLAN ID Swap kind: Number description: VLAN ID to swap to during SWAP operations optional: true order_weight: 1200 - name: inner_vlan_id label: Inner VLAN ID kind: Number description: Inner VLAN ID for operations involving double VLAN tags optional: true order_weight: 1300 - name: inner_tag_protocol_id label: Inner Tag Protocol ID kind: Number description: Inner tag protocol ID (TPID) optional: true order_weight: 1400 - name: tag_protocol_id label: Tag Protocol ID kind: Number description: Tag protocol ID (TPID) for outer VLAN operations optional: true order_weight: 1500 relationships: - name: interface kind: Parent peer: DcimInterface description: Interface to which the Input/Output VLAN mapping is applied cardinality: one optional: false label: Interface order_weight: 1000 extensions: nodes: - kind: DcimInterface relationships: - name: network_maps kind: Component peer: NetworkMapInOut description: Interface Input/Output VLAN mapping cardinality: many label: Input/Output MAP order_weight: 1600 ``` --- # VRF This schema extension contains models to support VRF in your network. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) ## Nodes[​](#nodes "Direct link to Nodes") ### VRF[​](#vrf "Direct link to VRF") * **Label:** VRF * **Description:** A VRF is isolated layer three domain * **Namespace:** Ipam * **Icon:** mdi :router * **Human Friendly ID:** name\_\_value #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------- | ----------- | ---- | -------- | -------------- | ------- | | name | | Text | False | | | | vrf\_rd | | Text | True | | | | description | | Text | True | | | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ---------- | ------------------ | -------- | ----------- | --------- | | namespace | BuiltinIPNamespace | False | one | Attribute | | import\_rt | IpamRouteTarget | True | one | Attribute | | export\_rt | IpamRouteTarget | True | one | Attribute | ### RouteTarget[​](#routetarget "Direct link to RouteTarget") * **Label:** Route Target * **Description:** Route Target (RFC 4360) * **Namespace:** Ipam * **Icon:** mdi :target * **Human Friendly ID:** name\_\_value #### Attributes[​](#attributes-1 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | ----------- | ----------- | ---- | -------- | -------------- | ------- | | name | | Text | | | | | description | | Text | True | | | #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ---- | ------- | -------- | ----------- | ---- | | vrf | IpamVRF | True | many | | ## Extensions[​](#extensions "Direct link to Extensions") note In this context "extensions" refer to modifications or additions to the existing schema, such as adding new attributes, relationships, or other schema elements. ### IpamPrefix[​](#ipamprefix "Direct link to IpamPrefix") #### Relationships[​](#relationships-2 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ---- | ------- | -------- | ----------- | --------- | | vrf | IpamVRF | True | one | Attribute | ### IpamIPAddress[​](#ipamipaddress "Direct link to IpamIPAddress") #### Relationships[​](#relationships-3 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ---- | ------- | -------- | ----------- | --------- | | vrf | IpamVRF | True | one | Attribute | ## Code[​](#code "Direct link to Code") ``` version: '1.0' nodes: - name: VRF namespace: Ipam description: A VRF is isolated layer three domain label: VRF icon: mdi:router human_friendly_id: - name__value order_by: - name__value display_label: name__value attributes: - name: name kind: Text optional: false unique: true - name: vrf_rd label: Route Distinguisher kind: Text optional: true - name: description kind: Text optional: true order_weight: 1200 relationships: - name: namespace peer: BuiltinIPNamespace optional: false cardinality: one kind: Attribute - name: import_rt identifier: vrf__import label: Import Targets peer: IpamRouteTarget optional: true cardinality: one kind: Attribute - name: export_rt identifier: vrf__export label: Export Targets peer: IpamRouteTarget optional: true cardinality: one kind: Attribute - name: RouteTarget namespace: Ipam description: Route Target (RFC 4360) label: Route Target icon: mdi:target menu_placement: IpamVRF human_friendly_id: - name__value order_by: - name__value display_label: name__value attributes: - name: name kind: Text unique: true - name: description kind: Text optional: true relationships: - name: vrf peer: IpamVRF optional: true cardinality: many extensions: nodes: - kind: IpamPrefix relationships: - name: vrf label: VRF peer: IpamVRF optional: true cardinality: one kind: Attribute order_weight: 1150 - kind: IpamIPAddress relationships: - name: vrf label: VRF peer: IpamVRF optional: true cardinality: one kind: Attribute order_weight: 1150 ``` --- # VRRP This schema extension contains models for VRRP. ## Details[​](#details "Direct link to Details") * **Dependencies:** * [base](/schema-library/reference/dcim.md) ## Nodes[​](#nodes "Direct link to Nodes") ### VRRPGroup[​](#vrrpgroup "Direct link to VRRPGroup") * **Label:** VRRP Group * **Description:** VRRP Group configuration * **Namespace:** Network * **Icon:** fluent :virtual-network-20-filled * **Human Friendly ID:** name\_\_value #### Attributes[​](#attributes "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | -------- | ------------------------ | -------------- | -------- | -------------- | ------- | | name | Unique name of the entry | Text | | | | | group | VRRP Group | Text | | | | | password | VRRP Password/Key | HashedPassword | True | | | #### Relationships[​](#relationships "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ---------------- | -------------------- | -------- | ----------- | --------- | | ip\_address | IpamIPAddress | True | many | Attribute | | vrrp\_interfaces | NetworkVRRPInterface | | many | Component | ### VRRPInterface[​](#vrrpinterface "Direct link to VRRPInterface") * **Label:** VRRP Interface * **Description:** VRRP Interface configuration * **Namespace:** Network * **Icon:** carbon :interface-usage * **Uniqueness Constraints:** * vrrp\_group, interface #### Attributes[​](#attributes-1 "Direct link to Attributes") | name | description | kind | optional | default\_value | choices | | -------- | ------------------------------------------ | ------ | -------- | -------------- | ------- | | priority | VRRP Priority (Should be between 0 to 255) | Number | | 100 | | #### Relationships[​](#relationships-1 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ----------- | ---------------- | -------- | ----------- | --------- | | vrrp\_group | NetworkVRRPGroup | False | one | Attribute | | interface | InterfaceLayer3 | False | one | Attribute | ## Extensions[​](#extensions "Direct link to Extensions") note In this context "extensions" refer to modifications or additions to the existing schema, such as adding new attributes, relationships, or other schema elements. ### InterfaceLayer3[​](#interfacelayer3 "Direct link to InterfaceLayer3") #### Relationships[​](#relationships-2 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ---- | -------------------- | -------- | ----------- | --------- | | vrrp | NetworkVRRPInterface | | one | Component | ### IpamIPAddress[​](#ipamipaddress "Direct link to IpamIPAddress") #### Relationships[​](#relationships-3 "Direct link to Relationships") | name | peer | optional | cardinality | kind | | ---- | ---------------- | -------- | ----------- | --------- | | vrrp | NetworkVRRPGroup | | one | Component | ## Code[​](#code "Direct link to Code") ``` version: '1.0' nodes: - name: VRRPGroup namespace: Network label: VRRP Group icon: fluent:virtual-network-20-filled description: VRRP Group configuration display_label: name__value order_by: - name__value human_friendly_id: - name__value attributes: - name: name kind: Text label: Name unique: true description: Unique name of the entry order_weight: 1000 - name: group kind: Text description: VRRP Group label: Group order_weight: 1100 - name: password kind: HashedPassword description: VRRP Password/Key label: Password optional: true order_weight: 1400 relationships: - name: ip_address on_delete: cascade description: VRRP IP (v4 or v6) label: IP Address peer: IpamIPAddress optional: true cardinality: many kind: Attribute order_weight: 1200 - name: vrrp_interfaces on_delete: cascade kind: Component peer: NetworkVRRPInterface cardinality: many label: VRRP Interfaces order_weight: 1300 - name: VRRPInterface namespace: Network label: VRRP Interface icon: carbon:interface-usage menu_placement: NetworkVRRPGroup description: VRRP Interface configuration order_by: - priority__value uniqueness_constraints: - - vrrp_group - interface attributes: - name: priority kind: Number label: VRRP Priority description: VRRP Priority (Should be between 0 to 255) regex: ^(25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9])$ default_value: 100 order_weight: 1100 relationships: - name: vrrp_group description: VRRP Group label: VRRP Group peer: NetworkVRRPGroup optional: false cardinality: one kind: Attribute order_weight: 1200 - name: interface description: Interface L3 label: Interface peer: InterfaceLayer3 optional: false cardinality: one kind: Attribute order_weight: 1300 extensions: nodes: - kind: InterfaceLayer3 relationships: - name: vrrp kind: Component peer: NetworkVRRPInterface description: VRRP Interface Configuration cardinality: one label: VRRP order_weight: 1500 - kind: IpamIPAddress relationships: - name: vrrp kind: Component peer: NetworkVRRPGroup description: Part of VRRP Group cardinality: one label: VRRP Group order_weight: 1600 ``` --- # Infrahub Skills Infrahub Skills is an open-source AI skills package that gives your AI coding assistant built-in knowledge of Infrahub's data model, conventions, and workflow patterns. Instead of learning the platform from documentation alone, describe what you want to build in plain language and the skills produce valid, best-practice Infrahub resources — schemas, object data, generators, transforms, checks, and menus — ready to load into a running instance. The skills cover the full Infrahub development lifecycle. Each skill embeds the rules, examples, and references the AI needs for a specific task, and loads only the context required — so the assistant stays focused and accurate without exhausting its context window. ## What you can do with it[​](#what-you-can-do-with-it "Direct link to What you can do with it") ### Design and build your data model[​](#design-and-build-your-data-model "Direct link to Design and build your data model") Describe your data model in plain language and the skills produce a valid Infrahub schema — node types, attribute kinds, relationships, generics, display properties — without requiring manual study of the schema format. Naming conventions, relationship identifiers, and attribute type rules are applied at the point of generation. ### Build generators, transforms, and checks[​](#build-generators-transforms-and-checks "Direct link to Build generators, transforms, and checks") Create Python generators that auto-create infrastructure objects from design definitions. Write Python or Jinja2 transforms that convert Infrahub data to device configs, reports, or other formats. Build validation checks that run in proposed change pipelines — with Infrahub-specific conventions handled automatically. ### Populate and manage data[​](#populate-and-manage-data "Direct link to Populate and manage data") Produce YAML data files for infrastructure objects — devices, locations, organizations, interfaces — with correct file structure, value mappings, and load order. Generate batches of objects from descriptions or seed data for new instances. ### Query and analyze a live instance[​](#query-and-analyze-a-live-instance "Direct link to Query and analyze a live instance") The Data Analyzer skill connects to a running Infrahub instance via MCP and answers operational questions directly: cross-node correlation, drift detection, blast-radius analysis, and data quality audits — without writing GraphQL queries manually. ### Audit your repository[​](#audit-your-repository "Direct link to Audit your repository") Run a comprehensive audit of an Infrahub repository against all best practices. The Repo Auditor checks schema files, object files, Python components, `.infrahub.yml` registration, and project structure — then reports what to fix and why. ### Customize the UI[​](#customize-the-ui "Direct link to Customize the UI") Design custom navigation menus for the Infrahub web interface — sidebar structure, section groupings, icons, and ordering — from a plain language description. ## Three ways to work[​](#three-ways-to-work "Direct link to Three ways to work") ### Direct mode[​](#direct-mode "Direct link to Direct mode") For well-scoped, single-skill tasks. Describe what you need and the AI matches it to the right skill automatically. Best for adding an attribute, creating a check, populating a batch of objects, or any task that maps to a single skill. ### Spec-Driven Development (SDD)[​](#spec-driven-development-sdd "Direct link to Spec-Driven Development (SDD)") For complex or multi-part builds. The AI reasons through requirements with you first — capturing what needs to be built, validating the approach against Infrahub conventions, and laying out a task plan for review. Nothing is generated until you approve the plan. Best for designing new schema domains, building generator chains, or any task that involves design decisions across multiple skills. See [Spec-Driven Development](/skills/spec-driven-development.md) for details. ### End-to-end execution[​](#end-to-end-execution "Direct link to End-to-end execution") For full lifecycle workflows. The AI generates files, loads them with `infrahubctl`, validates the result, and iterates if something fails — all in a single session. ## Skills included[​](#skills-included "Direct link to Skills included") | Skill | What it does | | -------------------------------------------------------------------- | ------------------------------------------------------------------------------- | | [Schema Manager](/skills/skills-reference/managing-schemas.md) | Creates and validates Infrahub schema YAML from natural language | | [Object Manager](/skills/skills-reference/managing-objects.md) | Produces YAML data files for infrastructure objects | | [Check Manager](/skills/skills-reference/managing-checks.md) | Builds Python validation checks for proposed change pipelines | | [Generator Manager](/skills/skills-reference/managing-generators.md) | Creates design-driven generators that auto-create infrastructure objects | | [Transform Manager](/skills/skills-reference/managing-transforms.md) | Builds Python or Jinja2 transforms for config generation and data export | | [Menu Manager](/skills/skills-reference/managing-menus.md) | Designs custom navigation menus for the Infrahub web UI | | [Data Analyzer](/skills/skills-reference/analyzing-data.md) | Queries and analyzes live Infrahub data via MCP | | [Repo Auditor](/skills/skills-reference/auditing-repo.md) | Audits a repository against Infrahub best practices | | [Data Importer](/skills/skills-reference/importing-data.md) | Converts CSV/TSV inputs into Infrahub object YAML and loads onto a fresh branch | A shared reference library (`infrahub-common`) provides GraphQL query syntax, `.infrahub.yml` configuration format, and YAML structure conventions to all skills. ## Compatibility[​](#compatibility "Direct link to Compatibility") Infrahub Skills works with any AI tool that supports the Agent Skills format or reads custom context files from the project directory: * **Claude Code** — full support including auto-detection and direct skill invocation * **GitHub Copilot** — passive skill activation from project context * **Cursor** — passive skill activation from project context * **Windsurf** — passive skill activation from project context * **Amp, Cline, Codex** — passive skill activation from project context See [Installation & Setup](/skills/installation-setup.md) for tool-specific configuration. ## Next steps[​](#next-steps "Direct link to Next steps") * **Get started** — [Installation & Setup](/skills/installation-setup.md) * **Understand the architecture** — [How It Works](/skills/how-it-works.md) * **Plan a complex build** — [Spec-Driven Development](/skills/spec-driven-development.md) * **Explore individual skills** — [Skills Reference](/skills/skills-reference/managing-schemas.md) --- # How It Works Infrahub Skills gives an AI assistant domain-specific knowledge about the Infrahub platform. Each skill is a structured set of Markdown files — rules, examples, and references — that the AI reads at the point it's needed. The assistant matches each user request to the relevant skill automatically, loads only the context required for that task, and applies the rules embedded in the skill. ## What a skill is[​](#what-a-skill-is "Direct link to What a skill is") A skill is a directory containing a `SKILL.md` entry point, modular rule files, examples, and references. Skills are designed to load progressively so they don't exhaust the AI's context window: * **Metadata** (always loaded, \~100 words) — the name and description in `SKILL.md` frontmatter. This is the triggering mechanism: if the description matches the user's request, the AI activates the skill and reads the full `SKILL.md`. * **SKILL.md body** (loaded on activation) — overview, workflow steps, and pointers to supporting files. Tells the AI what to do and where to look next. * **Supporting files** (loaded on demand) — individual rule files organized by category, `examples.md`, `reference.md`. The AI reads only the files relevant to the current task. ``` skills/infrahub-managing-schemas/ ├── SKILL.md # Entry point — overview and workflow ├── examples.md # Concrete schema examples ├── reference.md # Attribute types, relationship kinds ├── validation.md # Schema validation rules └── rules/ ├── naming.md # Naming convention rules ├── relationships.md # Relationship identifier rules ├── generics.md # Generic/hierarchy rules └── ... ``` ## How skills are triggered[​](#how-skills-are-triggered "Direct link to How skills are triggered") The AI infers whether a skill is relevant by matching the user's request against each skill's description. When a user asks "create a schema for VLAN management," the AI recognizes this matches the Schema Manager skill, activates it, and reads its `SKILL.md` to understand how to proceed. Users can also invoke skills directly by name in Claude Code: ``` /infrahub:managing-schemas /infrahub:managing-objects /infrahub:managing-checks ``` For tools other than Claude Code, skill activation is passive — the AI finds skill files through its normal file context and applies them when the request is relevant. No explicit invocation is needed. ## Auto-detection (Claude Code plugin)[​](#auto-detection-claude-code-plugin "Direct link to Auto-detection (Claude Code plugin)") When installed as a Claude Code plugin, a `SessionStart` hook runs on project open. The hook checks for Infrahub project markers: * `.infrahub.yml` * `infrahub.toml` * Schema files containing `version: "1.0"` with `nodes:` or `generics:` keys If markers are found, the hook outputs context that tells the AI which skills are available and how to use them — before the user types anything. With other AI tools (npx or manual copy), discovery happens because the skill files are present in the project directory. The AI finds them through its normal file context when the user's request is relevant. ## The infrahub-common library[​](#the-infrahub-common-library "Direct link to The infrahub-common library") A shared set of references available to all skills. Skills reference these files rather than duplicating the content, which keeps rules consistent across the entire package. Contents: * **GraphQL query syntax** — how to structure queries for Infrahub's API * **`.infrahub.yml` configuration format** — how to register checks, generators, transforms, and artifact definitions * **YAML structure** — correct structure for schema and object definition files * **Shared rules** — git integration patterns, protocol/generated file handling, Python environment detection, and display label caching ## Context efficiency[​](#context-efficiency "Direct link to Context efficiency") Each skill is self-contained but references shared resources by file path rather than embedding them. When the AI activates a skill, it loads: 1. The `SKILL.md` body (\~500–1000 words) 2. Only the rule files relevant to the current task 3. Shared references from `infrahub-common` as needed This progressive loading means a simple task like "add an attribute to this node" loads far less context than a complex task like "design a new schema domain with generics and hierarchies." The AI manages this automatically. ## Supported AI tools[​](#supported-ai-tools "Direct link to Supported AI tools") | AI Tool | Installation Method | Auto-detection | Direct Skill Invocation | | -------------------- | ---------------------------------- | ------------------ | ---------------------------------------- | | Claude Code (plugin) | `/plugin install infrahub@opsmill` | Yes (session hook) | Yes (`/infrahub:managing-schemas`, etc.) | | Claude Code (npx) | `npx skills add` | No | Yes | | GitHub Copilot | `npx skills add` | No | No — passive only | | Cursor | `npx skills add` | No | No — passive only | | Windsurf | `npx skills add` | No | No — passive only | | Amp | `npx skills add` | No | No — passive only | | Cline | `npx skills add` | No | No — passive only | | Codex | `npx skills add` | No | No — passive only | **Auto-detection** means the tool automatically recognizes an Infrahub project and activates skills without the user explicitly asking. This is a Claude Code plugin feature via the session start hook. **Direct skill invocation** means the user can call a skill by name (e.g., `/infrahub:managing-schemas`). This is a Claude Code feature. All other tools are passive — they read skill content as context when the request is relevant. For passive tools, describe what you want and the AI applies the right skill automatically. --- # Installation & Setup Infrahub Skills can be installed into any AI tool that supports skills or custom context files. Installation takes one command for most setups — install into a specific Infrahub repository to work in that project, or install globally to use the skills across all your projects. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * **AI coding assistant** — any tool that supports the Agent Skills format or reads custom context files: Claude Code, GitHub Copilot, Cursor, Windsurf, Amp, Cline, Codex, and others * **Infrahub instance** — version 1.7 or later (earlier versions may work but are not tested). See the [Infrahub installation docs](https://docs.infrahub.app/) for setup instructions * **infrahubctl** — required for loading generated schemas, objects, and running generators. See the [infrahubctl docs](https://docs.infrahub.app/infrahubctl) * **Infrahub MCP server** (optional) — required only for the [Data Analyzer](/skills/skills-reference/analyzing-data.md) skill, beneficial but not required for other skills. See the [MCP server docs](https://docs.infrahub.app/mcp) ## Installation[​](#installation "Direct link to Installation") * npx (Recommended) * Claude Code Plugin * Manual Copy The `npx` installer detects which AI tools you have configured and installs the skills in the correct format for each one automatically. ``` npx skills add opsmill/infrahub-skills ``` The installer determines whether to install the skills in the current project directory or globally. If installing per-project, run the command from your Infrahub project root. Install from the Claude Code plugin marketplace for a global installation that works across all Infrahub projects: ``` /plugin marketplace add opsmill/claude-marketplace /plugin install infrahub@opsmill ``` The plugin auto-detects Infrahub projects on session start by looking for `.infrahub.yml`, `infrahub.toml`, or schema file markers in the project directory. For any tool that reads files from the project directory: ``` git clone https://github.com/opsmill/infrahub-skills.git cp -r infrahub-skills/skills /path/to/your-project/ rm -rf infrahub-skills ``` warning Always include `skills/infrahub-common/` — it contains shared references that all skills depend on. Omitting it will cause skills to produce incomplete or incorrect output. ## Tool-specific configuration[​](#tool-specific-configuration "Direct link to Tool-specific configuration") After installing via npx or manual copy, some tools benefit from a small configuration file to activate the skills more reliably. These tools will use the skills without these files, but adding them improves consistency. ### GitHub Copilot[​](#github-copilot "Direct link to GitHub Copilot") Create `.github/instructions/infrahub.instructions.md` in your project root: ``` --- applyTo: "**" --- Use the skills in the `skills/` directory when working on Infrahub schemas, objects, checks, generators, transforms, or menus. Each skill's SKILL.md file describes when and how to use it. ``` ### Cursor[​](#cursor "Direct link to Cursor") Create `.cursor/rules/infrahub.mdc` in your project root: ``` --- description: Infrahub development skills for schemas, objects, checks, generators, transforms, and menus globs: "**/*.{yml,yaml,py,gql,j2}" alwaysApply: false --- Use the skills in the `skills/` directory when working on Infrahub resources. Each skill's SKILL.md file describes when and how to use it. ``` ### Windsurf[​](#windsurf "Direct link to Windsurf") Windsurf reads Markdown files from the project automatically. Optionally, reference the skills from `.windsurfrules`: ``` For Infrahub development tasks, use the skills in the `skills/` directory. Each skill's SKILL.md file describes when and how to use it. ``` ### Claude Code (plugin)[​](#claude-code-plugin "Direct link to Claude Code (plugin)") No additional configuration required after plugin install. The session hook fires on project open and provides context about available skills automatically. ## MCP server setup (Data Analyzer skill)[​](#mcp-server-setup-data-analyzer-skill "Direct link to MCP server setup (Data Analyzer skill)") The [Data Analyzer](/skills/skills-reference/analyzing-data.md) skill uses the Infrahub MCP server to query live data from a running instance. All other skills work without it. See the [Infrahub MCP server docs](https://docs.infrahub.app/mcp) for setup instructions. tip Without MCP, the AI can still query Infrahub data by constructing GraphQL API calls directly. The interaction is more verbose, but functional. ## Verifying the installation[​](#verifying-the-installation "Direct link to Verifying the installation") 1. **Check the files are present** — confirm that `skills/*/SKILL.md` files exist in your project directory (or globally, depending on your install method). 2. **Test skill activation** — open the project in your AI tool and ask: > What Infrahub skills do you have available? The AI should list the installed skills. 3. **Test skill output** — ask the AI to create a simple schema: > Create a Device node with a name and status attribute. If the skills are active, the output should: * Start with `version: "1.0"` * Use `kind: Dropdown` for the status field with choice objects * Include `human_friendly_id` and `display_label` Without the skills, most AI assistants produce a simpler or incorrect schema that misses these conventions. ## Upgrading[​](#upgrading "Direct link to Upgrading") * npx * Claude Code Plugin * Manual ``` npx skills update ``` ``` /plugin update infrahub@opsmill ``` Pull the latest from GitHub and re-copy: ``` git clone https://github.com/opsmill/infrahub-skills.git cp -r infrahub-skills/skills /path/to/your-project/ rm -rf infrahub-skills ``` --- ## [📄️Release 1.2.7](/skills/release-notes/release-1_2_7.md) [Build generators that reuse query results instead of requesting each related object separately.](/skills/release-notes/release-1_2_7.md) --- # Release 0.0.1 | Release Number | 0.0.1 | | -------------- | ------------------------------------------------------------------------ | | Release Type | Feature | | Release Date | April 9th, 2026 | | Tag | [v0.0.1](https://github.com/opsmill/infrahub-skills/releases/tag/v0.0.1) | ## Release summary[​](#release-summary "Direct link to Release summary") Describe a schema, object, check, generator, transform, or menu in plain language and get valid Infrahub resources without first mastering the platform's data model. Infrahub Skills is an open-source skills package for AI coding assistants, and this first release ships eight skills across the build, query, and audit lifecycle. ### Build Infrahub resources from natural language[​](#build-infrahub-resources-from-natural-language "Direct link to Build Infrahub resources from natural language") Eight skills cover the Infrahub development lifecycle, one per task, each with the rules, examples, and references for that task. * Generate schemas, objects, checks, generators, transforms, and menus, one skill per task — [managing-schemas](/skills/skills-reference/managing-schemas.md), [managing-objects](/skills/skills-reference/managing-objects.md), and the rest. * Query live data with [analyzing-data](/skills/skills-reference/analyzing-data.md), and audit a repository against Infrahub conventions with [auditing-repo](/skills/skills-reference/auditing-repo.md), which checks details that span files, such as whether query names match between Python and `.infrahub.yml`. * Apply conventions verified against the server: a relationship to a generic needs an explicit `kind:` and `data:` wrapper, `regex` / `min_length` / `max_length` move into a `parameters` block, and generated SDK protocol files are never edited manually. ### Let the right skill activate automatically inside an Infrahub repository[​](#let-the-right-skill-activate-automatically-inside-an-infrahub-repository "Direct link to Let the right skill activate automatically inside an Infrahub repository") Open an Infrahub project and the matching skill activates for the task at hand. Installed as a Claude Code plugin, a session-start hook detects the project automatically; with other tools, the skills are present in the project directory. * A `SessionStart` hook detects an Infrahub project from `.infrahub.yml`, `infrahub.toml`, or a schema file, then points the assistant at the matching skill. * Each skill description carries explicit `TRIGGER when:` and `DO NOT TRIGGER when:` patterns, so the matching skill activates for a request. * [analyzing-data](/skills/skills-reference/analyzing-data.md) and [auditing-repo](/skills/skills-reference/auditing-repo.md) run in a forked context, so their large reports run separately from the main conversation. ### Work on large schema and generator tasks without exhausting context[​](#work-on-large-schema-and-generator-tasks-without-exhausting-context "Direct link to Work on large schema and generator tasks without exhausting context") Only the guidance the current step needs is loaded, leaving room in the context for the work itself. * Skill metadata decides whether a skill applies; the `SKILL.md` body supplies the workflow; rule and reference files load only when a step needs them. * Each skill includes a step-by-step `## Workflow` that names the exact rule to read at each step. ## Minor changes[​](#minor-changes "Direct link to Minor changes") **Testing** * A skillgrade evaluation suite with deterministic Python graders checks each skill's behavior, including guards against tests that would pass on empty output. **Developer experience** * A shared rule protects generated SDK protocol files from manual edits. * `infrahubctl info` connectivity and version checks (with uv and Poetry environment detection) validate output against a running server. * CI automation: version auto-bump from PR labels, release-drafter notes, and a documentation build check. ## Full changelog[​](#full-changelog "Direct link to Full changelog") ### Added[​](#added "Direct link to Added") * Eight skills: managing-schemas, managing-objects, managing-checks, managing-generators, managing-transforms, managing-menus, auditing-repo, and analyzing-data ([#2](https://github.com/opsmill/infrahub-skills/pull/2), [#6](https://github.com/opsmill/infrahub-skills/pull/6), [#7](https://github.com/opsmill/infrahub-skills/pull/7)). * Project detection via a `SessionStart` hook ([#1](https://github.com/opsmill/infrahub-skills/pull/1)), and the skillgrade evaluation harness ([#13](https://github.com/opsmill/infrahub-skills/pull/13)). * Apache 2.0 license, with setup guidance for Claude Code, GitHub Copilot, and Cursor ([#3](https://github.com/opsmill/infrahub-skills/pull/3)). * CI automation: auto-bump, release-drafter, and documentation build ([#40](https://github.com/opsmill/infrahub-skills/pull/40)). See the [full list of merged pull requests](https://github.com/opsmill/infrahub-skills/releases/tag/v0.0.1). ## Skills included[​](#skills-included "Direct link to Skills included") * infrahub-managing-schemas * infrahub-managing-objects * infrahub-managing-checks * infrahub-managing-generators * infrahub-managing-transforms * infrahub-managing-menus * infrahub-auditing-repo * infrahub-analyzing-data --- # Release 1.2.0 | Release Number | 1.2.0 | | -------------- | ------------------------------------------------------------------------ | | Release Type | Maintenance | | Release Date | April 10th, 2026 | | Tag | [v1.2.0](https://github.com/opsmill/infrahub-skills/releases/tag/v1.2.0) | ## Release summary[​](#release-summary "Direct link to Release summary") The package version moves from `0.0.1` to `1.2.0` ahead of the automated release tooling. No skill changes. ## Full changelog[​](#full-changelog "Direct link to Full changelog") ### Changed[​](#changed "Direct link to Changed") * Plugin version bumped from `0.0.1` to `1.2.0`, with version metadata synced across every `SKILL.md`, before release-drafter and the auto-bump workflow manage versioning. ## Skills included[​](#skills-included "Direct link to Skills included") * infrahub-managing-schemas * infrahub-managing-objects * infrahub-managing-checks * infrahub-managing-generators * infrahub-managing-transforms * infrahub-managing-menus * infrahub-auditing-repo * infrahub-analyzing-data --- # Release 1.2.1 | Release Number | 1.2.1 | | -------------- | ------------------------------------------------------------------------ | | Release Type | Improvement | | Release Date | April 16th, 2026 | | Tag | [v1.2.1](https://github.com/opsmill/infrahub-skills/releases/tag/v1.2.1) | ## Release summary[​](#release-summary "Direct link to Release summary") Browse the installation guide, how-it-works, and per-skill reference on a documentation site, now available alongside the repository instead of only from inside an AI assistant. ## Full changelog[​](#full-changelog "Direct link to Full changelog") ### Added[​](#added "Direct link to Added") * A documentation site covering installation, how the skills work, and a per-skill reference ([#33](https://github.com/opsmill/infrahub-skills/pull/33)). ## Skills included[​](#skills-included "Direct link to Skills included") * infrahub-managing-schemas * infrahub-managing-objects * infrahub-managing-checks * infrahub-managing-generators * infrahub-managing-transforms * infrahub-managing-menus * infrahub-auditing-repo * infrahub-analyzing-data --- # Release 1.2.2 | Release Number | 1.2.2 | | -------------- | ------------------------------------------------------------------------ | | Release Type | Improvement | | Release Date | May 11th, 2026 | | Tag | [v1.2.2](https://github.com/opsmill/infrahub-skills/releases/tag/v1.2.2) | ## Release summary[​](#release-summary "Direct link to Release summary") Model schema patterns common in production OpsMill repositories so they pass Infrahub's validators on the first load. You can now express constructs that often fail validation — computed attributes, branch behavior, delete handling, menu placement, artifact targets, and object templates — and define relationships and register checks the way the server-side validator expects. ### Model schema patterns common in production repositories[​](#model-schema-patterns-common-in-production-repositories "Direct link to Model schema patterns common in production repositories") When you model a schema with one of these constructs, you no longer have to infer the shape Infrahub expects — you get it from the managing-schemas skill, with compliant and non-compliant examples. * Define an attribute whose value is computed from other fields with a Jinja2 expression (`computed_attribute`, paired with `read_only: true`), instead of storing it directly. * Mark an attribute `branch: agnostic` so its value applies across every branch, or leave it branch-aware (the default) so changes stay scoped to a branch until merge. * Set a relationship's `on_delete` to `cascade` or `no-action` to control whether deleting a node also deletes its related nodes. * Control where a node appears in the sidebar with `menu_placement`, instead of relying on the default position. * Extend `CoreArtifactTarget` so a node can be rendered against by artifact definitions, and set `generate_template: true` to create nodes from a reusable template. * Define a `kind: Parent` relationship as `optional: false` and `cardinality: one`, with at most one per node, so it matches the server-side validator. ## Bug fixes[​](#bug-fixes "Direct link to Bug fixes") * `check_definitions` does not accept a `query:` field, unlike `generator_definitions`. The query binds from the Python class's `query` attribute, which must match a `name` under the top-level `queries:` section; the configuration model forbids extra keys, so a stray `query:` makes the repository configuration fail validation. ## Full changelog[​](#full-changelog "Direct link to Full changelog") ### Added[​](#added "Direct link to Added") * Six schema rules from production repositories: computed attributes, branch behavior, relationship `on_delete`, menu placement, `CoreArtifactTarget`, and object templates ([#41](https://github.com/opsmill/infrahub-skills/pull/41)). ### Changed[​](#changed "Direct link to Changed") * `kind: Parent` constraints tightened to match the server-side validator ([#41](https://github.com/opsmill/infrahub-skills/pull/41)). ### Fixed[​](#fixed "Direct link to Fixed") * Clarified that `check_definitions` has no `query:` field ([#41](https://github.com/opsmill/infrahub-skills/pull/41)). ## Skills included[​](#skills-included "Direct link to Skills included") * infrahub-managing-schemas * infrahub-managing-objects * infrahub-managing-checks * infrahub-managing-generators * infrahub-managing-transforms * infrahub-managing-menus * infrahub-auditing-repo * infrahub-analyzing-data --- # Release 1.2.3 | Release Number | 1.2.3 | | -------------- | ------------------------------------------------------------------------ | | Release Type | Improvement | | Release Date | May 11th, 2026 | | Tag | [v1.2.3](https://github.com/opsmill/infrahub-skills/releases/tag/v1.2.3) | ## Release summary[​](#release-summary "Direct link to Release summary") Every `managing-*` skill now appears after a fresh install. A non-standard frontmatter field had hidden six of them from Claude Code's invocable list. ## Bug fixes[​](#bug-fixes "Direct link to Bug fixes") * Removed the non-standard `paths:` frontmatter from the six `managing-*` skills. Claude Code's loader treated `paths:` as unsupported and silently hid those skills — `/infrahub-managing-schemas` returned "Unknown skill" after a fresh install. All six now appear and invoke. ## Full changelog[​](#full-changelog "Direct link to Full changelog") ### Fixed[​](#fixed "Direct link to Fixed") * Removed unsupported `paths:` frontmatter that hid the `managing-*` skills ([#44](https://github.com/opsmill/infrahub-skills/pull/44)). ## Skills included[​](#skills-included "Direct link to Skills included") * infrahub-managing-schemas * infrahub-managing-objects * infrahub-managing-checks * infrahub-managing-generators * infrahub-managing-transforms * infrahub-managing-menus * infrahub-auditing-repo * infrahub-analyzing-data --- # Release 1.2.4 | Release Number | 1.2.4 | | -------------- | ------------------------------------------------------------------------ | | Release Type | Improvement | | Release Date | May 11th, 2026 | | Tag | [v1.2.4](https://github.com/opsmill/infrahub-skills/releases/tag/v1.2.4) | ## Release summary[​](#release-summary "Direct link to Release summary") Ask analyzing-data a question and get an answer to that question. When the question was omitted, it had answered a built-in example instead. ## Bug fixes[​](#bug-fixes "Direct link to Bug fixes") * [analyzing-data](/skills/skills-reference/analyzing-data.md) now requires the question as `args`. It runs in a forked context with no view of the parent conversation; without `args` it had used the example question built into the skill. It now stops with a re-invoke instruction instead of answering a different question. ## Full changelog[​](#full-changelog "Direct link to Full changelog") ### Fixed[​](#fixed "Direct link to Fixed") * Require the question as `args` for analyzing-data ([#45](https://github.com/opsmill/infrahub-skills/pull/45)). ## Skills included[​](#skills-included "Direct link to Skills included") * infrahub-managing-schemas * infrahub-managing-objects * infrahub-managing-checks * infrahub-managing-generators * infrahub-managing-transforms * infrahub-managing-menus * infrahub-auditing-repo * infrahub-analyzing-data --- # Release 1.2.5 | Release Number | 1.2.5 | | -------------- | ------------------------------------------------------------------------ | | Release Type | Feature | | Release Date | May 26th, 2026 | | Tag | [v1.2.5](https://github.com/opsmill/infrahub-skills/releases/tag/v1.2.5) | ## Release summary[​](#release-summary "Direct link to Release summary") You can now report Infrahub bugs and feature requests without first working out which repository owns them or manually redacting your terminal output. ### Report bugs and feature requests across the Infrahub ecosystem[​](#report-bugs-and-feature-requests-across-the-infrahub-ecosystem "Direct link to Report bugs and feature requests across the Infrahub ecosystem") Report an issue from your AI assistant with the reporting-issues skill, and it is routed to the right repository, filled into that repository's template, and cleared of sensitive data before submission. * Route a report to the correct repository in the Infrahub ecosystem, defaulting to `opsmill/infrahub` when the target is unclear. * Run a deterministic sanitization pass that replaces IPs, internal hostnames, URLs, and filesystem paths, and stops to ask you when it detects a token or database connection string. * Stop at a review step instead of submitting automatically — choose `gh`, the MCP server, or manual submission. ## Minor changes[​](#minor-changes "Direct link to Minor changes") **Documentation** * Infrahub schema string-length limits documented in the schema skill ([#48](https://github.com/opsmill/infrahub-skills/pull/48)). ## Full changelog[​](#full-changelog "Direct link to Full changelog") ### Added[​](#added "Direct link to Added") * `infrahub-reporting-issues` skill ([#47](https://github.com/opsmill/infrahub-skills/pull/47)). ### Changed[​](#changed "Direct link to Changed") * Dropped the stale `CHANGELOG.md`; release-drafter now owns release notes ([#50](https://github.com/opsmill/infrahub-skills/pull/50)). ### Fixed[​](#fixed "Direct link to Fixed") * Typo in the skills update instructions ([#51](https://github.com/opsmill/infrahub-skills/pull/51)). ## Skills included[​](#skills-included "Direct link to Skills included") * infrahub-managing-schemas * infrahub-managing-objects * infrahub-managing-checks * infrahub-managing-generators * infrahub-managing-transforms * infrahub-managing-menus * infrahub-auditing-repo * infrahub-analyzing-data * infrahub-reporting-issues --- # Release 1.2.6 | Release Number | 1.2.6 | | -------------- | ------------------------------------------------------------------------ | | Release Type | Feature | | Release Date | June 15th, 2026 | | Tag | [v1.2.6](https://github.com/opsmill/infrahub-skills/releases/tag/v1.2.6) | ## Release summary[​](#release-summary "Direct link to Release summary") After upgrading, data changes target a dedicated branch by default, you can model file attachments with `CoreFileObject`, and you can collect a redacted diagnostic bundle to share when you escalate an issue to OpsMill. ### Keep data changes on a branch you can review or roll back[​](#keep-data-changes-on-a-branch-you-can-review-or-roll-back "Direct link to Keep data changes on a branch you can review or roll back") Data changes now target a dedicated branch by default instead of the default branch, so you can review them through a proposed change or discard the branch instead of reverting each object manually. * Direct every write path — `infrahubctl object load`, the MCP `infrahub_create` / `infrahub_update` / `infrahub_delete` tools, `infrahubctl generator run`, and the Python SDK — to a dedicated branch instead of the default branch (`main` by convention, configurable per deployment). * A shared rule in `infrahub-common` covers when a direct write to the default branch is and is not appropriate, so the assistant defaults to a branch even when a request asks to write directly. ### Model file attachments with `CoreFileObject`[​](#model-file-attachments-with-corefileobject "Direct link to model-file-attachments-with-corefileobject") Store documents and binary files — diagrams, certificates, contracts, rack photos — on Infrahub nodes with the managing-schemas skill, using the built-in `CoreFileObject` generic instead of a text attribute that holds a URL or path. * Make a node a file-bearing entity by inheriting `CoreFileObject`; Infrahub adds a `file:` argument to the node's create, update, and upsert mutations so a file can be uploaded against it. * An audit rule flags reserved-attribute collisions (`file_name`, `file_size`, `file_type`, `checksum`, `storage_id`) and the antipattern of storing a file as a URL or path in a `kind: Text` attribute. ### Collect a redacted diagnostic bundle to share with OpsMill[​](#collect-a-redacted-diagnostic-bundle-to-share-with-opsmill "Direct link to Collect a redacted diagnostic bundle to share with OpsMill") Assemble a redacted, manifest-described diagnostic bundle from a local deployment with the infrahub-collecting-diagnostics skill when you escalate an issue to OpsMill. * Collect from Docker Compose or Kubernetes deployments, including multi-replica setups where one replica's missing logs can hide a bug, and read instance state through `infrahubctl` only. * Apply tiered redaction before producing the bundle, and describe its contents in a `manifest.yml`. ## Bug fixes[​](#bug-fixes "Direct link to Bug fixes") * Fixed integration-layer gaps found in a live build: union-typed GraphQL queries missing inline fragments, relationship references passed as bare strings or mis-packed lists, natural-key collisions on create, and artifact regeneration treated as synchronous (managing-generators, managing-transforms). * Corrected owner and source metadata semantics: source records lineage only, while `owner` together with `is_protected` controls write access. * Verified content-type allowlists, `extensions:` scope, `__typename` resolution, and the Jinja2 filter sandbox against Infrahub source or a running server. ## Minor changes[​](#minor-changes "Direct link to Minor changes") **Documentation** * Resources testing-framework documentation added to the check and transform skills. **Developer experience** * "Information Priority" guidance moved to a shared rule inherited by every skill. ## Full changelog[​](#full-changelog "Direct link to Full changelog") ### Added[​](#added "Direct link to Added") * `infrahub-collecting-diagnostics` skill ([#54](https://github.com/opsmill/infrahub-skills/pull/54)). * `CoreFileObject` schema and audit rules ([#55](https://github.com/opsmill/infrahub-skills/pull/55)). ### Changed[​](#changed "Direct link to Changed") * Branch-default across object loads, MCP CRUD, generators, and the SDK ([#56](https://github.com/opsmill/infrahub-skills/pull/56)). * "Information Priority" moved to a shared rule ([#57](https://github.com/opsmill/infrahub-skills/pull/57)). ### Fixed[​](#fixed "Direct link to Fixed") * Integration-layer fixes from a live build ([#49](https://github.com/opsmill/infrahub-skills/pull/49), [#53](https://github.com/opsmill/infrahub-skills/pull/53)). * Owner and source metadata semantics corrected ([#27](https://github.com/opsmill/infrahub-skills/pull/27)). * Testing-framework documentation added to the check and transform skills ([#18](https://github.com/opsmill/infrahub-skills/pull/18)). ## Skills included[​](#skills-included "Direct link to Skills included") * infrahub-managing-schemas * infrahub-managing-objects * infrahub-managing-checks * infrahub-managing-generators * infrahub-managing-transforms * infrahub-managing-menus * infrahub-auditing-repo * infrahub-analyzing-data * infrahub-reporting-issues * infrahub-collecting-diagnostics --- # Release 1.2.7 | Release Number | 1.2.7 | | -------------- | ------------------------------------------------------------------------ | | Release Type | Improvement | | Release Date | June 16th, 2026 | | Tag | [v1.2.7](https://github.com/opsmill/infrahub-skills/releases/tag/v1.2.7) | ## Release summary[​](#release-summary "Direct link to Release summary") When you build a generator that works through the results of a query, you can now read each related object from the response it already returned instead of sending a separate request for each one. For generators that handle many objects, that means far fewer requests. ### Build generators that reuse query results instead of requesting each object[​](#build-generators-that-reuse-query-results-instead-of-requesting-each-object "Direct link to Build generators that reuse query results instead of requesting each object") Build a generator with the managing-generators skill so that, when its query already returns the related objects it needs, the generated code builds them from that response instead of fetching each one again. * Create related objects from the data already in the query response with `from_graphql`, instead of calling `client.get` once per object. * Follow the query-coverage requirement so the response carries everything those objects need: `__typename`, `id`, and every attribute the generator changes. * Use the before/after example in [managing-generators](/skills/skills-reference/managing-generators.md) to refactor an existing generator. ## Full changelog[​](#full-changelog "Direct link to Full changelog") ### Added[​](#added "Direct link to Added") * `from_graphql` rule for managing-generators: build related objects from the query response instead of requesting them again ([#58](https://github.com/opsmill/infrahub-skills/pull/58)). ## Skills included[​](#skills-included "Direct link to Skills included") * infrahub-managing-schemas * infrahub-managing-objects * infrahub-managing-checks * infrahub-managing-generators * infrahub-managing-transforms * infrahub-managing-menus * infrahub-auditing-repo * infrahub-analyzing-data * infrahub-reporting-issues * infrahub-collecting-diagnostics --- # Data Analyzer The Data Analyzer skill queries and analyzes data from a live Infrahub instance using the Infrahub MCP server. It answers operational questions that span multiple node types — correlating data, detecting drift, tracing service impact, auditing data quality — without requiring the user to write GraphQL queries manually. ## Requirements[​](#requirements "Direct link to Requirements") The Data Analyzer skill requires the **Infrahub MCP server** to be configured and connected to your AI tool. See the [MCP server docs](https://docs.infrahub.app/mcp) for setup instructions. tip Without MCP, the AI can still query Infrahub data by constructing GraphQL API calls directly. The interaction is more verbose and less intuitive, but functional. ## When to use[​](#when-to-use "Direct link to When to use") * Answering ad-hoc operational questions about live infrastructure data * Cross-referencing two or more node types to find relationships or gaps * Investigating the blast radius of a change before executing it * Auditing data quality (missing fields, stale records, naming violations) * Exploring schema structure and data before writing a generator or check * Producing one-time or on-demand reports ## Types of analysis[​](#types-of-analysis "Direct link to Types of analysis") | Analysis type | Example question | | ----------------------- | ---------------------------------------------------------------------------------- | | **Compliance** | "Are all devices following the naming convention `--`?" | | **Service impact** | "Which services are hosted on devices in Rack A-03?" | | **Maintenance windows** | "Which devices are in a maintenance window, and what BGP sessions depend on them?" | | **Drift detection** | "Which realized devices differ from their topology design object?" | | **Capacity** | "Which racks are over 80% utilized?" | | **Inventory gaps** | "Which devices have no platform or OS version recorded?" | | **Change impact** | "What BGP sessions, services, and IP allocations depend on this prefix?" | ## How it works[​](#how-it-works "Direct link to How it works") The skill uses MCP tools (`infrahub_query`, `infrahub_list_schema`, `infrahub_get`) to fetch data from the running instance. For multi-step analysis, queries run sequentially or in combination — data is joined in the AI's context, not in the database. The skill reports findings with counts, lists of affected objects, and suggested next steps. ## Data Analyzer vs. Check Manager vs. Transform Manager[​](#data-analyzer-vs-check-manager-vs-transform-manager "Direct link to Data Analyzer vs. Check Manager vs. Transform Manager") | Use Data Analyzer when... | Use Check Manager when... | Use Transform Manager when... | | --------------------------------- | ------------------------------------ | ------------------------------------------------- | | Answering a one-time question | Enforcing a rule in the pipeline | Producing a repeatable scheduled report | | Interactive, exploratory analysis | Blocking changes that violate policy | Generating artifacts (configs, exports) | | No pipeline integration needed | Automated enforcement required | Output needs to be stored as a versioned artifact | --- # Repo Auditor The Repo Auditor performs a comprehensive audit of an Infrahub repository against all Infrahub best practices. It checks schema files, object files, Python components (checks, generators, transforms), `.infrahub.yml` registration, and project structure — then generates a report identifying issues and explaining what to fix and why. ## When to use[​](#when-to-use "Direct link to When to use") * Before loading a new schema into a production instance * After onboarding to verify the repository follows Infrahub conventions * When inheriting or taking over an existing Infrahub implementation * As a periodic quality check on an active repository * Before opening a pull request that touches schema or automation files ## What it checks[​](#what-it-checks "Direct link to What it checks") ### Project structure[​](#project-structure "Direct link to Project structure") * Required files present (`.infrahub.yml`, schema files) * Directory conventions followed * File naming patterns (numeric prefixes on object files, snake\_case on Python files) ### Schema[​](#schema "Direct link to Schema") * Naming conventions (namespace, node name, attribute name lengths and casing) * Attribute types (no deprecated `String`, correct use of `Dropdown`, `Text`, `Number`, etc.) * Relationship identifiers (both sides match) * Relationship peer references (full namespace+name) * `human_friendly_id` present on all nodes * `display_label` present and syntactically correct * Hierarchy configuration (correct `parent`/`children` setup for hierarchical generics) ### Object files[​](#object-files "Direct link to Object files") * Correct `apiVersion`, `kind: Object`, `spec` structure * Value mapping (correct use of `value:`, dropdown names vs. labels) * Load order (no forward references) * Relationship references match existing node `human_friendly_id` values ### Python components[​](#python-components "Direct link to Python components") * **`InfrahubCheck`** — `validate()` signature, `log_error()` vs `log_info()` usage, error collection pattern * **`InfrahubGenerator`** — `allow_upsert=True`, `delete_unused_nodes=True`, async `generate()` method * **`InfrahubTransform`** — correct return types, `data` variable usage in Jinja2 templates ### Cross-references and registration[​](#cross-references-and-registration "Direct link to Cross-references and registration") * Every Python component referenced in `.infrahub.yml` exists as a file * Every `.gql` query referenced in Python classes exists as a file * Query names match between `.gql` files and Python class attributes * Artifact definitions reference existing transforms ### Best practices[​](#best-practices "Direct link to Best practices") * `display_label` caching patterns for nodes with parent relationships * Python environment detection configuration * Git integration setup ### YAGNI / cost-to-fix[​](#yagni--cost-to-fix "Direct link to YAGNI / cost-to-fix") Beyond correctness, the auditor flags places where Python (or denormalized data) is doing work a cheaper, built-in Infrahub layer already handles — for example: * A check that re-implements a uniqueness, presence, or allowed-values rule the schema can enforce as a constraint * A Python transform that only formats strings, where a Jinja2 template would do * A generator that hardcodes data belonging in `objects/`, reinvents a built-in IPAM/VLAN primitive, or hand-rolls subnet/IP/VLAN/port allocation instead of using a resource pool These findings are advisory — the code works as-is, but the cheaper layer is less to maintain and visible to the rest of the platform. Each one names the specific layer to move to, and they are ranked by how cheap the fix is (a one-line schema change ranks above a full rewrite). ## What it produces[​](#what-it-produces "Direct link to What it produces") A structured audit report listing: * **Issue** — what was found * **Severity** — error, warning, or info * **Location** — file and line where possible * **Recommended fix** — what to change and why Issues are grouped by category so the most critical problems are addressed first. ## Acting on the results[​](#acting-on-the-results "Direct link to Acting on the results") | Severity | Meaning | Action | | ----------- | ------------------------------------------------------ | ------------------------------------- | | **Error** | Will cause load failures or runtime errors | Must fix before loading into Infrahub | | **Warning** | Convention violation that may cause confusing behavior | Should fix | | **Info** | Suggestion for improvement | Optional | Cost-to-fix (YAGNI) findings are always advisory — they never block a load. They are listed cheapest-fix-first so you can pick off the easy wins, and each carries the specific replacement to apply. The Repo Auditor can be asked to fix issues directly after reporting them — describe which issues to address and the AI applies the relevant skill to make the corrections. --- # Data Importer The Data Importer turns CSV or TSV inputs into Infrahub object YAML and loads it onto a fresh branch. It introspects the live schema, maps columns to attributes by heuristic plus an up-front interview, splits denormalized inputs across the right kinds with the correct load order, and fails closed when a column has no schema home — without ever proposing a schema edit. ## When to use[​](#when-to-use "Direct link to When to use") * Importing infrastructure data from a CSV or TSV export (NetBox, vendor inventories, spreadsheets) * Loading a denormalized one-big-sheet export and splitting it across the right Infrahub kinds * Bulk-loading initial data into a new Infrahub instance from a folder of CSVs * Re-importing edits to existing rows by HFID (upsert) * Importing data with lineage tagging via `source` / `owner` / `is_protected` ## What it produces[​](#what-it-produces "Direct link to What it produces") * Numbered object YAML files (`01_manufacturers.yml`, `02_sites.yml`, `10_devices.yml`, …) that conform to the Object Manager envelope (`apiVersion: infrahub.app/v1`, `kind: Object`, `spec.kind`, `spec.data`) * A fresh import branch created via `infrahubctl branch create` before any validate or load * A confirmed mapping plan from the up-front interview, locked before the first file is written * Local self-check against Object Manager rules, followed by server-side `infrahubctl object validate`, followed by `object load` — all scoped to the import branch * Optional `source` / `owner` / `is_protected` metadata stamping when the user opts in ## Example prompts[​](#example-prompts "Direct link to Example prompts") * "Import `devices.csv` into Infrahub on a fresh branch" * "Convert this denormalized CSV with manufacturer, location, and device columns into separate object files in the right load order" * "Load these three CSVs (manufacturers, sites, devices) and resolve the references between them" * "Import an interface CSV with `eth0..eth47` ranges and collapse them with `expand_range: true`" * "Import `assets.csv` and stamp every value with `source: csv-import-20260622`" ## Key rules enforced[​](#key-rules-enforced "Direct link to Key rules enforced") * **Schema is read-only** — the skill never proposes attribute additions, dropdown choice additions, or any schema edit; an unmappable column triggers a fail-closed report and routes to the Schema Manager * **Up-front interview** — every ambiguity (unmapped dropdown cells, denormalization splits, branch name, lineage opt-in) is batched into one question round before any file is written * **Branch-first** — runs `infrahubctl branch create ` before `object validate` or `object load`; the import never lands on the default branch * **Local self-check before server validate** — the emission is walked against the Object Manager rules locally first, so shape errors don't cost a branch that has to be discarded * **Dropdown label to choice name** — UI-facing labels in the CSV (e.g., `Active`) are translated to the schema's choice name (e.g., `active`) using a label-to-name lookup * **Reference shape from HFID** — relationship references emit as a scalar or a positional list based on the target node's `human_friendly_id` length, read directly from the schema * **Range collapse for interfaces** — contiguous sequences like `eth0..eth47` collapse to `eth[0-47]` with `parameters.expand_range: true` when all sibling columns match across the range * **Numbered load order** — files use `NN_` prefixes so referents load before referrers, and dependent kinds are emitted to higher-numbered files ## Common mistakes it catches[​](#common-mistakes-it-catches "Direct link to Common mistakes it catches") | Mistake | What the skill does instead | | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ | | Dropping unmapped columns silently | Fails closed with a structured report of unmappable columns and the kinds checked | | Emitting dropdown labels instead of choice names | Builds a label→name table from the schema and emits the name | | Wrong reference shape (scalar vs list) | Reads the target's `human_friendly_id` length and emits the matching shape | | Repeated parent rows treated as duplicates | Detects the denormalization and asks whether to nest as component children or split into separate kinds | | Emitting interface rows as 48 literal entries | Detects contiguous sequences with identical sibling columns and emits a single range row plus `expand_range: true` | | Loading to the default branch | Creates a dedicated import branch and validates + loads there | | Trying to resume a partial load | Discards the branch and re-runs with a fresh branch name (`object load` is not transactional across files) | ## Validating and loading[​](#validating-and-loading "Direct link to Validating and loading") The skill produces these commands as part of the workflow. Run them in order on the import branch: ``` # Create the dedicated import branch (after the local self-check passes) infrahubctl branch create csv-import-20260622-1430 # Validate the emission against the branch infrahubctl object validate ./output_dir/ --branch csv-import-20260622-1430 # Load on success infrahubctl object load ./output_dir/ --branch csv-import-20260622-1430 # If anything fails partway, discard the branch and re-run with a fresh name infrahubctl branch delete csv-import-20260622-1430 ``` warning The skill never edits the schema. If a CSV column has no attribute or relationship to map to, the skill stops with a list of the unmappable columns and the kinds it checked. Resolve the gap with the [Schema Manager](/skills/skills-reference/managing-schemas.md), then re-run. tip For Excel inputs, export each sheet to CSV first — the skill covers CSV and TSV only. For LDJSON dumps from `infrahubctl export dump`, use `infrahubctl import load` instead; that's a different format and a different tool. --- # Check Manager The Check Manager produces Python validation checks that run in Infrahub's proposed change pipeline. Each check consists of three components: a GraphQL query file, a Python class inheriting from `InfrahubCheck`, and a `.infrahub.yml` registration entry. The skill generates all three and ensures they are correctly wired together. ## When to use[​](#when-to-use "Direct link to When to use") * Enforcing naming conventions on devices, interfaces, or other nodes when a change is proposed * Validating that required relationships are populated (e.g., every device has a primary IP) * Blocking changes that would violate design intent (e.g., a BGP session referencing a decommissioned device) * Any validation that should block a proposed change from merging ## What it produces[​](#what-it-produces "Direct link to What it produces") Three files that must be used together: 1. **GraphQL query file** (`.gql`) — fetches the data the check needs. Named to match the `query` attribute in the Python class. 2. **Python class** (`InfrahubCheck` subclass) — implements the `validate()` method. Uses `self.log_error()` to flag violations (which blocks the change) and `self.log_info()` for informational messages (which does not block). 3. **`.infrahub.yml` entry** — registers the check under `check_definitions` with the class name, query name, and any parameters. ## Example prompts[​](#example-prompts "Direct link to Example prompts") * "Create a check that validates every device has a primary IP assigned" * "Write a check that enforces the naming convention `--` for all device names" * "Create a check that blocks any proposed change that removes a BGP session from an active device" ## Key rules enforced[​](#key-rules-enforced "Direct link to Key rules enforced") * **Three-component structure** — all three files are always generated together; missing any one causes the check to fail at runtime * **`log_error()` vs `log_info()`** — `log_error()` blocks the proposed change; `log_info()` does not. The skill uses the correct one based on the intent described * **GraphQL query naming** — the query name in the `.gql` file must match the `query` class attribute exactly * **Parameter mapping** — if the check accepts parameters (e.g., to parameterize a naming pattern), the parameters are declared in both the Python class and the `.infrahub.yml` registration * **Error collection before logging** — errors are collected across all objects first, then logged together, rather than short-circuiting on the first error ## Common mistakes it catches[​](#common-mistakes-it-catches "Direct link to Common mistakes it catches") | Mistake | What the skill does instead | | --------------------------------------------------- | ---------------------------------------------------- | | Using `log_error()` when `log_info()` was intended | Matches the log method to the described intent | | Query name mismatch between `.gql` and Python class | Uses the same name in both | | Missing `.infrahub.yml` registration | Generates the registration entry alongside the check | | Short-circuiting on first error | Collects all errors before logging | ## Running checks[​](#running-checks "Direct link to Running checks") Checks run automatically in the proposed change pipeline when a change is submitted to Infrahub. To test a check locally: ``` infrahubctl check run ``` --- # Generator Manager The Generator Manager produces Python generators that query Infrahub for design objects and automatically create or update the infrastructure objects derived from them. Generators are idempotent: re-running them updates existing objects rather than creating duplicates. The skill generates the Python class, GraphQL query, and `.infrahub.yml` registration. ## When to use[​](#when-to-use "Direct link to When to use") * Auto-creating devices, interfaces, and IP addresses from a fabric design object * Generating BGP sessions from a topology design * Creating any set of objects that should be derived from a design definition and kept in sync with it * Any scenario where "given this design, create these objects" needs to run repeatedly as the design evolves ## What it produces[​](#what-it-produces "Direct link to What it produces") Three components: 1. **Target group** — a `CoreGeneratorGroup` that defines which design objects trigger the generator 2. **GraphQL query** (`.gql`) — fetches the design object(s) the generator will act on 3. **Python class** (`InfrahubGenerator` subclass) — implements the `async generate()` method, uses `self.client.create()` with `allow_upsert=True` ## Example prompts[​](#example-prompts "Direct link to Example prompts") * "Create a generator that takes a fabric design object and creates all spine and leaf devices with their loopback interfaces" * "Write a generator that creates BGP sessions between all spine-leaf pairs defined in a topology design" * "Generate IP address assignments for all interfaces based on a design's IP allocation scheme" ## Key rules enforced[​](#key-rules-enforced "Direct link to Key rules enforced") * **`allow_upsert=True`** — always set on `self.client.create()` calls; without it, re-running the generator creates duplicates instead of updating existing objects * **`delete_unused_nodes=True`** — enables automatic cleanup of objects that were created by the generator but are no longer in the design; prevents orphaned objects from accumulating over time * **Target group** — generator must reference a `CoreGeneratorGroup`; this is what connects design objects to the generator execution * **Async `generate()` method** — the method signature must be `async def generate(self, data):` * **GraphQL query naming** — query name must match the `query` class attribute ## Common mistakes it catches[​](#common-mistakes-it-catches "Direct link to Common mistakes it catches") | Mistake | What the skill does instead | | ---------------------------------- | ---------------------------------------------------------- | | Missing `allow_upsert=True` | Always includes it — prevents duplicate creation on re-run | | Missing `delete_unused_nodes=True` | Always includes it — prevents orphaned objects | | Sync `generate()` method | Uses `async def generate(self, data):` | | Missing target group configuration | Generates the `CoreGeneratorGroup` definition | ## Running generators[​](#running-generators "Direct link to Running generators") Generators run automatically when a design object in their target group changes. To run a generator manually: ``` infrahubctl generator run ``` --- # Menu Manager The Menu Manager produces YAML menu definition files that customize the Infrahub web UI sidebar. Menus can be flat or deeply nested, with group headers, icons, and ordering. The skill generates syntactically correct menu YAML and handles the nesting structure that is easy to get wrong by hand. ## When to use[​](#when-to-use "Direct link to When to use") * Replacing Infrahub's auto-generated sidebar navigation with a custom structure * Grouping schema nodes into logical sections (e.g., "IP Management", "Network Topology", "Inventory") * Adding nested sub-menus and section headers * Controlling the ordering of menu items ## What it produces[​](#what-it-produces "Direct link to What it produces") * YAML file with `apiVersion`, `kind: Menu`, and a `spec.data` block containing the menu tree * Loaded into Infrahub alongside schema and object files ## Example prompts[​](#example-prompts "Direct link to Example prompts") * "Create a menu with sections for Network Topology, IP Management, and Inventory — each with relevant schema nodes nested underneath" * "Build a flat menu that lists Device, Interface, Site, and Rack in that order" * "Add a nested sub-menu under Network Topology for BGP with entries for BGP Sessions and BGP Peers" ## Key rules enforced[​](#key-rules-enforced "Direct link to Key rules enforced") * **`spec.data` wrapping** — the top-level menu items must be in `spec.data`; a common mistake is writing `spec:` followed by a list directly * **`children.data` wrapping** — nested items must use `children.data:` not `children:` directly; this is the most common nesting mistake * **`kind` vs `path`** — each item uses either `kind:` (links to a schema node's list view) or `path:` (links to a custom URL), never both * **Group headers** — section headers with no link (no `kind` or `path`) are valid and used for visual grouping * **Icon format** — icons use the Material Design Icons format with `mdi:` prefix (e.g., `mdi:server`, `mdi:ip-network`) * **`order_weight`** — integer controlling item display order; lower numbers appear first ## Loading[​](#loading "Direct link to Loading") Load a menu file into a running Infrahub instance: ``` infrahubctl menu load ``` --- # Object Manager The Object Manager produces YAML data files for Infrahub infrastructure objects — devices, locations, organizations, interfaces, modules, and any custom node type. It applies the correct file structure, value mappings, and load order so files are ready to load into a running Infrahub instance. ## When to use[​](#when-to-use "Direct link to When to use") * Creating initial data files for a new Infrahub instance * Populating objects from a spreadsheet, CSV, or description * Creating a batch of devices, interfaces, or IP addresses with consistent structure * Generating test or demo data for a schema * Adding new infrastructure objects to an existing instance ## What it produces[​](#what-it-produces "Direct link to What it produces") * YAML files with `apiVersion`, `kind: Object`, and a `spec` block * Each file covers one or more objects of the same node type * Supports multi-document YAML files (multiple objects in one file separated by `---`) * Supports `expand_range: true` for sequential interface/object generation (e.g., `eth0` through `eth47`) * Output respects load order: schemas must be loaded before objects ## Example prompts[​](#example-prompts "Direct link to Example prompts") * "Create object files for 3 spine switches and 6 leaf switches with loopback interfaces" * "Populate 5 locations: 2 regions, 2 sites within each region, and a rack in each site" * "Generate 48 interfaces on a device named `spine-01` using expand\_range" * "Create organization objects for Cisco, Juniper, and Arista as manufacturers" ## Key rules enforced[​](#key-rules-enforced "Direct link to Key rules enforced") * **File structure** — `apiVersion`, `kind: Object`, `spec` with `nodes` list; missing any of these causes load failure * **Value mapping** — Text values wrapped in `value:` key; Dropdown fields use the choice name not the label; relationship references use the node's `human_friendly_id` value * **Children and hierarchy** — child objects (e.g., interfaces on a device) use the correct nesting structure with `kind:` specified * **Load order** — objects referencing other objects (e.g., interfaces referencing a device) must be in the right file or the right order within a multi-document file * **Numeric file prefixes** — files are named with numeric prefixes (e.g., `01_organizations.yml`, `02_locations.yml`) to enforce load order ## Common mistakes it catches[​](#common-mistakes-it-catches "Direct link to Common mistakes it catches") | Mistake | What the skill does instead | | ------------------------------------------------------------ | -------------------------------------------------------- | | Missing `value:` wrapper on text attributes | Wraps all text values in `value:` | | Using Dropdown label instead of name | Uses the choice name (e.g., `"active"` not `"Active"`) | | Relationship references that don't match `human_friendly_id` | Uses the correct identifier value | | Incorrect nesting for child objects | Applies the correct `children` structure with `kind:` | | Load order violations | Orders files and documents so dependencies are satisfied | ## Loading[​](#loading "Direct link to Loading") Load object files into a running Infrahub instance: ``` infrahubctl object load ``` warning Load schemas before objects. Objects that reference node types not yet in the schema will fail to load. --- # Schema Manager The Schema Manager produces valid Infrahub schema YAML from natural language descriptions. It applies Infrahub's naming conventions, relationship rules, attribute type requirements, and display property patterns automatically — without requiring manual study of the schema format. ## When to use[​](#when-to-use "Direct link to When to use") * Creating a new schema node from a description * Adding attributes or relationships to an existing node * Designing a generic (abstract base type) with inheriting nodes * Creating hierarchical node types (location trees, organizational hierarchies) * Modifying an existing schema to add new fields or fix convention violations * Validating a schema against best practices before loading ## What it produces[​](#what-it-produces "Direct link to What it produces") * Schema YAML file starting with `version: "1.0"` containing `nodes:` and/or `generics:` * Each node includes: namespace, name, attributes (with kinds and defaults), relationships (with identifiers, cardinality, peer references), `human_friendly_id`, `display_label`, and `order_weight` * Output is ready to validate with `infrahubctl schema check` and load with `infrahubctl schema load` ## Example prompts[​](#example-prompts "Direct link to Example prompts") * "Create a schema for VLAN management with an id attribute, a name, a status dropdown, and a role field" * "Add a `contract_start_date` attribute to `InfraCircuit`" * "Design a location hierarchy with Region, Site, and Rack as hierarchical generics" * "Create a Device generic with Platform and Role as dropdowns, and model specific device types (Router, Switch, Firewall) inheriting from it" ## Key rules enforced[​](#key-rules-enforced "Direct link to Key rules enforced") * **Naming conventions** — namespace is PascalCase and meaningful (e.g., `Infra`, `Ipam`, `Dcim`); node names are PascalCase; attribute names are snake\_case with minimum 3 characters * **Attribute types** — uses `Text` (not deprecated `String`); `Dropdown` for status/role fields with choices defined; `Number`, `Boolean`, `DateTime` as appropriate * **Relationship identifiers** — both sides of every relationship share the same identifier string; mismatched identifiers are the most common source of schema errors * **Relationship peer references** — uses full namespace+name (e.g., `IpamVlanGroup`) not short names * **human\_friendly\_id** — included for every node so objects are identifiable in the UI and through the API * **display\_label** — Jinja2 template set for every node for dropdown and reference rendering * **order\_weight** — integer set on attributes to control display order in the UI * **Hierarchy setup** — for hierarchical generics: `hierarchical: true`, `parent` and `children` relationships configured correctly ## Common mistakes it catches[​](#common-mistakes-it-catches "Direct link to Common mistakes it catches") | Mistake | What the skill does instead | | ----------------------------------------- | ---------------------------------------------------------------- | | Using `String` instead of `Text` | Uses `Text` — `String` is deprecated | | Attribute names shorter than 3 characters | Uses descriptive names (e.g., `vlan_id` not `id`) | | `display_labels` (plural) | Uses `display_label` (singular) | | Mismatched relationship identifiers | Sets the same identifier on both sides | | Short kind references in peers | Uses full namespace+name (e.g., `IpamVlanGroup` not `VlanGroup`) | | Missing `human_friendly_id` | Includes it on every node | ## Validating and loading[​](#validating-and-loading "Direct link to Validating and loading") Validate a generated schema before loading it into Infrahub: ``` infrahubctl schema check ``` Load the schema into a running instance: ``` infrahubctl schema load ``` tip Schema changes on an existing instance may require a migration. See the [schema topic guide](https://docs.infrahub.app/topics/schema) for details on how Infrahub handles schema evolution. --- # Transform Manager The Transform Manager produces data transforms that convert Infrahub data into other formats — device configurations, JSON exports, CSV reports, and more. Transforms can be implemented as Python classes, Jinja2 templates, or a hybrid of both. The skill generates the transform code, GraphQL query, optional Jinja2 templates, and `.infrahub.yml` registration. ## When to use[​](#when-to-use "Direct link to When to use") * Generating device configuration files from Infrahub data * Exporting infrastructure data to JSON, CSV, or other formats * Producing human-readable reports from graph data * Any scenario where Infrahub data needs to be rendered into a different representation ## Transform types[​](#transform-types "Direct link to Transform types") ### Python transform[​](#python-transform "Direct link to Python transform") Use when the output requires logic: conditional rendering, calculations, data reshaping. * Implements `InfrahubTransform` class with a `transform()` method * Returns a `dict` for JSON output or a `str` for text output ### Jinja2 transform[​](#jinja2-transform "Direct link to Jinja2 transform") Use when the output is primarily a template with variable substitution: device configs, structured text files. * Template file receives the GraphQL query response in a `data` variable * Supports `netutils` filters for network-specific operations (IP address manipulation, interface normalization) ### Hybrid (Python + Jinja2)[​](#hybrid-python--jinja2 "Direct link to Hybrid (Python + Jinja2)") Use when data needs preprocessing before rendering. * Python class prepares and reshapes the data, Jinja2 template handles rendering * Supports platform-specific templates (e.g., different configs per vendor) using `FileSystemLoader` ## What it produces[​](#what-it-produces "Direct link to What it produces") * GraphQL query file (`.gql`) — fetches the data the transform needs * Python transform class and/or Jinja2 template file(s) * `.infrahub.yml` entry under `python_transforms` or `jinja2_transforms` * `artifact_definitions` entry if the transform produces versioned artifacts ## Example prompts[​](#example-prompts "Direct link to Example prompts") * "Create a Jinja2 transform that produces Cisco IOS configuration for each device, pulling interface and BGP data from Infrahub" * "Write a Python transform that exports all IP addresses to a CSV file" * "Build a hybrid transform that generates vendor-specific configs — Cisco IOS for routers, Juniper JunOS for switches" ## Key rules enforced[​](#key-rules-enforced "Direct link to Key rules enforced") * **Return type** — Python transform must return `dict` for JSON output or `str` for text; wrong return type causes rendering failure * **`data` variable** — Jinja2 templates always receive the GraphQL response as `data`; accessing the wrong key is a common mistake * **Artifact definitions** — if the transform output should be stored as a versioned artifact in Infrahub, an `artifact_definitions` entry is required in `.infrahub.yml`; the skill generates this when appropriate * **`netutils` availability** — `netutils` filters are available in Jinja2 templates; the skill uses them where appropriate for IP and interface operations ## Running transforms[​](#running-transforms "Direct link to Running transforms") Transforms run as part of the artifact generation pipeline or on demand: ``` infrahubctl transform run ``` --- # Spec-Driven Development Spec-Driven Development (SDD) is a structured planning mode for complex or multi-part Infrahub builds. Instead of generating files immediately, the AI reasons through requirements with you first — capturing what needs to be built, validating the approach against Infrahub conventions, breaking the work into discrete tasks, and only generating once the plan is approved. The key benefit: structural mistakes — wrong relationship cardinality, missing `allow_upsert`, incorrect generic usage — are caught before any file is produced. ## When to use SDD vs. direct mode[​](#when-to-use-sdd-vs-direct-mode "Direct link to When to use SDD vs. direct mode") | Direct Mode | SDD | | --------------------------------------- | -------------------------------------------------------------------------- | | Adding an attribute to an existing node | Designing a new schema node with relationships | | Writing a single validation check | Building a generator chain | | Creating a menu section | Standing up a complete new domain (schema + objects + checks + generators) | | Populating a batch of objects | Refactoring relationships across multiple schema files | | Any well-scoped, single-skill task | Anything that involves design decisions or spans multiple skills | ## The SDD workflow[​](#the-sdd-workflow "Direct link to The SDD workflow") ### 1. Specify[​](#1-specify "Direct link to 1. Specify") Describe the feature or requirement in plain language. The AI captures requirements, asks clarifying questions about scope, Infrahub version, existing schema context, and dependencies. The goal is a complete picture of what needs to be built before any planning starts. ### 2. Plan[​](#2-plan "Direct link to 2. Plan") The AI produces an implementation plan. For each component to be built (schema nodes, generators, checks, transforms), it identifies which skill to use, what the inputs and outputs are, and what dependencies exist between steps. The plan is validated against Infrahub skill rules before it's presented. ### 3. Review[​](#3-review "Direct link to 3. Review") You review the plan. This is the key checkpoint — adjust the approach, correct assumptions, or request changes before any file is generated. The AI explains its reasoning for each decision so you can evaluate whether the approach is correct. ### 4. Implement[​](#4-implement "Direct link to 4. Implement") Once approved, the AI executes each task using the correct Infrahub skill. Tasks run sequentially where dependencies exist, or in parallel where they don't — the AI manages the dependency tree. ## Working with the plan[​](#working-with-the-plan "Direct link to Working with the plan") * **Interrogate** — ask why a specific decision was made, request alternatives, or ask what would change if a requirement changed * **Adjust** — describe what's wrong or what you want differently; the AI updates the plan before proceeding * **Approve** — explicitly confirm the plan is correct. The AI does not proceed without confirmation ## Sequential vs. parallel execution[​](#sequential-vs-parallel-execution "Direct link to Sequential vs. parallel execution") Simple builds execute sequentially: schema first, then objects, then checks. Complex builds with independent components can execute in parallel using sub-agents. The AI determines which tasks are independent based on the dependency tree it built during planning. ## Compatible SDD frameworks[​](#compatible-sdd-frameworks "Direct link to Compatible SDD frameworks") SDD works with any framework that supports a spec, plan, task, and implement workflow. The [infrahub-template](https://github.com/opsmill/infrahub-template) repository includes a pre-configured SDD setup using Spec Kit as a starting point. ## Example walkthrough: VLAN management domain[​](#example-walkthrough-vlan-management-domain "Direct link to Example walkthrough: VLAN management domain") This example uses [Spec Kit](https://github.com/github/spec-kit) to design a VLAN management domain from scratch — schema, object data, and a validation check. Any SDD framework that follows a spec, plan, task, and implement workflow produces a similar result. ### Setup[​](#setup "Direct link to Setup") ``` # Initialize Spec Kit in your Infrahub project specify init . --ai claude ``` This creates `.speckit/` config and installs slash commands into `.claude/commands/`. ### Step 1: Specify[​](#step-1-specify "Direct link to Step 1: Specify") Run `/speckit.specify` and describe the feature: > I need a VLAN management domain for our data center network. We need to track VLANs with their numeric ID, name, operational status, and role. VLANs should be grouped into VLAN Groups. We also need a validation check that ensures no two VLANs in the same group share the same VLAN ID. Spec Kit produces `spec.md` capturing the requirements — two schema nodes (VLAN, VLANGroup), a grouping relationship, a uniqueness check, and the attributes for each. ### Step 2: Plan[​](#step-2-plan "Direct link to Step 2: Plan") Run `/speckit.plan`. The AI reads `spec.md` and the Infrahub skills to produce `plan.md`: > **Plan Summary** > > 1. **Schema** (Schema Manager) — Define `IpamVLAN` and `IpamVLANGroup` nodes in the `Ipam` namespace. > > > > * `IpamVLAN`: attributes `name` (Text), `vlan_id` (Number), `status` (Dropdown), `role` (Text, optional). Relationship to `IpamVLANGroup` (many-to-one). > * `IpamVLANGroup`: attribute `name` (Text). Relationship back to `IpamVLAN` (one-to-many). > * Both nodes get `human_friendly_id` and `display_label`. > * Uniqueness constraint on VLAN: `[vlan_id__value, group]`. > > 2. **Object data** (Object Manager) — Seed two VLAN groups and sample VLANs. > > 3. **Validation check** (Check Manager) — Python check querying VLANs per group, flagging duplicates. Register in `.infrahub.yml`. > > **Dependencies:** Schema must be loaded before objects. Check depends on schema being present. The plan is validated against Infrahub skills — the AI catches that `status` should be `kind: Dropdown` (not Text), that `vlan_id` is used instead of `id` (attribute names must be 3+ characters), and that the relationship between VLAN and VLANGroup needs matching identifiers on both sides. ### Step 3: Review[​](#step-3-review "Direct link to Step 3: Review") You review `plan.md` and adjust: > Looks good, but add `description` as an optional Text attribute on both nodes. And use "provisioned" instead of "provisioning" for the status choice. The AI updates the plan. ### Step 4: Tasks[​](#step-4-tasks "Direct link to Step 4: Tasks") Run `/speckit.tasks`. The AI breaks the plan into discrete steps in `tasks.md`: > * > Task 1: Create `schemas/vlan_management.yml` with IpamVLAN and IpamVLANGroup nodes > * > Task 2: Create `data/vlan_groups.yml` with two seed groups > * > Task 3: Create `data/vlans.yml` with sample VLANs assigned to groups > * > Task 4: Create `checks/check_vlan_id_uniqueness/` with Python check class and GraphQL query > * > Task 5: Register the check in `.infrahub.yml` > * > Task 6: Validate schema with `infrahubctl schema check` ### Step 5: Implement[​](#step-5-implement "Direct link to Step 5: Implement") Run `/speckit.implement`. The AI executes each task using the appropriate Infrahub skill. **Task 1 output** — `schemas/vlan_management.yml` (via Schema Manager): ``` version: "1.0" nodes: - name: VLAN namespace: Ipam label: "VLAN" icon: "mdi:lan-pending" human_friendly_id: - "name__value" display_label: "{{ name__value }}" uniqueness_constraints: - ["vlan_id__value", "group"] attributes: - name: name kind: Text order_weight: 1000 - name: vlan_id kind: Number order_weight: 1100 - name: status kind: Dropdown order_weight: 1200 choices: - name: active label: Active color: "#7fbf7f" - name: provisioned label: Provisioned color: "#ffff7f" - name: maintenance label: Maintenance color: "#ffd27f" - name: decommissioning label: Decommissioning color: "#bf7f7f" - name: role kind: Text optional: true order_weight: 1300 - name: description kind: Text optional: true order_weight: 1400 relationships: - name: group peer: IpamVLANGroup kind: Attribute cardinality: one optional: false identifier: "group__vlans" - name: VLANGroup namespace: Ipam label: "VLAN Group" icon: "mdi:folder-network" human_friendly_id: - "name__value" display_label: "{{ name__value }}" attributes: - name: name kind: Text order_weight: 1000 - name: description kind: Text optional: true order_weight: 1100 relationships: - name: vlans peer: IpamVLAN kind: Attribute cardinality: many identifier: "group__vlans" ``` **Task 4 output** — `checks/check_vlan_id_uniqueness/check.py` (via Check Manager): ``` from infrahub_sdk.checks import InfrahubCheck class CheckVlanIdUniqueness(InfrahubCheck): query = "check_vlan_id_uniqueness" def validate(self, data): vlans = data["IpamVLAN"]["edges"] groups = {} for vlan in vlans: node = vlan["node"] group_name = node["group"]["node"]["display_label"] vlan_id = node["vlan_id"]["value"] key = (group_name, vlan_id) if key in groups: self.log_error( message=f"Duplicate VLAN ID {vlan_id} in group {group_name}" ) groups[key] = node["display_label"] ``` **Task 5 output** — registration added to `.infrahub.yml`: ``` check_definitions: - name: check_vlan_id_uniqueness class_name: CheckVlanIdUniqueness file_path: checks/check_vlan_id_uniqueness/check.py query: check_vlan_id_uniqueness ``` ### Result[​](#result "Direct link to Result") From a single natural-language description, the SDD workflow produced: * A schema with correct naming, Dropdown status, matching relationship identifiers, and uniqueness constraints * Seed data files with proper references * A working validation check registered in `.infrahub.yml` Each artifact follows Infrahub best practices because the AI applied the relevant skill at each step — not because the user knew the conventions upfront. --- # Infrahub Sync Infrahub Sync synchronizes infrastructure data between Infrahub and external systems — NetBox, Nautobot, IP Fabric, Slurp’it, Cisco ACI, Peering Manager, ServiceNow-style CMDBs, and any system with a REST/JSON API. Use it to migrate from a legacy system of record, run two systems side-by-side, or keep Infrahub in sync with another source of truth. Define a sync project declaratively in YAML, and the CLI generates the adapter code, calculates the diff, and applies only the deltas on each run. Infrahub Sync is open source under Apache 2.0, distributed on [PyPI](https://pypi.org/project/infrahub-sync/), and maintained on [GitHub](https://github.com/opsmill/infrahub-sync). Infrahub Sync supports the following: * **Migration from an existing system of record** — data moves from NetBox, Nautobot, or another source into Infrahub one model at a time, on the schedule the team chooses. The legacy system continues to operate during the migration. * **Recurring synchronization between systems** — a sync project runs as often as the environment requires. Each run calculates a fresh diff and applies only the deltas. * **Inventory population from network discovery** — adapters for IP Fabric and Slurp’it bring discovered network state into Infrahub as the source of truth, rather than requiring inventory to be entered by hand. * **Outbound data movement from Infrahub** — Infrahub data is published into monitoring, observability, or CMDB systems that need a current view of infrastructure. * **Translation between data models** — source fields map to destination fields through a declarative YAML configuration. Identifiers, relationships, and static values are handled in the same file. * **Diff preview before changes are applied** — `infrahub-sync diff` shows the differences between source and destination state without modifying either system. ## How it works[​](#how-it-works "Direct link to How it works") ### Concepts[​](#concepts "Direct link to Concepts") * **Sync project** — a directory containing a YAML configuration file (`config.yml`) that defines one synchronization between two systems. A project specifies the source, the destination, the sync order, and the per-model schema mapping. A team can have many sync projects, each managing a different source-destination pair. * **Adapter** — the component that connects Infrahub Sync to a specific system. Each adapter handles both communication (API calls, authentication, request handling) and translation (converting the system's data into the internal sync engine's format). Infrahub Sync ships with adapters for common systems and supports custom adapters for systems without a pre-built one. * **Schema mapping** — the part of the project configuration that defines how source fields map to destination fields. Direct field mappings, references between models, identifiers, and static values are all declared in YAML. Three CLI commands operate on a sync project: `generate`, `diff`, and `sync`. ### Define a sync project[​](#define-a-sync-project "Direct link to Define a sync project") Each sync project consists of a directory and a `config.yml` describing the sync. The configuration specifies the source adapter and destination adapter with their connection details, the order in which models should be synchronized, and how each source field maps to a destination field. Credentials reference environment variables rather than being embedded in the file. → [Create a sync project](/sync/creating-a-sync-project.md) · [Sync instance configuration](/sync/reference/config.md) ### Generate the adapter code[​](#generate-the-adapter-code "Direct link to Generate the adapter code") `infrahub-sync generate --name --directory ` reads the YAML configuration and produces the Python adapter and model code that `diff` and `sync` use. Re-run `generate` whenever the configuration or the schema mapping changes. ### Preview with `diff`[​](#preview-with-diff "Direct link to preview-with-diff") `infrahub-sync diff --name --directory ` reads both the source and destination, calculates what would change, and prints the result to the terminal. The destination is not modified. The `diff` command is read-only and is typically run before applying any sync — particularly during initial setup or when adjusting mappings. ### Execute the sync[​](#execute-the-sync "Direct link to Execute the sync") `infrahub-sync sync --name --directory ` applies the changes calculated by the diff, in the order defined by the project's `order` key — independent models first, then dependent models, then models that reference earlier ones. The sync is idempotent: if a run fails partway through, re-running calculates a fresh diff and applies whatever is still outstanding. Three `diffsync_flags` (`SKIP_UNMATCHED_DST` by default, `SKIP_UNMATCHED_SRC`, `SKIP_MODIFIED`) and per-mapping filters control what each run is allowed to change. → [Run a sync](/sync/running-a-sync.md) · [Sync CLI](/sync/reference/cli.md) ## Who it's for[​](#who-its-for "Direct link to Who it's for") ### Implementing Infrahub alongside an existing system of record[​](#implementing-infrahub-alongside-an-existing-system-of-record "Direct link to Implementing Infrahub alongside an existing system of record") Data lives in NetBox, Nautobot, IP Fabric, or another tool, and the team is adopting Infrahub. Infrahub Sync provides a path that does not require all teams to move at the same time, and that does not require writing integration code. → [Install Infrahub Sync](/sync/installation.md) · [Create a sync project](/sync/creating-a-sync-project.md) ### Operating Infrahub at steady state[​](#operating-infrahub-at-steady-state "Direct link to Operating Infrahub at steady state") Infrahub is deployed and needs to stay current with the other systems the team uses — IPAM, ITSM, monitoring, network discovery, or in-house databases. One sync project per source, run on the cadence the environment requires. → [Create a sync project](/sync/creating-a-sync-project.md) · [Run a sync](/sync/running-a-sync.md) ### Building inventory from network discovery[​](#building-inventory-from-network-discovery "Direct link to Building inventory from network discovery") Infrahub is populated from what is actually deployed in the network rather than from manually curated inventory. IP Fabric and Slurp’it adapters connect to discovery tools and bring discovered state into Infrahub. → [IP Fabric adapter](/sync/adapters/ipfabric.md) · [Slurp’it adapter](/sync/adapters/slurpit.md) ## What's included[​](#whats-included "Direct link to What's included") * **Pre-built adapter library** — adapters for Infrahub, NetBox, Nautobot, IP Fabric, Cisco ACI, LibreNMS, Observium, Peering Manager, Prometheus, and Slurp’it, plus a Generic REST API adapter for systems with HTTP/JSON APIs. Each adapter handles both communication and translation for its target system. * **Declarative YAML configuration** — a single file per sync project defines source, destination, sync order, and per-model schema mapping. Mappings support 14 filter operations (including `regex` and `is_ip_within`), per-field transforms, custom Jinja filters, and ordered cross-reference resolution. * **Sync engine** — built on the `diffsync` framework. Diffs and applies only deltas; three flags control what each run is allowed to change (creates, deletes, modifications). * **Typer-based CLI** — four commands: `list` (show available projects), `generate` (produce adapter code from the configuration), `diff` (preview changes), `sync` (apply changes). * **Custom adapter support** — for systems without a pre-built adapter, write a local custom adapter and load it from a filesystem path, a Python module path, or an installed entry point (`INFRAHUB_SYNC_ADAPTER_PATHS`). * **Custom CA certificate support** — connect to systems with self-signed or internal CA-issued TLS certificates. ### Adapter reference[​](#adapter-reference "Direct link to Adapter reference") | Adapter | Direction supported | | ---------------- | ------------------------------------------------------- | | Infrahub | source or destination | | NetBox | NetBox → Infrahub | | Nautobot | Nautobot → Infrahub | | IP Fabric | IP Fabric → Infrahub | | Cisco ACI | Cisco ACI → Infrahub | | Peering Manager | Peering Manager → Infrahub · Infrahub → Peering Manager | | Prometheus | Prometheus → Infrahub | | Slurp’it | Slurp’it → Infrahub | | LibreNMS | LibreNMS → Infrahub | | Observium | Observium → Infrahub | | Generic REST API | external system → Infrahub | ## Get started[​](#get-started "Direct link to Get started") 1. **Prerequisites** * A running [Infrahub](https://github.com/opsmill/infrahub) instance * Python 3.10–3.13 * Credentials and network access for the source and destination systems 2. **Install Infrahub Sync.** See [Install Infrahub Sync](/sync/installation.md) for the full setup steps. The short version: `pip install infrahub-sync` into a virtual environment. 3. **Choose your starting point.** * Setting up a sync project for the first time? → [Create a sync project](/sync/creating-a-sync-project.md) * Running an existing project? → [Run a sync](/sync/running-a-sync.md) ## Common questions[​](#common-questions "Direct link to Common questions") **Do I have to migrate everything at once?** No. Infrahub Sync is designed to move data one model at a time, on the team's own schedule. The legacy system keeps running throughout — there is no required cutover moment. **Does Infrahub Sync replace my scheduler?** No. Infrahub Sync runs as a CLI and is designed to plug into whatever scheduling tooling the team already uses — cron, CI jobs, Prefect, Dagster, or similar. There is no built-in scheduler by design. **What happens if a sync run fails partway through?** Sync runs are idempotent. Re-running the sync calculates a fresh diff against the current destination state and applies only what is still outstanding. Retries on failure are safe. **Can changes in the destination be overwritten by a sync?** By default, `SKIP_UNMATCHED_DST` is enabled, which preserves destination objects that have no corresponding object in the source. For destination objects that do have a source match, the sync's behavior depends on the configured `diffsync_flags`. Decide upfront which system is authoritative for each model and configure the flags accordingly. **What if my source system doesn't have a pre-built adapter?** Most systems with a REST/JSON API can use the Generic REST API adapter without modifications. For systems with non-standard APIs or custom logic requirements, build a local custom adapter. See [Local Adapters](/sync/adapters/local-adapters.md). **Can I run two sync projects at the same time?** Yes. Each sync project is independent — a separate directory, configuration, and CLI invocation. Schedule and operate each project on its own cadence. ## Additional resources[​](#additional-resources "Direct link to Additional resources") | What you want to do | Where to go | | ------------------------ | -------------------------------------------------------------------------------------------------------------------- | | Set up your environment | [Install Infrahub Sync](/sync/installation.md) | | Configure a sync project | [Create a sync project](/sync/creating-a-sync-project.md) · [Sync instance configuration](/sync/reference/config.md) | | Run a sync | [Run a sync](/sync/running-a-sync.md) | | CLI reference | [Sync CLI](/sync/reference/cli.md) | | All adapters | See the **Adapters** section in the sidebar | | Custom CA certificates | [Use custom CA certificates](/sync/custom-certificates.md) | | Build a custom adapter | [Local Adapters](/sync/adapters/local-adapters.md) | | Contribute | [Contributing guide](/sync/contributing.md) | | Source code | [github.com/opsmill/infrahub-sync](https://github.com/opsmill/infrahub-sync) | --- # Cisco ACI adapter ## What is Cisco ACI?[​](#what-is-cisco-aci "Direct link to What is Cisco ACI?") The *Cisco ACI* is a software-defined networking (SDN) solution that provides a policy-based, application-centric approach to managing and orchestrating network infrastructure. It is commonly used in data centers for scalable, policy-driven networking. ## Sync direction[​](#sync-direction "Direct link to Sync direction") Cisco ACI → Infrahub note Currently, the Cisco ACI adapter supports only **one-way synchronization** from ACI to Infrahub. Syncing data back into ACI is not yet supported. ## Configuration[​](#configuration "Direct link to Configuration") The adapter reads connection settings from the synchronization configuration and can be overridden by environment variables. Credentials should be provided via a secret manager or environment variables in production. ### Configuration parameters[​](#configuration-parameters "Direct link to Configuration parameters") ``` --- name: from-cisco-aci source: name: aci settings: url: "https://" username: "" password: "" api_endpoint: "api" # optional, default: api verify: true # boolean or string ("false","0") accepted ``` ### Environment variables[​](#environment-variables "Direct link to Environment variables") * CISCO\_APIC\_URL: overrides settings.url * CISCO\_APIC\_USERNAME: overrides settings.username * CISCO\_APIC\_PASSWORD: overrides settings.password * CISCO\_APIC\_VERIFY: overrides settings.verify; accepts true/false/0/1 (strings are normalized) Notes: * Credentials must come from environment variables or a secret manager in production. Never commit secrets. * The adapter normalizes verify to a boolean (strings like false, 0, no are treated as False). * The adapter records login timestamps in UTC to avoid timezone related issues and ensure correct token refresh behavior. ### Schema mapping examples[​](#schema-mapping-examples "Direct link to Schema mapping examples") #### Basic device mapping[​](#basic-device-mapping "Direct link to Basic device mapping") ``` - name: DcimPhysicalDevice mapping: "class/fabricNode.json" identifiers: ["name"] fields: - name: name mapping: "fabricNode.attributes.name" - name: serial mapping: "fabricNode.attributes.serial" - name: role mapping: "fabricNode.attributes.role" filters: - field: "fabricNode.attributes.fabricSt" operation: "==" value: "active" ``` #### Interface mapping with ACI Jinja filter[​](#interface-mapping-with-aci-jinja-filter "Direct link to Interface mapping with ACI Jinja filter") ``` - name: DcimPhysicalInterface mapping: "class/l1PhysIf.json" identifiers: ["device", "name"] fields: - name: name mapping: "l1PhysIf.attributes.id" - name: device mapping: "l1PhysIf.attributes.dn" reference: DcimPhysicalDevice - name: description mapping: "l1PhysIf.attributes.descr" transforms: - field: device expression: "{{ l1PhysIf.attributes.dn.split('/')[2].replace('node-', '') | aci_device_name }}" - field: status expression: "{{ 'active' if l1PhysIf.attributes.adminSt == 'up' else 'free' }}" filters: - field: "l1PhysIf.attributes.id" operation: "contains" value: "eth" ``` ## ACI-specific Jinja filters[​](#aci-specific-jinja-filters "Direct link to ACI-specific Jinja filters") The ACI adapter provides custom Jinja filters for data transformation: ### `aci_device_name` filter[​](#aci_device_name-filter "Direct link to aci_device_name-filter") The `aci_device_name` filter resolves ACI node IDs to device names automatically. This is particularly useful when mapping physical interfaces to their parent devices. **Usage:** ``` {{ node_id | aci_device_name }} ``` **Example:** * Input: `"102"` (ACI node ID) * Output: `"spine-102"` (actual device name from ACI) **Common use case in transforms:** ``` transforms: - field: device expression: "{{ l1PhysIf.attributes.dn.split('/')[2].replace('node-', '') | aci_device_name }}" ``` This transform: 1. Extracts the node ID from the ACI Distinguished Name (DN) 2. Removes the `"node-"` prefix (for example: `"node-102"` → `"102"`) 3. Uses the `aci_device_name` filter to resolve the node ID to the actual device name **How it works:** * The adapter automatically queries the ACI `fabricNode` class during initialization * Builds a mapping of node IDs to device names * The filter performs a lookup with a fallback to the original node ID if not found ## Generating the models[​](#generating-the-models "Direct link to Generating the models") Use the generate command to produce models from the schema mapping and examples: ``` uv run infrahub-sync generate --name from-cisco-aci --directory examples/ ``` ## Common issues and troubleshooting[​](#common-issues-and-troubleshooting "Direct link to Common issues and troubleshooting") ### Authentication and connectivity[​](#authentication-and-connectivity "Direct link to Authentication and connectivity") * If you see token refresh errors, ensure the APIC response includes refreshTimeoutSeconds; the adapter forces re-login when refresh data is unavailable. * For TLS verification problems, set CISCO\_APIC\_VERIFY to false in a secure environment (use with caution). ### Device reference resolution[​](#device-reference-resolution "Direct link to Device reference resolution") * **Interface-device relationship errors**: If you see "Unable to locate the node device" errors, ensure: * The `DcimPhysicalDevice` mapping runs before `DcimPhysicalInterface` in the `order` configuration * The device transform uses the `aci_device_name` filter correctly: `{{ node_id | aci_device_name }}` * The ACI fabric node query succeeds (check logs for "Built ACI device mapping" messages) ### Jinja filter issues[​](#jinja-filter-issues "Direct link to Jinja filter issues") * **`aci_device_name` filter not found**: Ensure you're using the ACI adapter and the filter is correctly spelled * **Filter returns node ID instead of device name**: Check that the fabric node query succeeded during adapter initialization * **Transform expression errors**: Verify the DN parsing logic extracts the correct node ID: ``` expression: "{{ l1PhysIf.attributes.dn.split('/')[2].replace('node-', '') | aci_device_name }}" ``` ### General debugging[​](#general-debugging "Direct link to General debugging") * Enable DEBUG logging for the adapter to see raw fetched objects and mapping decisions. Logs will not include secrets. * Check the device mapping build process in logs: look for "Built ACI device mapping with X entries" --- # Choose an adapter Infrahub Sync includes adapters for the most common infrastructure systems and a Generic REST API adapter for everything else. For systems with non-standard APIs, build a custom adapter. ## How to choose[​](#how-to-choose "Direct link to How to choose") If a pre-built adapter exists for the system you want to connect, use it. If the system has a REST/JSON API but no dedicated adapter, use the Generic REST API adapter. Only build a custom adapter when the system has a non-standard API, requires custom authentication logic, or has data access patterns the Generic REST API adapter cannot handle. ## Pre-built adapters[​](#pre-built-adapters "Direct link to Pre-built adapters") Each adapter handles two responsibilities for its target system: **communication** (talking to the system's API, authentication, request handling) and **translation** (converting the system's data structures into the `diffsync` models the sync engine works with). | Adapter | Direction supported | Best for | | ---------------- | ------------------------------------------------------- | --------------------------------------------------------------------------------- | | Infrahub | source or destination | Any sync project where Infrahub is one side of the sync | | NetBox | NetBox → Infrahub | Existing NetBox deployments migrating to or syncing with Infrahub | | Nautobot | Nautobot → Infrahub | Existing Nautobot deployments migrating to or syncing with Infrahub | | IP Fabric | IP Fabric → Infrahub | Building Infrahub inventory from IP Fabric network discovery data | | Slurp'it | Slurp'it → Infrahub | Building Infrahub inventory from Slurp'it network discovery data | | Cisco ACI | Cisco ACI → Infrahub | Syncing data from Cisco ACI fabrics into Infrahub | | Peering Manager | Peering Manager → Infrahub · Infrahub → Peering Manager | Bi-directional sync between Peering Manager and Infrahub | | PeeringDB | PeeringDB → Infrahub | Pulling autonomous system and internet exchange data from PeeringDB into Infrahub | | Prometheus | Prometheus → Infrahub | Pulling target or metric metadata from Prometheus into Infrahub | | LibreNMS | LibreNMS → Infrahub | Syncing device inventory and metadata from LibreNMS into Infrahub | | Observium | Observium → Infrahub | Syncing device inventory and metadata from Observium into Infrahub | | Device42 | Device42 → Infrahub | Syncing IT asset and dependency data from Device42 into Infrahub | | Generic REST API | external system → Infrahub | Any external system with a REST/JSON API and no dedicated adapter | For per-adapter details — connection parameters, supported models, known limitations — see the individual adapter pages in the Adapters sidebar. ## When to use the Generic REST API adapter[​](#when-to-use-the-generic-rest-api-adapter "Direct link to When to use the Generic REST API adapter") The Generic REST API adapter works with any system that exposes data over HTTP/JSON. It handles authentication and basic data access patterns out of configuration alone. Set the API endpoint, the authentication method, and the structure of the data you want to read in the same YAML format as every other adapter. Common cases where the Generic REST API adapter is the right choice: * **ServiceNow** — pull device, location, or service data from a ServiceNow CMDB. * **Infoblox** — pull IP address management data from Infoblox. * **Internal IPAM tools** — pull data from a homegrown IPAM or asset database. * **Vendor-specific inventory APIs** — pull data from a vendor's cloud-hosted inventory service. * **Spreadsheet-based data** — when a team exports a spreadsheet to a JSON file served over HTTP, the Generic REST API adapter can read it. Most source systems without a dedicated adapter are reachable directly with the Generic REST API adapter. See [Generic REST API adapter](/sync/adapters/genericrestapi.md) for configuration details. ## When to build a custom adapter[​](#when-to-build-a-custom-adapter "Direct link to When to build a custom adapter") A custom adapter is the right choice when the Generic REST API adapter can't handle the source system. Common reasons: * **Non-REST APIs.** The system uses gRPC, SOAP, GraphQL with non-standard patterns, a binary protocol, or any other non-REST interface. * **Custom authentication flows.** The system requires multi-step authentication, token refresh logic, or other auth patterns standard configuration cannot express. * **Stateful or session-based access.** The system requires maintaining a session across multiple requests in a way the Generic REST API adapter does not support. * **Complex pagination or query patterns.** The data access requires logic beyond what configuration can express — for example, conditional pagination based on response content. * **Data transformations that exceed Jinja filter capabilities.** The source data requires substantial preprocessing before mapping into `diffsync` models. A custom adapter is a Python class implementing the same interface as the pre-built adapters. Load it from a filesystem path, a Python module path, or an installed entry point. See [Local adapters](/sync/adapters/local-adapters.md) for how to build and load one. If you build a custom adapter for a system other teams might use, consider contributing it back to the Infrahub Sync repository. ## Choosing between two valid options[​](#choosing-between-two-valid-options "Direct link to Choosing between two valid options") Sometimes more than one adapter works for the same source system. For example: a Nautobot deployment is reachable through the Nautobot adapter or through the Generic REST API adapter pointed at Nautobot's REST API. Use the dedicated adapter when one exists — it understands the source system's data model and handles edge cases (nested data, authentication) that would otherwise need to be configured manually. The Generic REST API adapter is the fallback, not the default. ## Multiple adapters in one sync project[​](#multiple-adapters-in-one-sync-project "Direct link to Multiple adapters in one sync project") A sync project has one source adapter and one destination adapter. To sync data from multiple sources into Infrahub, create one sync project per source. Each project has its own configuration, sync order, and schedule. This keeps each sync independent — a failure in one project does not affect others, and each project can be scheduled and operated separately. ## Related[​](#related "Direct link to Related") * [Generic REST API adapter](/sync/adapters/genericrestapi.md) * [Local adapters](/sync/adapters/local-adapters.md) * [Create a sync project](/sync/creating-a-sync-project.md) * [Sync instance configuration](/sync/reference/config.md) --- # Device42 adapter ## What is Device42?[​](#what-is-device42 "Direct link to What is Device42?") [Device42](https://www.device42.com/) is a commercial IT infrastructure and asset management platform. It discovers and inventories physical and virtual infrastructure — servers, network devices, applications, services — and tracks the dependencies between them. ## Sync directions supported[​](#sync-directions-supported "Direct link to Sync directions supported") * Device42 → Infrahub info The Device42 adapter supports only **one-way synchronization** from Device42 to Infrahub. Writing data back into Device42 is not supported. Under Construction --- # GenericRestAPI adapter The GenericRestAPI adapter is a flexible, configurable adapter that can connect to any REST API following common patterns. It's designed to reduce code duplication and provide a foundation for creating adapters for new systems without having to build them from scratch. This adapter can be used in two ways: 1. As a standalone adapter for systems that have a REST API 2. As a base for building more specialized adapters (like LibreNMS, Observium, PeeringDB) ## Key features[​](#key-features "Direct link to Key features") * Flexible authentication methods (token, API key, basic auth) * Configurable endpoints and URL structures * Response format handling with customizable extraction * Transformation and filtering capabilities * Environment variable support for credentials ## Sync directions supported[​](#sync-directions-supported "Direct link to Sync directions supported") * GenericRestAPI → Infrahub info Currently, the GenericRestAPI adapter supports only **one-way synchronization** from a REST API source to Infrahub. Syncing data back to the source is not yet supported and need to be done on custom adapters. Those adapters can use the generic one as base similar to what is done with Peering Manager adapter. ## Configuration[​](#configuration "Direct link to Configuration") ### Basic configuration[​](#basic-configuration "Direct link to Basic configuration") To use the GenericRestAPI adapter, specify `genericrestapi` as the name in your configuration: ``` source: name: genericrestapi settings: url: "https://api.example.com" api_endpoint: "/api/v1" auth_method: "token" token: "YOUR_API_TOKEN" # Better to use environment variables ``` ### Configuration parameters[​](#configuration-parameters "Direct link to Configuration parameters") | Parameter | Description | Default | Required | | ---------------------- | ---------------------------------------------------------------- | --------- | -------------- | | `url` | Base URL of the API | None | Yes | | `api_endpoint` | API endpoint path | `/api/v0` | No | | `auth_method` | Authentication method (token, x-auth-token, api-key, key, basic) | `token` | No | | token | API token for token-based auth | None | For token auth | | username | Username for basic auth | None | For basic auth | | password | Password for basic auth | None | For basic auth | | `verify_ssl` | Whether to verify SSL certificates | `true` | No | | timeout | Request timeout in seconds | `30` | No | | `params` | Additional query parameters to include in all requests | `{}` | No | | `response_key_pattern` | Pattern for extracting data from responses | None | No | ### Environment variables[​](#environment-variables "Direct link to Environment variables") You can specify credentials using environment variables instead of storing them directly in the configuration: | Setting | Environment Variables | Description | | -------- | --------------------- | ------------------- | | `url` | URL, ADDRESS | API base URL | | token | TOKEN | API token/key | | username | USERNAME | Basic auth username | | password | PASSWORD | Basic auth password | The adapter will check for these environment variables in the order specified. ### Custom response handling[​](#custom-response-handling "Direct link to Custom response handling") If your API has a specific response format, you can customize how the data is extracted using the `response_key_pattern` setting: ``` source: name: genericrestapi settings: # ...other settings... response_key_pattern: "data.{resource}.items" ``` This pattern will be formatted with: * `{resource}`: The endpoint being queried * `{default}`: The default key (last part of the endpoint path) ## Schema mapping[​](#schema-mapping "Direct link to Schema mapping") The schema mapping defines how data from the API is mapped to Infrahub models: ``` schema_mapping: - name: InfraDevice # Infrahub model mapping: devices # API endpoint identifiers: ["name"] # Unique identifier fields filters: # Optional filters to apply - field: hostname operation: contains value: "router" fields: - name: name # Infrahub field mapping: hostname # API field - name: type mapping: device_type - name: site # Reference to another model mapping: location reference: LocationSite ``` ### Field mapping types[​](#field-mapping-types "Direct link to Field mapping types") * **Direct mapping**: Maps a field from the API to an Infrahub field * **Static value**: Sets a constant value for an Infrahub field * **Reference**: Links to another Infrahub model ``` fields: # Direct mapping - name: name mapping: hostname # Static value - name: type static: "Network Device" # Reference to another model - name: site mapping: location_id reference: LocationSite ``` ### Filtering records[​](#filtering-records "Direct link to Filtering records") You can filter records from the API before processing: ``` filters: - field: status operation: equals value: "active" - field: hostname operation: contains value: "prod" ``` Supported operations: equals, `not_equals`, contains, `not_contains`, in, `not_in` ## Examples[​](#examples "Direct link to Examples") ### Basic REST API example[​](#basic-rest-api-example "Direct link to Basic REST API example") ``` --- name: rest-api-example source: name: genericrestapi settings: url: "http://api.example.com" api_endpoint: "/api/v1" auth_method: "token" token: "${API_TOKEN}" # Uses environment variable destination: name: infrahub settings: url: "http://localhost:8000" order: [ "InfraDevice", "IpamIPAddress", ] schema_mapping: - name: InfraDevice mapping: devices identifiers: ["name"] fields: - name: name mapping: hostname - name: serial_number mapping: serial - name: type static: "Network Device" - name: IpamIPAddress mapping: ips identifiers: ["address"] fields: - name: address mapping: ip_address - name: description mapping: description ``` ### LibreNMS example[​](#librenms-example "Direct link to LibreNMS example") LibreNMS is one of the systems that use the GenericRestAPI adapter: ``` --- name: from-librenms source: name: genericrestapi settings: url: "http://librenms.example.com" api_endpoint: "api/v0" auth_method: "x-auth-token" token: "${LIBRENMS_TOKEN}" destination: name: infrahub settings: url: "http://localhost:8000" order: [ "CoreStandardGroup", "LocationSite", "IpamIPAddress", "InfraDevice", ] schema_mapping: - name: CoreStandardGroup mapping: devicegroups fields: - name: name mapping: name - name: description mapping: desc - name: LocationSite mapping: resources/locations identifiers: ["name"] fields: - name: name mapping: location - name: description mapping: location - name: IpamIPAddress mapping: devices identifiers: ["address"] fields: - name: address mapping: ip - name: description mapping: hostname - name: InfraDevice mapping: devices identifiers: ["name"] fields: - name: name mapping: hostname - name: serial_number mapping: serial - name: type mapping: hardware - name: site mapping: location reference: LocationSite ``` ## Common issues[​](#common-issues "Direct link to Common issues") ### Authentication failures[​](#authentication-failures "Direct link to Authentication failures") If you encounter authentication issues: 1. Verify your token/credentials are correct 2. Check that you're using the correct `auth_method` for your API 3. Ensure the API endpoint path is correct ### Response parsing errors[​](#response-parsing-errors "Direct link to Response parsing errors") If the adapter fails to parse the API response: 1. Check the API documentation for the correct response format 2. Use the `response_key_pattern` setting to customize data extraction 3. Consider implementing a custom adapter extending GenericRestAPI ### Connection timeout[​](#connection-timeout "Direct link to Connection timeout") For slow APIs or large datasets: 1. Increase the timeout setting 2. Add pagination parameters if the API supports it 3. Use filters to reduce the amount of data fetched ## Building specialized adapters[​](#building-specialized-adapters "Direct link to Building specialized adapters") You can extend the GenericRestAPI adapter to create more specialized adapters: ``` from infrahub_sync.adapters.genericrestapi import GenericrestapiAdapter class MySpecializedAdapter(GenericrestapiAdapter): def __init__(self, target, adapter, config, *args, **kwargs): # Override adapter_type to change the adapter name in logs super().__init__(target, adapter, config, adapter_type="MySpecialized", *args, **kwargs) # Override methods as needed for specialized behavior def _extract_objects_from_response(self, response_data, resource_name, element): # Custom extraction logic return custom_data ``` --- # Infrahub adapter ## What is Infrahub?[​](#what-is-infrahub "Direct link to What is Infrahub?") Infrahub is an open-source infrastructure management platform that combines a version-controlled database with a schema-driven data model. It serves as the central hub for network automation, providing a single source of truth for infrastructure data. ## Requirements[​](#requirements "Direct link to Requirements") This adapter uses the [Infrahub SDK](https://pypi.org/project/infrahub-sdk), which is included as a dependency of infrahub-sync. ## Sync directions supported[​](#sync-directions-supported "Direct link to Sync directions supported") * Source: Infrahub can be used as a data source * Destination: Infrahub is the primary destination for most sync configurations info The Infrahub adapter supports **bidirectional synchronization**. Most commonly, Infrahub is used as the destination, receiving data from other systems like NetBox, Nautobot, or IP Fabric. ## Configuration[​](#configuration "Direct link to Configuration") The adapter reads connection settings from the synchronization configuration and can be overridden by environment variables. ### Configuration parameters[​](#configuration-parameters "Direct link to Configuration parameters") * As Destination * As Source ``` --- name: from-netbox source: name: netbox settings: url: "https://" token: "" destination: name: infrahub settings: url: "http://localhost:8000" token: "" # Optional if using env var branch: "main" # Optional, defaults to main verify_ssl: true # Optional, defaults to true source: "data-sync" # Optional, CoreAccountGroup name for lineage source owner: "network-team" # Optional, CoreAccountGroup name for lineage owner ``` ``` --- name: to-peering-manager source: name: infrahub settings: url: "http://localhost:8000" token: "" branch: "main" destination: name: peeringmanager settings: url: "https://" token: "" ``` ### Environment variables[​](#environment-variables "Direct link to Environment variables") | Variable | Description | | ------------------------------------ | --------------------------------------------------------- | | `INFRAHUB_ADDRESS` or `INFRAHUB_URL` | Infrahub server URL (overrides `settings.url`) | | `INFRAHUB_API_TOKEN` | API token for authentication (overrides `settings.token`) | ### Settings reference[​](#settings-reference "Direct link to Settings reference") | Setting | Type | Required | Default | Description | | ------------ | ------- | -------- | ------- | ---------------------------------------------- | | `url` | string | Yes\* | - | Infrahub server URL | | `token` | string | Yes\* | - | API token for authentication | | `branch` | string | No | main | Target branch for operations | | `verify_ssl` | boolean | No | true | Verify SSL certificates | | `source` | string | No | - | CoreAccountGroup name to use as lineage source | | `owner` | string | No | - | CoreAccountGroup name to use as lineage owner | \*Can be provided via environment variables instead. ## Schema mapping[​](#schema-mapping "Direct link to Schema mapping") When Infrahub is used as a destination, the schema mapping defines how source data maps to Infrahub node types. The `name` field in schema\_mapping corresponds to the Infrahub schema kind. ### Basic example[​](#basic-example "Direct link to Basic example") ``` schema_mapping: - name: InfraDevice mapping: dcim.devices identifiers: ["name"] fields: - name: name mapping: name - name: description mapping: description - name: status mapping: status.value - name: InfraInterface mapping: dcim.interfaces identifiers: ["device", "name"] fields: - name: name mapping: name - name: device mapping: device.name reference: InfraDevice ``` ### Relationship handling[​](#relationship-handling "Direct link to Relationship handling") The Infrahub adapter automatically resolves relationships based on the schema: * **One-to-one relationships**: Resolved using the unique identifier of the related node * **One-to-many relationships**: Resolved as a list of unique identifiers ``` fields: # One-to-one relationship - name: site mapping: site.name reference: LocationSite # One-to-many relationship - name: tags mapping: tags reference: BuiltinTag ``` ## Lineage tracking[​](#lineage-tracking "Direct link to Lineage tracking") When syncing data into Infrahub, the adapter tracks the source and owner of each record using Infrahub's lineage system. This enables: * Tracking which system created each record * Protecting source-managed attributes from manual edits * Assigning ownership to teams or groups ### Default behavior[​](#default-behavior "Direct link to Default behavior") By default, the adapter looks for a `CoreAccount` with a name matching the source adapter name. This account is used as both the source and owner for all synced attributes. ### Using account groups[​](#using-account-groups "Direct link to Using account groups") You can override the default behavior by specifying `source` and/or `owner` in the destination settings. When specified, these values are used to look up a `CoreAccountGroup` by name: ``` destination: name: infrahub settings: url: "http://localhost:8000" token: "" source: "data-sync" # Uses CoreAccountGroup "data-sync" as source owner: "network-team" # Uses CoreAccountGroup "network-team" as owner ``` This allows you to: * Use different groups for source and owner * Override only one while keeping the default account for the other * Assign ownership to a team rather than an individual account | Configuration | Source | Owner | | ----------------------- | ------------------------- | ------------------------- | | Neither specified | CoreAccount (source name) | CoreAccount (source name) | | Only `source` specified | CoreAccountGroup | CoreAccount (source name) | | Only `owner` specified | CoreAccount (source name) | CoreAccountGroup | | Both specified | CoreAccountGroup | CoreAccountGroup | ## Generating the models[​](#generating-the-models "Direct link to Generating the models") Use the generate command to produce Python models from your configuration: ``` uv run infrahub-sync generate --name from-netbox --directory examples/ ``` ## Common issues and troubleshooting[​](#common-issues-and-troubleshooting "Direct link to Common issues and troubleshooting") ### Authentication errors[​](#authentication-errors "Direct link to Authentication errors") * Verify `INFRAHUB_API_TOKEN` is set correctly * Ensure the token has appropriate permissions for the target branch * Check that `INFRAHUB_ADDRESS` points to the correct server ### Schema mismatch errors[​](#schema-mismatch-errors "Direct link to Schema mismatch errors") * Ensure your Infrahub schema matches the expected node types in schema\_mapping * Run `infrahubctl schema load` to update the schema if needed * Verify field names match the Infrahub schema attribute names ### Relationship resolution failures[​](#relationship-resolution-failures "Direct link to Relationship resolution failures") * Ensure referenced objects are synced before objects that reference them (check `order` in configuration) * Verify the unique identifier used in references matches the target object's identifier * Check logs for "Unable to find ... in the Store" messages ### Branch operations[​](#branch-operations "Direct link to Branch operations") * Verify the target branch exists in Infrahub * Use `infrahubctl branch list` to see available branches * Create branches with `infrahubctl branch create ` if needed ### SSL certificate issues[​](#ssl-certificate-issues "Direct link to SSL certificate issues") * Set `verify_ssl: false` in settings for self-signed certificates (development only) * For production, ensure proper CA certificates are installed --- # IP Fabric adapter ## What is IP Fabric?[​](#what-is-ip-fabric "Direct link to What is IP Fabric?") The **IP Fabric** network infrastructure management platform provides on-demand network discovery, advanced analytics, and in-depth engineering visibility. Its lightweight discovery capabilities (via SSH or Telnet) quickly detect the current network state, including detailed data for each address and port. A network model of gathered data reconstructs the topologies for each switching and routing protocol, enabling cross-technology analysis of upstream and downstream relationships. Dependencies and dependents are calculated for each network element, allowing analysis to represent each aspect of the network in the context of productivity impact on downstream hosts and network devices. The immediate productivity impact of performance and capacity is also calculated for each user and every element. ## Requirements[​](#requirements "Direct link to Requirements") This Adapter uses [IP Fabric SDK](https://pypi.org/project/ipfabric). You will need to install it beforehand. ## Sync directions supported[​](#sync-directions-supported "Direct link to Sync directions supported") * IP Fabric → Infrahub info Currently, the IP Fabric adapter supports only **one-way synchronization** from IP Fabric to Infrahub. Syncing data back into IP Fabric is not yet supported. Although IP Fabric data cannot be edited, additional information such as **`Device Attributes`** and **`Site Separation`** rules can be updated from Infrahub data. ## Schema[​](#schema "Direct link to Schema") Our `infrahub` repository contains an **example schema** that serves as a starting point for syncing IP Fabric data into Infrahub. This schema follows best practices for Infrahub, **but it does not map the IP Fabric data model one-to-one** since Infrahub may have additional use cases. [Schemahttps://github.com/opsmill/infrahub/blob/stable/models/examples/ipfabric/ipfabric.yml](https://github.com/opsmill/infrahub/blob/stable/models/examples/ipfabric/ipfabric.yml) ### Installing the example schema[​](#installing-the-example-schema "Direct link to Installing the example schema") To install the example schema into Infrahub, follow these steps: ``` mkdir ipfabric-sync cd ipfabric-sync curl -o schema.yml https://raw.githubusercontent.com/opsmill/infrahub/refs/heads/stable/models/examples/ipfabric/ipfabric.yml infrahubctl schema load schema.yml ``` ## Configuration[​](#configuration "Direct link to Configuration") `infrahub-sync` allows defining what gets synchronized from a source to a destination. Included in the examples is a config.yml file that matches the example schema. [config.ymlhttps://github.com/opsmill/infrahub-sync/blob/main/examples/ipfabric\_to\_infrahub/config.yml](https://github.com/opsmill/infrahub-sync/blob/main/examples/ipfabric_to_infrahub/config.yml) To download the example `config.yml` ``` curl https://raw.githubusercontent.com/opsmill/infrahub-sync/refs/heads/main/examples/ipfabric_to_infrahub/config.yml > config.yml ``` ### Configuration parameters[​](#configuration-parameters "Direct link to Configuration parameters") The `source.name` is set to `ipfabricsync`, instructing `infrahub-sync` to use the IP Fabric adapter. The settings dictionary is passed directly to the IP Fabric Python SDK for authentication and connection details. Some of these parameters can be found in the [IP Fabric SDK Docs](https://docs.ipfabric.io/latest/integrations/python/#environment-variables). Below is a snippet from the example config.yml file: ``` --- name: from-ipfabric source: name: ipfabricsync settings: base_url: "https://" auth: "" verify_ssl: true # Default value ``` ### Schema mapping[​](#schema-mapping "Direct link to Schema mapping") The configuration file allows mapping tables from IP Fabric into Infrahub models. Below is an example showing how to: * Set the destination Infrahub model (`InfraDevice`) * Map source data from IP Fabric’s Path (`tables/inventory/devices`) * Specify field mappings between IP Fabric and Infrahub models ``` schema_mapping: - name: InfraDevice identifiers: ["hostname"] mapping: tables/inventory/devices fields: - name: hostname mapping: hostname - name: serial_number mapping: sn - name: hardware_serial_number mapping: snHw - name: fqdn mapping: fqdn - name: model mapping: model reference: TemplateDeviceType - name: location mapping: siteName reference: LocationGeneric - name: platform mapping: platform reference: InfraPlatform - name: version mapping: version reference: InfraNOSVersion ``` The table URI can be found by selecting `Table Description` in any IP Fabric table and copying everything after the version in the URL description. For more details regarding, refer to the [IP Fabric API Docs](https://docs.ipfabric.io/latest/IP_Fabric_API/?h=api+doc#technology-table-endpoints). --- # LibreNMS adapter ## What is LibreNMS?[​](#what-is-librenms "Direct link to What is LibreNMS?") LibreNMS is an open-source network monitoring system that automatically discovers, monitors, and alerts on your network devices using pre-configured setups, providing comprehensive insights for proactive network management. ## Sync directions supported[​](#sync-directions-supported "Direct link to Sync directions supported") * LibreNMS → Infrahub info Currently, the LibreNMS adapter supports only **one-way synchronization** from LibreNMS to Infrahub. Syncing data back into LibreNMS is not yet supported. Under Construction --- # Local Adapters ## Using local adapters[​](#using-local-adapters "Direct link to Using local adapters") The infrahub-sync tool supports loading adapters from various sources, including local filesystem paths. This feature allows you to develop and use custom adapters without having to integrate them into the main codebase. ### Adapter resolution paths[​](#adapter-resolution-paths "Direct link to Adapter resolution paths") The plugin loader can resolve adapters from: 1. **Built-ins**: Adapters that ship with infrahub-sync (`infrahub_sync.adapters.`) 2. **Dotted paths**: Python module paths (`myproj.adapters.foo:MyAdapter`) 3. **Filesystem paths**: Local files or directories (`./adapters/foo.py:MyAdapter`) 4. **Python entry points**: Installed packages that register entry points ### Configuring local adapters[​](#configuring-local-adapters "Direct link to Configuring local adapters") To use a local adapter in your configuration, specify the filesystem path in the `adapter` field: ``` source: name: mycustom adapter: ./path/to/my_adapter.py:MyCustomAdapter settings: # Your adapter settings here ``` If your adapter class name follows naming conventions (for example: `MycustomAdapter`), you can omit the class name: ``` source: name: mycustom adapter: ./path/to/my_adapter.py settings: # Your adapter settings here ``` ### Environment variables[​](#environment-variables "Direct link to Environment variables") You can also specify additional adapter search paths via the `INFRAHUB_SYNC_ADAPTER_PATHS` environment variable: ``` # Unix/Linux/macOS export INFRAHUB_SYNC_ADAPTER_PATHS="/path/to/adapters:/another/path" # Windows set INFRAHUB_SYNC_ADAPTER_PATHS="C:\path\to\adapters;D:\another\path" ``` ### Creating a custom adapter[​](#creating-a-custom-adapter "Direct link to Creating a custom adapter") A minimal custom adapter needs to extend `diffsync.Adapter` and implement the necessary methods: ``` from diffsync import Adapter from infrahub_sync import DiffSyncMixin class MyCustomAdapter(DiffSyncMixin, Adapter): def __init__(self, target, adapter, config, *args, **kwargs): super().__init__(*args, **kwargs) self.target = target self.settings = adapter.settings or {} self.config = config def model_loader(self, model_name, model): # Your implementation to load data into the model pass ``` For a more complete example, refer to the example in the repository: [Custom Adapter Examplehttps://github.com/opsmill/infrahub-sync/tree/main/examples/custom\_adapter](https://github.com/opsmill/infrahub-sync/tree/main/examples/custom_adapter) ### Adding custom Jinja filters[​](#adding-custom-jinja-filters "Direct link to Adding custom Jinja filters") Custom adapters can provide their own Jinja filters for use in transform expressions. This is particularly useful for adapter-specific data transformations. #### Implementing custom filters[​](#implementing-custom-filters "Direct link to Implementing custom filters") To add custom filters to your adapter, implement the `_add_custom_filters` class method in your DiffSync model class: ``` from typing import Any, ClassVar from diffsync import DiffSyncModel from infrahub_sync import DiffSyncModelMixin class MyCustomModel(DiffSyncModelMixin, DiffSyncModel): # Store any data needed by filters as class variables _my_mapping: ClassVar[dict[str, str]] = {} @classmethod def set_my_mapping(cls, mapping: dict[str, str]) -> None: """Set mapping data for use in filters.""" cls._my_mapping = mapping @classmethod def _add_custom_filters(cls, native_env: Any, item: dict[str, Any]) -> None: """Add custom filters to the Jinja environment.""" def my_custom_filter(value: str) -> str: """Custom filter that transforms values using stored mapping.""" return cls._my_mapping.get(str(value), value) def format_identifier(value: str) -> str: """Another custom filter for formatting identifiers.""" return f"ID-{value.upper()}" # Register filters with the Jinja environment native_env.filters["my_custom_filter"] = my_custom_filter native_env.filters["format_identifier"] = format_identifier ``` #### Setting up filter data[​](#setting-up-filter-data "Direct link to Setting up filter data") If your filters need data (like mappings, lookup values, etc.), initialize it in your adapter: ``` class MyCustomAdapter(DiffSyncMixin, Adapter): def __init__(self, target, adapter, config, *args, **kwargs): super().__init__(*args, **kwargs) # ... other initialization # Build data needed by filters my_mapping = self._build_custom_mapping() # Pass data to model class for filter use MyCustomModel.set_my_mapping(my_mapping) def _build_custom_mapping(self) -> dict[str, str]: """Build mapping data from your data source.""" # Implementation depends on your data source return {"key1": "value1", "key2": "value2"} ``` #### Using custom filters in configuration[​](#using-custom-filters-in-configuration "Direct link to Using custom filters in configuration") Once implemented, use your custom filters in transform expressions: ``` schema_mapping: - name: MyModel mapping: "api/endpoint" fields: - name: identifier mapping: "raw_id" - name: formatted_name mapping: "name" transforms: - field: identifier expression: "{{ raw_id | my_custom_filter | format_identifier }}" - field: status expression: "{{ 'active' if enabled else 'inactive' }}" ``` #### Filter implementation guidelines[​](#filter-implementation-guidelines "Direct link to Filter implementation guidelines") 1. **Keep filters focused**: Each filter should do one specific transformation 2. **Handle edge cases**: Always provide fallback values for missing data 3. **Use class variables**: Store filter data as class variables for efficient access 4. **Document your filters**: Add Python documentation strings explaining what each filter does 5. **Test thoroughly**: Ensure filters work with various input types and edge cases #### Real-world example: ACI device name filter[​](#real-world-example-aci-device-name-filter "Direct link to Real-world example: ACI device name filter") Here's how the ACI adapter implements the `aci_device_name` filter: ``` class AciModel(DiffSyncModelMixin, DiffSyncModel): _device_mapping: ClassVar[dict[str, str]] = {} @classmethod def set_device_mapping(cls, device_mapping: dict[str, str]) -> None: cls._device_mapping = device_mapping @classmethod def _add_custom_filters(cls, native_env: Any, item: dict[str, Any]) -> None: def aci_device_name(node_id: str) -> str: """Resolve ACI node IDs to device names.""" return cls._device_mapping.get(str(node_id), node_id) native_env.filters["aci_device_name"] = aci_device_name ``` Used in configuration: ``` transforms: - field: device expression: "{{ l1PhysIf.attributes.dn.split('/')[2].replace('node-', '') | aci_device_name }}" ``` ### Best practices[​](#best-practices "Direct link to Best practices") 1. **Package Structure**: Organize complex adapters as packages with `__init__.py` 2. **Testing**: Include test data and documentation with your adapter 3. **Configuration**: Use settings to make your adapter configurable 4. **Error Handling**: Implement proper error handling and logging 5. **Type Annotations**: Use type hints to make your code more maintainable 6. **Custom Filters**: Implement adapter-specific Jinja filters for complex transformations ### Local adapter example[​](#local-adapter-example "Direct link to Local adapter example") Here's an example configuration using a local adapter: ``` name: custom-example source: name: mockdb # Filesystem path to the adapter class adapter: ./examples/custom_adapter/custom_adapter_src/custom_adapter.py:MockdbAdapter settings: db_path: "./examples/custom_adapter/custom_adapter_src/mock_db.json" destination: name: infrahub settings: url: "http://localhost:8000" order: [ "InfraDevice", ] schema_mapping: # Your schema mapping here ``` --- # Nautobot adapter ## What is Nautobot?[​](#what-is-nautobot "Direct link to What is Nautobot?") Nautobot is an open-source network source of truth and automation platform, offering extended customization, plugin support, and enhanced functionality to simplify network documentation and operations. ## Requirements[​](#requirements "Direct link to Requirements") This Adapter uses [Nautobot SDK](https://pypi.org/project/pynautobot). You will need to install it beforehand. ## Sync directions supported[​](#sync-directions-supported "Direct link to Sync directions supported") * Nautobot → Infrahub info Currently, the Nautobot adapter supports only **one-way synchronization** from Nautobot to Infrahub. Syncing data back into Nautobot is not yet supported. ## Schema[​](#schema "Direct link to Schema") Our `infrahub` repository contains an **example schema** that serves as a starting point for syncing Nautobot data into Infrahub. This schema follows best practices for Infrahub, **but it does not map the Nautobot data model one-to-one** since Infrahub may have additional use cases. To reflect the breaking changes introduced in Nautobot v2, there is not one but two schemas. You can explore them to see the difference. * Nautobot v1 * Nautobot v2 [Schema V1https://github.com/opsmill/infrahub/blob/stable/models/examples/nautobot/nautobot-v1.yml](https://github.com/opsmill/infrahub/blob/stable/models/examples/nautobot/nautobot-v1.yml) [Schema V2https://github.com/opsmill/infrahub/blob/stable/models/examples/nautobot/nautobot-v2.yml](https://github.com/opsmill/infrahub/blob/stable/models/examples/nautobot/nautobot-v2.yml) ### Installing the example schema[​](#installing-the-example-schema "Direct link to Installing the example schema") To install the example schema into Infrahub, follow these steps: * Nautobot v1 * Nautobot v2 ``` mkdir nautobot-sync cd nautobot-sync curl -o schema.yml https://raw.githubusercontent.com/opsmill/infrahub/refs/heads/stable/models/examples/nautobot/nautobot-v1.yml infrahubctl schema load schema.yml ``` ``` mkdir nautobot-sync cd nautobot-sync curl -o schema.yml https://raw.githubusercontent.com/opsmill/infrahub/refs/heads/stable/models/examples/nautobot/nautobot-v2.yml infrahubctl schema load schema.yml ``` ## Configuration[​](#configuration "Direct link to Configuration") `infrahub-sync` allows defining what gets synchronized from a source to a destination. Included in the examples is a config.yml file that matches the example schema. * Nautobot v1 * Nautobot v2 [config.ymlhttps://github.com/opsmill/infrahub-sync/blob/main/examples/nautobot-v1\_to\_infrahub/config.yml](https://github.com/opsmill/infrahub-sync/blob/main/examples/nautobot-v1_to_infrahub/config.yml) [config.ymlhttps://github.com/opsmill/infrahub-sync/blob/main/examples/nautobot-v2\_to\_infrahub/config.yml](https://github.com/opsmill/infrahub-sync/blob/main/examples/nautobot-v2_to_infrahub/config.yml) To download the example `config.yml` * Nautobot v1 * Nautobot v2 ``` curl https://raw.githubusercontent.com/opsmill/infrahub-sync/refs/heads/main/examples/nautobot-v1_to_infrahub/config.yml > config.yml ``` ``` curl https://raw.githubusercontent.com/opsmill/infrahub-sync/refs/heads/main/examples/nautobot-v2_to_infrahub/config.yml > config.yml ``` ### Configuration parameters[​](#configuration-parameters "Direct link to Configuration parameters") To instruct `infrahub-sync` to use the Nautobot adapter, set `nautobot` in the direction 'name', you want to use for Peering Manager. Below is a snippet from the example config.yml file: * Source example ``` --- name: from-nautobot source: name: nautobot settings: url: "https://" token: "" verify_ssl: true # Default value ``` ### Schema mapping[​](#schema-mapping "Direct link to Schema mapping") The configuration file allows mapping Nautobot data to the Infrahub schema, which has been designed to loosely align with the Nautobot data model. Below is an example showing how to: * Set the destination Infrahub model (`OrganizationGeneric` and `InfraPlatform`) * Map source data from Nautobot (`dcim.manufacturers`, and `dcim.platforms`) * Specify field mappings between Nautobot and Infrahub models As you can see in this example, we can map several paths to the same Infrahub Model. ``` - name: OrganizationGeneric mapping: dcim.manufacturers identifiers: ["name"] fields: - name: name mapping: name - name: description mapping: description - name: type static: "Manufacturer" - name: InfraPlatform mapping: dcim.platforms identifiers: ["name", "manufacturer"] fields: - name: name mapping: name - name: description mapping: description - name: napalm_driver mapping: napalm_driver - name: manufacturer mapping: manufacturer reference: OrganizationGeneric ``` The models available on Nautobot can be find in the `/api/schema/swagger-ui` of your instance. [Nautobot API Docshttps://demo.nautobot.com/api/docs](https://demo.nautobot.com/api/docs) ## Related guides[​](#related-guides "Direct link to Related guides") * [Use NetBox or Nautobot with Infrahub](/sync/using-netbox-or-nautobot-with-infrahub.md) — running Nautobot and Infrahub side by side * [Migrate from NetBox or Nautobot](/sync/migrating-from-netbox-or-nautobot.md) — gradual migration into Infrahub * [Schema mapping reference](/sync/reference/schema-mapping.md) — mapping syntax with Nautobot examples * [Choose an adapter](/sync/adapters/choosing-an-adapter.md) --- # NetBox adapter ## What is NetBox?[​](#what-is-netbox "Direct link to What is NetBox?") NetBox is an open-source infrastructure resource management tool that centralizes and documents your network's devices, IP addresses, circuits, and connections using pre-configured models and automation for streamlined operations. ## Requirements[​](#requirements "Direct link to Requirements") This Adapter uses [Netbox SDK](https://pypi.org/project/pynetbox). You will need to install it beforehand. ## Sync directions supported[​](#sync-directions-supported "Direct link to Sync directions supported") * NetBox → Infrahub info Currently, the Netbox adapter supports only **one-way synchronization** from Netbox to Infrahub. Syncing data back into Netbox is not yet supported. ## Schema[​](#schema "Direct link to Schema") Our `infrahub` repository contains an **example schema** that serves as a starting point for syncing Netbox data into Infrahub. This schema follows best practices for Infrahub, **but it does not map the Netbox data model one-to-one** since Infrahub may have additional use cases. [Schemahttps://github.com/opsmill/infrahub/blob/stable/models/examples/netbox/netbox.yml](https://github.com/opsmill/infrahub/blob/stable/models/examples/netbox/netbox.yml) ### Installing the example schema[​](#installing-the-example-schema "Direct link to Installing the example schema") To install the example schema into Infrahub, follow these steps: ``` mkdir netbox-sync cd netbox-sync curl -o schema.yml https://raw.githubusercontent.com/opsmill/infrahub/refs/heads/stable/models/examples/netbox/netbox.yml infrahubctl schema load schema.yml ``` ## Configuration[​](#configuration "Direct link to Configuration") `infrahub-sync` allows defining what gets synchronized from a source to a destination. Included in the examples is a config.yml file that matches the example schema. [config.ymlhttps://github.com/opsmill/infrahub-sync/blob/main/examples/netbox\_to\_infrahub/config.yml](https://github.com/opsmill/infrahub-sync/blob/main/examples/netbox_to_infrahub/config.yml) To download the example `config.yml` ``` curl https://raw.githubusercontent.com/opsmill/infrahub-sync/refs/heads/main/examples/netbox_to_infrahub/config.yml > config.yml ``` ### Configuration parameters[​](#configuration-parameters "Direct link to Configuration parameters") To instruct `infrahub-sync` to use the Netbox adapter, set `netbox` in the direction 'name', you want to use for Peering Manager. Below is a snippet from the example config.yml file: * Source example ``` --- name: from-netbox source: name: netbox settings: url: "https://" token: "" verify_ssl: true # Default value ``` ### Schema mapping[​](#schema-mapping "Direct link to Schema mapping") The configuration file allows mapping Netbox data to the Infrahub schema, which has been designed to loosely align with the Netbox data model. Below is an example showing how to: * Set the destination Infrahub model (`BuiltinTag` and `LocationGeneric`) * Map source data from Netbox (`extras.tags`, `dcim.regions`, and `dcim.sites`) * Specify field mappings between Netbox and Infrahub models As you can see in this example, we can map several paths to the same Infrahub Model. ``` - name: BuiltinTag mapping: extras.tags identifiers: ["name"] fields: - name: name mapping: name - name: description mapping: description - name: LocationGeneric mapping: dcim.regions fields: - name: name mapping: slug - name: description mapping: name - name: type static: "Region" - name: tags mapping: tags reference: BuiltinTag - name: LocationGeneric mapping: dcim.sites fields: - name: name mapping: slug - name: description mapping: name - name: type static: "Site" - name: tags mapping: tags reference: BuiltinTag ``` The models available on Netbox can be find in the `/api/schema/swagger-ui` of your instance. [Netbox API Swaggerhttps://demo.netbox.dev/api/schema/swagger-ui](https://demo.netbox.dev/api/schema/swagger-ui) ## Related guides[​](#related-guides "Direct link to Related guides") * [Use NetBox or Nautobot with Infrahub](/sync/using-netbox-or-nautobot-with-infrahub.md) — running NetBox and Infrahub side by side * [Migrate from NetBox or Nautobot](/sync/migrating-from-netbox-or-nautobot.md) — gradual migration into Infrahub * [Schema mapping reference](/sync/reference/schema-mapping.md) — mapping syntax with NetBox examples * [Choose an adapter](/sync/adapters/choosing-an-adapter.md) --- # Observium adapter ## What is Observium?[​](#what-is-observium "Direct link to What is Observium?") Observium is a network monitoring platform that delivers auto-discovery, detailed performance metrics, and rich visualizations to help you effectively manage and optimize your network infrastructure. ## Sync directions supported[​](#sync-directions-supported "Direct link to Sync directions supported") * Observium → Infrahub info Currently, the Observium adapter supports only **one-way synchronization** from Observium to Infrahub. Syncing data back into Observium is not yet supported. Under Construction --- # Peering Manager adapter ## What is Peering Manager?[​](#what-is-peering-manager "Direct link to What is Peering Manager?") Peering Manager lets you effortlessly manage and document your network’s peering sessions and interconnections with pre-configured data models and automation for seamless BGP session management. ## Sync directions supported[​](#sync-directions-supported "Direct link to Sync directions supported") * Peering Manager → Infrahub * Infrahub → Peering Manager ## Schema[​](#schema "Direct link to Schema") Our `infrahub` repository contains an **example schema** that serves as a starting point for syncing Peering Manager data into Infrahub. This schema follows best practices for Infrahub, **but it does not map the Peering Manager data model one-to-one** since Infrahub may have additional use cases. This example is using the "base" demo schema and use an extension for Peering Manager. [Base Schemahttps://github.com/opsmill/infrahub/blob/stable/models/base/](https://github.com/opsmill/infrahub/blob/stable/models/base/) [Peering Manager Extensionhttps://github.com/opsmill/infrahub/blob/stable/models/examples/extension\_peering\_manager.yml](https://github.com/opsmill/infrahub/blob/stable/models/examples/extension_peering_manager.yml) ### Installing the example schema[​](#installing-the-example-schema "Direct link to Installing the example schema") To install the example schema into Infrahub, follow these steps: ``` mkdir peeringmanager-sync cd peeringmanager-sync # Fetch the list of YAML file names from the peeringmanager folder curl -s "https://api.github.com/repos/opsmill/infrahub/contents/models/base?ref=stable" | jq -r '.[].name | select(endswith(".yml"))' | while read file; do curl -O "https://raw.githubusercontent.com/opsmill/infrahub/refs/heads/stable/models/base/$file" done curl -O "https://raw.githubusercontent.com/opsmill/infrahub/refs/heads/stable/models/examples/extension_peering_manager.yml" infrahubctl schema load *.yml ``` ## Configuration[​](#configuration "Direct link to Configuration") `infrahub-sync` allows defining what gets synchronized from a source to a destination. Included in the examples there is two `config.yml` files that matches the example schema. [Source config.ymlhttps://github.com/opsmill/infrahub-sync/blob/main/examples/peering-manager\_to\_infrahub/config.yml](https://github.com/opsmill/infrahub-sync/blob/main/examples/peering-manager_to_infrahub/config.yml) [Destination config.ymlhttps://github.com/opsmill/infrahub-sync/blob/main/examples/infrahub\_to\_peering-manager/config.yml](https://github.com/opsmill/infrahub-sync/blob/main/examples/infrahub_to_peering-manager/config.yml) To download those examples `config.yml` peering-manager to infrahub ``` curl https://raw.githubusercontent.com/opsmill/infrahub-sync/refs/heads/main/examples/peering-manager_to_infrahub/config.yml > config.yml ``` infrahub to peering-manager ``` curl https://raw.githubusercontent.com/opsmill/infrahub-sync/refs/heads/main/examples/infrahub_to_peering-manager/config.yml > config.yml ``` ### Configuration parameters[​](#configuration-parameters "Direct link to Configuration parameters") To instruct `infrahub-sync` to use the Peering Manager adapter, set `peeringmanager` in the direction 'name', you want to use for Peering Manager. The settings dictionary is passed directly to the REST API client for Peering Manager for authentication and connection details. Below is a snippet from the example config.yml file: * Source example * Destination example ``` --- name: from-peering-manager source: name: peeringmanager settings: url: "https://" api_endpoint: "api" # Default value auth_method: "token" # Default value # auth_method: "token" token: "" ``` ``` --- name: to-peering-manager destination: name: peeringmanager settings: url: "https://" api_endpoint: "api" # Default value auth_method: "token" # Default value # auth_method: "token" token: "" verify_ssl: true # Default value ``` info You can pass `params` for the REST API client, used by Peering Manager adapter, in the settings. For example `limit` ### Schema mapping[​](#schema-mapping "Direct link to Schema mapping") The configuration file allows mapping tables from PeeringManager into Infrahub models. Below is an example showing how to: * Set the destination Infrahub model (`InfraBGPCommunity`) * Map source data from Peering Manager (`peering/communities`) * Specify field mappings between Peering Manager and Infrahub models ``` - name: InfraBGPCommunity mapping: peering/communities identifiers: ["name"] fields: - name: name mapping: slug - name: label mapping: name - name: description mapping: description - name: value mapping: value - name: community_type mapping: type ``` The models available on Peering Manager can be find in the /api of your instance. [Peering Manager API Swaggerhttps://demo.peering-manager.net/api/schema/swagger-ui](https://demo.peering-manager.net/api/schema/swagger-ui) ![Peering Manager API Example](/assets/images/peering-manager-api-8c7c5220752c321dbe43bd8aac08f78f.png) --- # PeeringDB adapter ## What is PeeringDB?[​](#what-is-peeringdb "Direct link to What is PeeringDB?") [PeeringDB](https://www.peeringdb.com/) is a free, public, user-maintained database of network peering information. It catalogs autonomous systems, internet exchanges, points of presence, and facilities — the data networks use to find and arrange interconnection with one another. ## Sync directions supported[​](#sync-directions-supported "Direct link to Sync directions supported") * PeeringDB → Infrahub info The PeeringDB adapter supports only **one-way synchronization** from PeeringDB to Infrahub. PeeringDB is a community-maintained reference database; writing data back into it is not supported. Under Construction --- # Prometheus adapter ## What is Prometheus?[​](#what-is-prometheus "Direct link to What is Prometheus?") Prometheus is an open-source monitoring and alerting toolkit that collects and stores metrics as time series data. It is widely used for monitoring infrastructure, applications, and services through its pull-based model and powerful query language (PromQL). ## Requirements[​](#requirements "Direct link to Requirements") This adapter uses the [prometheus-client](https://pypi.org/project/prometheus-client) library for parsing the Prometheus exposition format. ## Sync directions supported[​](#sync-directions-supported "Direct link to Sync directions supported") * Prometheus → Infrahub note Currently, the Prometheus adapter supports only **one-way synchronization** from Prometheus to Infrahub. Writing data back to Prometheus is not supported as Prometheus is a read-only metrics store. ## Operating modes[​](#operating-modes "Direct link to Operating modes") The Prometheus adapter supports two modes of operation: ### Scrape mode (default)[​](#scrape-mode-default "Direct link to Scrape mode (default)") Directly scrapes a Prometheus text exposition endpoint (for example, from `node_exporter`, application metrics endpoints). Best for collecting metrics from individual exporters. ### API mode[​](#api-mode "Direct link to API mode") Queries the Prometheus HTTP API using PromQL. Best for aggregating data from multiple targets or performing complex queries with joins. ## Configuration[​](#configuration "Direct link to Configuration") ### Scrape mode configuration[​](#scrape-mode-configuration "Direct link to Scrape mode configuration") * Scrape Mode * API Mode ``` --- name: from-node-exporter source: name: prometheus settings: mode: scrape url: "http://localhost:9100" endpoint: "/metrics" timeout: 10 # Optional, seconds verify_ssl: true # Optional destination: name: infrahub settings: url: "http://localhost:8000" ``` ``` --- name: from-prometheus-api source: name: prometheus settings: mode: api url: "http://prometheus:9090" endpoint: "/api/v1/query" promql: resources: node_info: 'node_uname_info' node_memory: 'node_memory_MemTotal_bytes' node_cpu: 'count(node_cpu_seconds_total{mode="idle"}) by (instance)' destination: name: infrahub settings: url: "http://localhost:8000" ``` ### Environment variables[​](#environment-variables "Direct link to Environment variables") | Variable | Description | | ---------------------------- | ------------------------------------------------ | | `PROM_URL` or `PROM_ADDRESS` | Prometheus server URL (overrides `settings.url`) | | `PROM_TOKEN` | Bearer token for authentication | | `PROM_USERNAME` | Username for basic authentication | | `PROM_PASSWORD` | Password for basic authentication | ### Settings reference[​](#settings-reference "Direct link to Settings reference") | Setting | Type | Required | Default | Description | | ------------------ | ------- | -------- | -------------------------------------------- | ------------------------------------------------------ | | `mode` | string | No | `scrape` | Operating mode: `scrape` or `api` | | `url` | string | Yes | - | Prometheus server or exporter URL | | `endpoint` | string | No | `/metrics` (scrape) or `/api/v1/query` (API) | Target endpoint | | `timeout` | number | No | 10 | Request timeout in seconds | | `verify_ssl` | boolean | No | true | Verify SSL certificates | | `auth_method` | string | No | `none` | Authentication: `none`, `basic`, or `bearer` | | `username` | string | No | - | Username for basic auth | | `password` | string | No | - | Password for basic auth | | `token` | string | No | - | Token for bearer auth | | `headers` | object | No | - | Additional HTTP headers | | `params` | object | No | - | Query parameters for scrape mode | | `promql.resources` | object | No | - | PromQL queries for API mode (`resource_name`: `query`) | ## Schema mapping[​](#schema-mapping "Direct link to Schema mapping") The Prometheus adapter normalizes metrics into a consistent format for schema mapping: ``` # Each metric sample contains: # - __metric__: metric name # - labels: { label_key: label_value, ... } # - value: numeric value # - timestamp: optional timestamp # - help: metric help text # - type: metric type (counter, gauge, etc.) ``` ### Basic field mapping[​](#basic-field-mapping "Direct link to Basic field mapping") Map fields using dot notation to access metric properties: ``` schema_mapping: - name: VirtualizationVirtualMachine mapping: node_uname_info # Metric name to load from identifiers: ["name"] fields: - name: name mapping: labels.nodename # Access label value - name: os_name mapping: labels.sysname - name: os_kernel mapping: labels.release ``` ### Using the lookup function[​](#using-the-lookup-function "Direct link to Using the lookup function") The `lookup()` function allows you to join data from different metrics: ``` transforms: # Syntax: lookup(metric_name, match_criteria, value_path [, default]) # Get total memory from a different metric - field: mem_total_bytes expression: "{{ lookup('node_memory_MemTotal_bytes', {}, 'value') or 0 }}" # Match by label value - field: mac_address expression: "{{ lookup('node_network_info', {'device': labels.device}, 'labels.address') or '' }}" # With default value - field: status expression: "{{ lookup('node_time_seconds', {}, 'value', 0) }}" ``` ### Lookup function parameters[​](#lookup-function-parameters "Direct link to Lookup function parameters") | Parameter | Description | | ---------------- | ---------------------------------------------------------------------- | | `metric_name` | Name of the metric to look up | | `match_criteria` | Dict of label conditions to match (empty `{}` matches first record) | | `value_path` | Dot-notation path to extract (for example: `value`, `labels.hostname`) | | `default` | Optional default value if lookup fails | ## Example: Node Exporter to Infrahub[​](#example-node-exporter-to-infrahub "Direct link to Example: Node Exporter to Infrahub") [Node Exporter Examplehttps://github.com/opsmill/infrahub-sync/tree/main/examples/prometheus\_to\_infrahub%20(node\_exporter)](https://github.com/opsmill/infrahub-sync/tree/main/examples/prometheus_to_infrahub%20\(node_exporter\)) This example syncs `node_exporter` metrics to create virtual machine records in Infrahub: ``` schema_mapping: - name: VirtualizationVirtualMachine mapping: node_uname_info identifiers: ["name"] fields: - name: name mapping: labels.nodename - name: os_name mapping: labels.sysname - name: mem_total_bytes mapping: mem_total_t - name: status mapping: status_t transforms: # Join memory info from separate metric - field: mem_total_t expression: "{{ lookup('node_memory_MemTotal_bytes', {}, 'value') or 0 }}" # Derive status from exporter availability - field: status_t expression: "{{ 'active' if ((lookup('node_time_seconds', {}, 'value') | float) > 0) else 'unknown' }}" - name: VirtualizationVMNetworkInterface mapping: node_network_mtu_bytes identifiers: ["virtual_machine", "name"] fields: - name: virtual_machine mapping: vm_name - name: name mapping: labels.device - name: mtu mapping: value transforms: # Link to parent VM - field: vm_name expression: "{{ lookup('node_uname_info', {}, 'labels.nodename') or 'unknown' }}" ``` ## Generating the models[​](#generating-the-models "Direct link to Generating the models") Use the generate command to produce Python models from your configuration: ``` uv run infrahub-sync generate --name from-node-exporter --directory examples/ ``` ## Common issues and troubleshooting[​](#common-issues-and-troubleshooting "Direct link to Common issues and troubleshooting") ### Connection errors[​](#connection-errors "Direct link to Connection errors") * Verify the exporter or Prometheus server is running and accessible * Check firewall rules allow connections to the metrics endpoint * For scrape mode, ensure the endpoint returns Prometheus text format ### Empty results[​](#empty-results "Direct link to Empty results") * In scrape mode, verify the `/metrics` endpoint returns data * In API mode, test your PromQL queries directly in Prometheus UI first * Check that `schema_mapping.mapping` matches actual metric names ### Lookup failures[​](#lookup-failures "Direct link to Lookup failures") * Ensure the metric being looked up exists in the scraped data * Verify label names match exactly (case-sensitive) * Use empty dict `{}` for `match_criteria` to get the first matching record * Provide default values for optional lookup operations ### Authentication issues[​](#authentication-issues "Direct link to Authentication issues") * For basic auth, set both `username` and `password` * For bearer auth, set `auth_method: bearer` and provide `token` * Use environment variables (`PROM_USERNAME`, `PROM_PASSWORD`, `PROM_TOKEN`) for credentials ### API mode specific[​](#api-mode-specific "Direct link to API mode specific") * Ensure `promql.resources` is a dict mapping resource names to PromQL queries * Verify queries return vector or scalar results (other types not supported) * Test queries in Prometheus UI before using in configuration --- # Slurp’it adapter ## What is Slurp’it?[​](#what-is-slurpit "Direct link to What is Slurp’it?") Slurp’it lets you easily retrieve and store any data you want from your network in an offline, structured database. Out of the box, all major vendors and templates are already pre-configured. Run the installation wizard and Slurp’it will start mining your network. You can add as many templates as you want. ## Requirements[​](#requirements "Direct link to Requirements") This Adapter uses [Slurp’it SDK](https://pypi.org/project/slurpit_sk). You will need to install it beforehand. ## Sync directions supported[​](#sync-directions-supported "Direct link to Sync directions supported") * Slurp’it → Infrahub info Currently, the Slurp’it adapter supports only **one-way synchronization** from Slurp’it to Infrahub. Syncing data back into Slurp’it is not yet supported. ## Schema[​](#schema "Direct link to Schema") Our `infrahub` repository contains an **example schema** that serves as a starting point for syncing Slurp’it data into Infrahub. This schema follows best practices for Infrahub, **but it does not map the Slurp’it data model one-to-one** since Infrahub may have additional use cases and Slurp’it can be customizable. [Schemahttps://github.com/opsmill/infrahub/blob/stable/models/examples/slurpit/slurpit.yml](https://github.com/opsmill/infrahub/blob/stable/models/examples/slurpit/slurpit.yml) ### Installing the example schema[​](#installing-the-example-schema "Direct link to Installing the example schema") To install the example schema into Infrahub, follow these steps: ``` mkdir slurpit-sync cd slurpit-sync curl -o schema.yml https://raw.githubusercontent.com/opsmill/infrahub/refs/heads/stable/models/examples/slurpit/slurpit.yml infrahubctl schema load schema.yml ``` ## Configuration[​](#configuration "Direct link to Configuration") `infrahub-sync` allows defining what gets synchronized from a source to a destination. Included in the examples is a `config.yml` file that matches the example schema. [config.ymlhttps://github.com/opsmill/infrahub-sync/blob/main/examples/slurpit\_to\_infrahub/config.yml](https://github.com/opsmill/infrahub-sync/blob/main/examples/slurpit_to_infrahub/config.yml) To download the example `config.yml` ``` curl https://raw.githubusercontent.com/opsmill/infrahub-sync/refs/heads/main/examples/slurpit_to_infrahub/config.yml > config.yml ``` ### Configuration parameters[​](#configuration-parameters "Direct link to Configuration parameters") The `source.name` is set to `slurpitsync`, instructing `infrahub-sync` to use the Slurp’it adapter. The settings dictionary is passed directly to the Slurp’it Python SDK for authentication and connection details. Some of these parameters may be found in the [Slurp’it SDK Repository](https://gitlab.com/slurpit.io/slurpit_sdk). Below is a snippet from the example config.yml file: ``` --- name: from-slurpit source: name: slurpitsync settings: url: "" api_key: "" verify_ssl: true # Default value ``` ### Schema mapping[​](#schema-mapping "Direct link to Schema mapping") The configuration file allows mapping Slurp’it data to the Infrahub schema, which has been designed to loosely align with the Slurp’it data model. Below is an example showing how to: * Set the destination Infrahub model (`OrganizationGeneric`) * Map source data from Slurp’it and run a custom function `unique_vendors` * Specify field mappings between Slurp’it and Infrahub models ``` schema_mapping: - name: OrganizationGeneric mapping: unique_vendors fields: - name: name mapping: brand - name: type static: "Vendor" ``` Schema mapping connects Slurp’it fields to Infrahub's Node attributes and relationships. Each field can either map directly to a Slurp’it field or be statically defined. This table highlights the available `mappings` the adapter has to gather and normalize data from Slurp’it | Name | Description | | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `unique_vendors` | The `unique_vendors` function retrieves a list of devices asynchronously, extracts the unique brands (vendors) from the device list, and returns a list of dictionaries, each containing a distinct brand. This ensures that only unique vendor names are included in the output. | | `unique_device_type` | The `unique_device_type` function retrieves a list of devices asynchronously, then extracts unique combinations of brand, `device_type`, and `device_os`. It returns a list of dictionaries where each dictionary contains these three distinct attributes, ensuring only unique device types are included in the output. | | `model`.`function` | This mapping directly relates to the SDK. For example once the SDK client is initiated you can get devices by using `device.get_devices` this will execute the query to gather all the devices. | | `planning_results`.`` | If the mapping starts with `planning_result` a call will be made to the Slurp’it planning API to gather the data for the data that matches the slug. For example: `planning_results.routing-table` will query the routing-table plannings for all the data it has and return it to be used in the mapping. | | `filter_networks` | The `filter_networks` function processes a list of network entries, normalizing the network and mask fields, and filters out unwanted networks based on predefined "ignore" prefixes (such as 0.0.0.0/0 and 127.0.0.0/8). It returns a list of filtered, normalized networks, excluding those with prefixes deemed unnecessary or invalid. | | `filter_interfaces` | The `filter_interfaces` function normalizes IP addresses in a list of interfaces and matches them against precomputed network prefixes from `filtered_networks`. It uses asynchronous tasks to process entries concurrently. Each entry's IP is normalized, and if it matches a known network, additional data (like VRF) is added. The function returns a list of interfaces with normalized addresses, excluding invalid or unmatched entries. | --- # Contributing This guide covers how to set up a development environment for `infrahub-sync` and contribute to the project. For the release runbook, see [RELEASING.md](https://github.com/opsmill/infrahub-sync/blob/main/RELEASING.md) at the repository root — that's maintainer-only. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Python 3.10–3.13 (3.12 recommended) * [uv](https://docs.astral.sh/uv/) for dependency management * Git ## Setting up your development environment[​](#setting-up-your-development-environment "Direct link to Setting up your development environment") ### Clone the repository[​](#clone-the-repository "Direct link to Clone the repository") ``` git clone https://github.com/opsmill/infrahub-sync.git cd infrahub-sync ``` ### Install uv[​](#install-uv "Direct link to Install uv") If you don't have uv installed, you can install it with: ``` curl -LsSf https://astral.sh/uv/install.sh | sh ``` Or see the [uv installation guide](https://docs.astral.sh/uv/getting-started/installation/) for other options. ### Install dependencies[​](#install-dependencies "Direct link to Install dependencies") ``` uv sync --group dev ``` This installs all runtime and development dependencies defined in `pyproject.toml`. ### Verify your setup[​](#verify-your-setup "Direct link to Verify your setup") ``` uv run infrahub-sync --help uv run infrahub-sync list --directory examples/ ``` ## Development workflow[​](#development-workflow "Direct link to Development workflow") Before committing any changes, run the following commands in order: ``` uv run invoke format # Format code with ruff uv run invoke lint # Lint code with ruff and pylint uv run mypy infrahub_sync/ --ignore-missing-imports ``` ### Validate the CLI[​](#validate-the-cli "Direct link to Validate the CLI") After making changes, verify the CLI still works: ``` uv run infrahub-sync --help uv run infrahub-sync list --directory examples/ uv run infrahub-sync generate --name from-netbox --directory examples/ ``` ### Running tests[​](#running-tests "Direct link to Running tests") ``` uv run pytest -q ``` ## Code standards[​](#code-standards "Direct link to Code standards") ### Python style[​](#python-style "Direct link to Python style") * Python 3.10–3.13 compatible * Type hints on new or changed code * Ruff-formatted and lint-clean * Mypy-checked (do not increase existing error count) * Public functions and classes require documentation strings * Raise specific exceptions; avoid broad `except Exception:` ### Line length[​](#line-length "Direct link to Line length") * Maximum line length: 120 characters (configured in `pyproject.toml`) ## Documentation[​](#documentation "Direct link to Documentation") If you make user-facing changes (CLI flags, configuration options, new adapters), update the documentation. ### Generate command-line documentation[​](#generate-command-line-documentation "Direct link to Generate command-line documentation") ``` uv run invoke docs.generate ``` ### Build documentation site[​](#build-documentation-site "Direct link to Build documentation site") First-time setup (requires Node.js): ``` cd docs && npm install ``` Build the site: ``` uv run invoke docs.docusaurus ``` ### Lint markdown files[​](#lint-markdown-files "Direct link to Lint markdown files") ``` npx markdownlint-cli "docs/docs/**/*.{md,mdx}" npx markdownlint-cli --fix "docs/docs/**/*.{md,mdx}" ``` ## Adding a new adapter[​](#adding-a-new-adapter "Direct link to Adding a new adapter") 1. Create `infrahub_sync/adapters/.py` following existing adapter patterns 2. Add connection configuration schema and an example under `examples/` 3. Provide `list` and `diff` pathways before enabling `sync` 4. Document required environment variables and expected error cases 5. Create a documentation page in `docs/docs/adapters/` 6. Add the adapter to the sidebar in `docs/sidebars.ts` ## Invoke tasks[​](#invoke-tasks "Direct link to Invoke tasks") View all available tasks: ``` uv run invoke --list ``` Common tasks: | Task | Description | | -------------------- | ----------------------------- | | `linter.format-ruff` | Format Python code with ruff | | `linter.lint-ruff` | Lint Python code with ruff | | `linter.lint-pylint` | Lint Python code with pylint | | `linter.lint-yaml` | Lint YAML files with yamllint | | `docs.generate` | Generate CLI documentation | | `docs.docusaurus` | Build documentation website | | `format` | Alias for ruff format | | `lint` | Run all linters | --- # Create a sync project A sync project is a directory containing a `config.yml` file that defines one synchronization between two systems. Configure four parts: source and destination, sync order, schema mapping, and sync behavior. A Nautobot → Infrahub example runs through each. ## Define the source and destination[​](#define-the-source-and-destination "Direct link to Define the source and destination") The `source` and `destination` keys identify which adapter to use on each side of the sync and how to connect to each system. Credentials reference environment variables rather than being embedded in the file. ``` --- name: example-sync-task source: name: nautobot settings: url: "https://nautobot.example.com" token: "NAUTOBOT_TOKEN" # This can also be loaded from environment variables destination: name: infrahub settings: url: "https://infrahub.example.com" token: "INFRAHUB_API_TOKEN" # This can also be loaded from environment variables ``` For the full list of adapters and their connection parameters, see [Choose an adapter](/sync/adapters/choosing-an-adapter.md). ## Set the sync order[​](#set-the-sync-order "Direct link to Set the sync order") The `order` key specifies the sequence in which objects should be synchronized. Order matters because some objects depend on others — a device cannot be created until its location, role, and platform already exist in the destination. ``` order: - "InfraDevice" - "InfraInterface" ``` ## Map the schema fields[​](#map-the-schema-fields "Direct link to Map the schema fields") The `schema_mapping` section defines how data is translated from the source's schema into the destination's schema. info * The `name` key in the destination model corresponds to the Infrahub attribute. * The `mapping` key corresponds to the key in the source payload to use. * If `reference` is used, it links to a model that has been synchronized prior to this model. ``` schema_mapping: - name: InfraDevice mapping: "dcim.devices" identifiers: ["name"] fields: - name: "name" mapping: "name" - name: "device_type" mapping: "device_type.display_name" - name: "manufacturer" mapping: "device_type.manufacturer.name" - name: InfraInterface mapping: "dcim.interfaces" identifiers: ["device", "name"] fields: - name: "name" mapping: "name" - name: "interface_type" static: "10gbe" - name: "description" mapping: "description" - name: "device" reference: "InfraDevice" ``` In this example, `device_type` and `manufacturer` are attributes of `InfraDevice`. For destination objects that have relationships to other models, the related models must be synchronized first — see how `InfraInterface` references `InfraDevice` via the `reference` key. For the full mapping syntax — direct mappings, nested attributes, static values, references, identifiers, filters, transforms, and worked examples — see [Schema mapping reference](/sync/reference/schema-mapping.md). ## Tune sync behavior[​](#tune-sync-behavior "Direct link to Tune sync behavior") The `diffsync_flags` key controls how the synchronization handles three scenarios: unmatched objects in the destination, unmatched objects in the source, and modified objects. ``` # Optional: control sync behavior with diffsync flags diffsync_flags: - "SKIP_UNMATCHED_DST" # Skip objects in destination that don't exist in source ``` Understanding diffsync flags Available flags: | Flag | Description | | -------------------- | ------------------------------------------------------------------------------------ | | `SKIP_UNMATCHED_DST` | Skip objects in the destination that don't exist in the source (prevents deletion) | | `SKIP_UNMATCHED_SRC` | Skip objects in the source that don't exist in the destination (prevents creation) | | `SKIP_MODIFIED` | Skip objects that exist in both systems but have different values (prevents updates) | If no flags are specified, `SKIP_UNMATCHED_DST` is used by default — destination objects that don't exist in the source are preserved rather than deleted. For more on customizing sync configuration and troubleshooting, see [Sync instance configuration](/sync/reference/config.md). When the project is configured, run the sync — see [Run a sync](/sync/running-a-sync.md). --- # Use custom CA certificates For sync sources or destinations that use TLS certificates signed by an internal CA — common in enterprise networks — install the CA in the system trust store, then point Python's HTTP client at that store. ## Add the certificate to the system trust store[​](#add-the-certificate-to-the-system-trust-store "Direct link to Add the certificate to the system trust store") Create a subdirectory for the custom CA and copy the root certificate into it: ``` sudo mkdir /usr/local/share/ca-certificates/custom-ca sudo cp customRCA.crt /usr/local/share/ca-certificates/custom-ca/ ``` Then load the certificates into the trusted root store: ``` sudo update-ca-certificates ``` The exact paths above are for Debian/Ubuntu systems. On other distributions the trust store lives elsewhere (`/etc/pki/ca-trust/source/anchors/` on RHEL/Fedora, for example) — adapt accordingly. ## Point Python's HTTP client at the trust store[​](#point-pythons-http-client-at-the-trust-store "Direct link to Point Python's HTTP client at the trust store") Infrahub Sync uses `requests` under the hood, which reads the `REQUESTS_CA_BUNDLE` environment variable to find a custom certificate bundle: ``` export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt ``` Set this in the same shell environment where `infrahub-sync` runs — either exported in the user's shell profile, set in the scheduler that runs the sync, or passed in the container environment if running containerized. --- # Install Infrahub Sync Infrahub Sync is available on [PyPI](https://pypi.org/project/infrahub-sync/) and can be installed using the pip package installer. It is recommended to install the Sync into a virtual environment. ``` python3 -m venv .venv source .venv/bin/activate pip install infrahub-sync ``` --- # Migrate from NetBox or Nautobot Migrate data from NetBox or Nautobot into Infrahub one model at a time. The existing system keeps running throughout, and you decide when — or whether — to retire it. Before you start, install Infrahub Sync if you haven't already — see [Install Infrahub Sync](/sync/installation.md). ## The gradual migration approach[​](#the-gradual-migration-approach "Direct link to The gradual migration approach") A one-shot cutover from NetBox or Nautobot to Infrahub carries real risk: the new system has to be completely ready, all dependent automation has to switch at the same time, and rollback is hard if something surfaces afterward. The gradual approach removes that risk by syncing data into Infrahub model by model, verifying each step, and migrating dependent workflows on your own schedule. The phases: 1. **Prepare the Infrahub schema.** Define the schema in Infrahub that will hold the migrated data. 2. **Build the sync project.** Create a YAML configuration that maps NetBox or Nautobot models and fields to the corresponding Infrahub models and fields. 3. **Seed Infrahub with initial data.** Run the sync to populate Infrahub with the current state of the source system. 4. **Run in side-by-side mode.** Keep both systems active. Sync runs on a recurring schedule to keep Infrahub current with source-system changes. 5. **Migrate workflows.** Update automation, scripts, and dashboards to read from Infrahub instead of the source system. 6. **Retire the legacy system (optional).** Once all dependent workflows read from Infrahub and the legacy system is no longer the source of truth for any data, decommission it — or keep it running for the workflows where it remains the better tool. For long-running side-by-side operation (where the legacy system is not being retired), see [Use NetBox or Nautobot with Infrahub](/sync/using-netbox-or-nautobot-with-infrahub.md). The phases below focus on migration specifically. ## Phase 1 — Prepare the Infrahub schema[​](#phase-1--prepare-the-infrahub-schema "Direct link to Phase 1 — Prepare the Infrahub schema") Infrahub uses a custom schema that you define. Before migrating data, decide on the schema Infrahub will use for the data coming over from NetBox or Nautobot. The Infrahub schema does not have to mirror the source system one-to-one. Migration is an opportunity to model the data the way you need going forward, not the way the previous tool happened to model it. For a concrete starting point, see the example sync projects at `examples/netbox_to_infrahub/` and `examples/nautobot_to_infrahub/` in the [Infrahub Sync repository](https://github.com/opsmill/infrahub-sync). Each references an Infrahub demo schema you can use as a reference when defining your own. Replace its model and field names with the equivalents in your Infrahub schema. ## Phase 2 — Build the sync project[​](#phase-2--build-the-sync-project "Direct link to Phase 2 — Build the sync project") A sync project is a directory containing a `config.yml` that defines the source (NetBox or Nautobot), the destination (Infrahub), and the per-model schema mapping. ### Start from the example[​](#start-from-the-example "Direct link to Start from the example") The Infrahub Sync repository includes working examples at `examples/netbox_to_infrahub/` and `examples/nautobot_to_infrahub/`. Copy the directory matching your source system as the starting point. The examples use the Infrahub demo schema — update model and field names to match the schema you chose in Phase 1. ### Define sync order[​](#define-sync-order "Direct link to Define sync order") Models must be synced in dependency order: anything referenced by another model must be synced first. A typical order: 1. Independent objects: tags, locations, manufacturers, device roles, platforms 2. Rack and site data (depends on locations) 3. Devices (depends on locations, roles, manufacturers, platforms) 4. Interfaces (depends on devices) 5. IP addresses and prefixes (depends on interfaces, VRFs) 6. VLANs and VLAN groups The exact order depends on your schema. See [Sync instance configuration](/sync/reference/config.md) for the syntax of the `order` key. ### Map fields between the source system and Infrahub[​](#map-fields-between-the-source-system-and-infrahub "Direct link to Map fields between the source system and Infrahub") For each model, define how source fields map to Infrahub fields. The schema mapping section of the YAML handles direct field mappings, identifier mappings, references to other models, and static values. For detail on each mapping pattern, see [Schema mapping reference](/sync/reference/schema-mapping.md). ## Phase 3 — Seed Infrahub with initial data[​](#phase-3--seed-infrahub-with-initial-data "Direct link to Phase 3 — Seed Infrahub with initial data") Run `infrahub-sync diff` to verify what the sync will do before applying changes: ``` infrahub-sync diff --name --directory ``` The diff prints the proposed changes to the terminal without modifying Infrahub. Review it model by model: * Are the right models being created in Infrahub? * Are field values being mapped correctly? * Are relationships between models being resolved correctly? * Are there any unexpected creates, updates, or deletes? When the diff looks correct, run the sync: ``` infrahub-sync sync --name --directory ``` The sync applies changes in the order defined by the project's `order` key. Independent models are created first, then dependent models, then models that reference earlier ones. ### Migrate one model at a time[​](#migrate-one-model-at-a-time "Direct link to Migrate one model at a time") For the first sync, enable only the most independent models (tags, locations). Verify the data in Infrahub, then add the next layer of models (racks, devices), run again, and so on. This keeps the diff output reviewable and isolates any mapping issues to the model that caused them. ## Phase 4 — Run in side-by-side mode[​](#phase-4--run-in-side-by-side-mode "Direct link to Phase 4 — Run in side-by-side mode") After the initial seed, schedule the sync to run on a recurring cadence so Infrahub stays current with changes in the source system. The cadence depends on how often the source data changes — hourly, daily, or on-demand are all reasonable. Infrahub Sync has no built-in scheduler. Use cron, CI, Prefect, or another workflow tool — see [Schedule sync runs](/sync/orchestration.md). ### Default behavior preserves Infrahub-only data[​](#default-behavior-preserves-infrahub-only-data "Direct link to Default behavior preserves Infrahub-only data") By default, the `SKIP_UNMATCHED_DST` flag is enabled — Infrahub objects without a corresponding object in the source system are left alone. This is the safe default. Add Infrahub-only data (new schemas, design objects, intent data) without worrying that a sync run will delete it. To change this behavior — for example, removing Infrahub objects when they're deleted in the source system — adjust the `diffsync_flags` in the sync configuration. See [Sync instance configuration](/sync/reference/config.md). ### Decide on conflict policy early[​](#decide-on-conflict-policy-early "Direct link to Decide on conflict policy early") During side-by-side operation, the same model may be edited in both tools. Decide upfront which tool is authoritative for each model and document the decision in the project configuration. If both tools allow edits to the same model, conflicts will eventually occur — and Infrahub Sync's default behavior is for the source system to win. ## Phase 5 — Migrate workflows[​](#phase-5--migrate-workflows "Direct link to Phase 5 — Migrate workflows") As Infrahub becomes a reliable mirror of the source system, update dependent automation to read from Infrahub instead. This is the longest phase of a migration and typically happens workflow by workflow: * Identify each script, pipeline, or dashboard that reads from the source system. * Update it to read from Infrahub (using the Python SDK, infrahubctl, or GraphQL). * Verify the workflow behaves correctly with Infrahub as the source. * Once you're confident, retire the source-reading version of the workflow. For each migrated workflow, you can also stop writing to the source system for the affected data — at which point that data type is fully migrated. ## Phase 6 — Retire the legacy system (optional)[​](#phase-6--retire-the-legacy-system-optional "Direct link to Phase 6 — Retire the legacy system (optional)") Once all dependent workflows read from Infrahub and the legacy system is no longer the source of truth for any data: * **Decommission the legacy system entirely** if it's no longer needed. * **Keep the legacy system running for specific workflows** (rack elevation, cable tracing, or any other capability you prefer there). Reconfigure the sync to keep the legacy system in sync with Infrahub for those workflows, or stop syncing entirely if the data does not need to flow. Many teams reach a stable side-by-side state and stay there indefinitely. See [Use NetBox or Nautobot with Infrahub](/sync/using-netbox-or-nautobot-with-infrahub.md). ## Differences between migrating from NetBox versus Nautobot[​](#differences-between-migrating-from-netbox-versus-nautobot "Direct link to Differences between migrating from NetBox versus Nautobot") The same phases apply for both source systems. The mechanical differences: * Use the [NetBox adapter](/sync/adapters/netbox.md) for NetBox sources and the [Nautobot adapter](/sync/adapters/nautobot.md) for Nautobot sources. * Start from `examples/netbox_to_infrahub/` for NetBox, `examples/nautobot_to_infrahub/` for Nautobot. * Field names and nested structures differ between the two systems — for example, Nautobot status fields are objects while NetBox status fields are strings. The schema mapping work is source-specific, but the overall approach is identical. ## Common issues[​](#common-issues "Direct link to Common issues") * **Sync runs slowly on large datasets.** Initial syncs of large source-system installations can take a while. Sync performance is an active area of development. Recurring sync runs are typically fast — only deltas are applied. * **A model is being created in Infrahub that should not be.** Check the sync project's `order` key and per-model filters — the model may be enabled when it shouldn't be, or a reference from another model may be pulling it in. * **Field values are not mapping correctly.** Check the schema mapping section of the YAML. Common causes: a source field has a different name than expected, or a relationship reference is not resolving because the referenced model was not synced first. * **Custom fields in the source system are not appearing in Infrahub.** NetBox and Nautobot custom fields require explicit mapping in the YAML — they are not synced automatically. ## Related[​](#related "Direct link to Related") * [Use NetBox or Nautobot with Infrahub](/sync/using-netbox-or-nautobot-with-infrahub.md) * [Create a sync project](/sync/creating-a-sync-project.md) * [Run a sync](/sync/running-a-sync.md) * [Schema mapping reference](/sync/reference/schema-mapping.md) * [Schedule sync runs](/sync/orchestration.md) * [NetBox adapter](/sync/adapters/netbox.md) * [Nautobot adapter](/sync/adapters/nautobot.md) --- # Schedule sync runs Infrahub Sync runs as a single CLI command. Schedule it with whatever tooling already runs scheduled jobs in your environment — cron, CI, Prefect, Dagster, or a homegrown runner. ## Common orchestration options[​](#common-orchestration-options "Direct link to Common orchestration options") Any tool that can run a CLI command on a schedule and capture its output works. Pick whichever fits your existing operational model. ### Cron[​](#cron "Direct link to Cron") The most direct option. A cron entry runs `infrahub-sync sync` on a defined schedule. Output and exit codes are captured by your normal cron logging. **When this works well:** * You already operate a cron host for scheduled jobs. * The sync is straightforward — one project, one schedule, no dependencies on other jobs. * Failure handling can be reactive (logs are reviewed when something looks off). **Trade-offs:** No native retry on failure, no visibility beyond logs, no built-in alerting. For mission-critical syncs, wrap the cron entry in a script that handles retries and alerts. ### CI jobs (GitHub Actions, GitLab CI, etc.)[​](#ci-jobs-github-actions-gitlab-ci-etc "Direct link to CI jobs (GitHub Actions, GitLab CI, etc.)") Define the sync as a scheduled workflow in your CI system. CI provides run history, log retention, secret management, and notification on failure. **When this works well:** * You treat infrastructure data movement as part of your software delivery pipeline. * You want run history and structured failure notifications from tooling you already operate. * Sync project configurations live in a git repository alongside other infrastructure code. **Trade-offs:** CI systems are designed for short-lived jobs. For very large or long-running syncs, watch out for job timeout limits. ### Prefect[​](#prefect "Direct link to Prefect") Prefect is a Python-based workflow orchestrator. Wrap a sync run in a Prefect flow to get retry policies, dependency management, observability through the Prefect UI, and the ability to compose syncs with other Python tasks. **When this works well:** * You already use Prefect for other infrastructure or data workflows. * Syncs need to be composed with other tasks — for example, run a sync, then trigger downstream automation. * You want centralized observability across many sync projects. **Trade-offs:** Prefect itself has to be operated. If you don't already run it, this adds operational surface area. ### Dagster, Airflow, and other workflow engines[​](#dagster-airflow-and-other-workflow-engines "Direct link to Dagster, Airflow, and other workflow engines") Any workflow engine that can run a CLI command works the same way as Prefect. Pick based on what you already use and what other workloads share the orchestration platform. ### Event-driven execution[​](#event-driven-execution "Direct link to Event-driven execution") Some sync use cases are better triggered by an event than by a schedule. For example: "when a device is created in Infrahub, sync its details from the inventory system." For event-driven patterns, Infrahub's trigger event system can call `infrahub-sync` directly instead of running on a fixed cadence. Event-driven sync is the exception, not the rule. Most data movement is well-served by a recurring schedule. Use event-driven patterns when the data volume is small, the freshness requirement is high, or the source system is itself event-driven. ## Choosing a cadence[​](#choosing-a-cadence "Direct link to Choosing a cadence") Match the cadence to the rate at which source data changes and your tolerance for staleness: * **Every few minutes** — for high-change-rate sources that need near-real-time freshness. Verify the sync run completes faster than the cadence to avoid overlap. * **Hourly** — a common default for active infrastructure data. * **Daily** — appropriate for slower-moving data or for syncs where the next run can absorb a missed one. * **On demand** — for migrations, audits, or one-off seeding rather than steady-state operation. Each sync run calculates a fresh diff and applies only deltas, so running more often does not multiply work. The constraint is the time the sync takes, not the work it does. ## Observability and failure handling[​](#observability-and-failure-handling "Direct link to Observability and failure handling") Whatever scheduling tool you use, plan for these operational concerns: * **Logging.** Infrahub Sync emits structured logs via `structlog`. Pipe the output to your log aggregator (Splunk, Datadog, Loki, ELK, etc.). * **Failure detection.** The CLI returns a non-zero exit code on failure. Wire the scheduling tool to catch this and alert the appropriate channel. * **Idempotency.** Sync runs are idempotent. If a run fails partway through, re-running it calculates a fresh diff against the current destination state and applies what is still outstanding. Retries on failure are safe. * **Run isolation.** Avoid overlapping runs of the same sync project. Set the cadence longer than the run time, or have the scheduler skip a run if the previous one is still active. * **Sync project versioning.** Store sync project directories in version control. Tag or release configuration changes the same way you handle infrastructure code. ## Running Infrahub Sync in a container[​](#running-infrahub-sync-in-a-container "Direct link to Running Infrahub Sync in a container") To run Infrahub Sync from any orchestrator while keeping a single Python environment under your control, package it as a container. Build the image with `infrahub-sync` installed and the sync project directory included, set the sync command as the entrypoint, and pass credentials via environment variables. Infrahub Sync does not ship a reference container image — examples in the [OpsMill GitHub repository](https://github.com/opsmill/infrahub-sync) show common patterns to adapt. ## What's not in Infrahub Sync (and what to use instead)[​](#whats-not-in-infrahub-sync-and-what-to-use-instead "Direct link to What's not in Infrahub Sync (and what to use instead)") * **Built-in scheduler.** Use one of the options above. * **Sync history dashboard.** The scheduling tool typically provides this — CI run history, Prefect UI, or aggregated logs. * **Built-in retry policies.** Configure retries in the scheduling tool. Runs are idempotent, so retrying is safe. * **Alerting on failure.** Configure alerts in the scheduling tool or log aggregator. ## Related[​](#related "Direct link to Related") * [Run a sync](/sync/running-a-sync.md) * [Create a sync project](/sync/creating-a-sync-project.md) * [CLI reference](/sync/reference/cli.md) * [Migrate from NetBox or Nautobot](/sync/migrating-from-netbox-or-nautobot.md) --- # Cache layout `infrahub-sync diff` and `infrahub-sync apply` persist run state under: ``` .infrahub-sync-cache// ├── .lock # per-pipeline filelock (held during runs) ├── last-successful-rowcounts.json # baseline for the rowcount guardrail └── / ├── A/ # source snapshot │ ├── BuiltinTag.parquet │ └── ... ├── B/ # destination snapshot │ └── ... ├── plan.parquet # the diff plan ├── errors.parquet # only when errors > 0 ├── cursors.json # {A: {Resource: cursor}, B: {Resource: cursor}} ├── schema-sub-hash.txt # invalidates the cache when shape changes └── run.json # status, mode, summary, finished_at ``` Override the root with `INFRAHUB_SYNC_CACHE_DIR=/path/to/shared/cache`. ## plan.parquet[​](#planparquet "Direct link to plan.parquet") One row per change. The columns are: | Column | Description | | ---------------- | ---------------------------------------------------------------------------------------------------------------------- | | `action` | `create`, `update`, or `delete`. Empty for no-op elements (which are skipped during serialization). | | `resource` | Kind name as declared in `schema_mapping[].name`. | | `source_id` | DiffSync `unique_id` of the source-side element. | | `dest_id` | Reserved for the destination's primary key once adapters return it. Empty today. | | `attribute` | Reserved for per-attribute granularity. Empty today (rows are per-element). | | `old_value` | JSON-encoded mapping of `{attr: prior_value}` from `element.get_attrs_diffs()["-"]`. Populated on `update` actions. | | `new_value` | JSON-encoded mapping of `{attr: new_value}` from `element.get_attrs_diffs()["+"]`. Populated on `create` and `update`. | | `owner` | Reserved for sync-identity-based skip logic. Empty today. | | `skip_reason` | Empty unless the engine deliberately skipped a row. | | `conflict_class` | Empty unless the engine flagged a write conflict. | Query with DuckDB without any import step: ``` duckdb -c "SELECT action, resource, source_id, new_value FROM read_parquet('.infrahub-sync-cache/from-netbox//plan.parquet') WHERE action <> 'create' LIMIT 20" ``` ## Commands[​](#commands "Direct link to Commands") * `infrahub-sync diff --name X` — writes side A, side B, and `plan.parquet`. * `infrahub-sync sync --name X` — runs diff then sync; writes the same cache artifacts as `diff` plus updates `last-successful-rowcounts.json` on success. * `infrahub-sync apply --name X --run-id ` — dispatches the cached plan against the destination without re-extracting the source. Refuses if the destination's schema sub-hash has drifted. * `--allow-rowcount-drop` (on `sync`) bypasses the rowcount guardrail when the operator knows the source has legitimately shrunk. * `--continue-on-error` (on `sync`) skips peer relationships missing identifier values rather than aborting; the engine logs each skip so you can review what was dropped. * `--no-concurrent-load` (on `diff` and `sync`) falls back to loading source then destination sequentially. The default (concurrent) is safe with all built-in adapters and roughly halves load wall-clock time on real APIs. --- # `infrahub-sync` Infrahub-sync: synchronize data between infrastructure sources and destinations. **Usage**: ``` $ infrahub-sync [OPTIONS] COMMAND [ARGS]... ``` **Options**: * `--verbosity [quiet|default|verbose]`: Log verbosity level \[default: default] * `-v, --verbose`: Shorthand for --verbosity verbose * `-q, --quiet`: Shorthand for --verbosity quiet * `--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. **Commands**: * `list`: List all available SYNC projects. * `diff`: Calculate and print the differences... * `sync`: Synchronize the data between source and... * `apply`: Apply a previously cached plan against the... * `generate`: Generate all the Python files for a given... ## `infrahub-sync list`[​](#infrahub-sync-list "Direct link to infrahub-sync-list") List all available SYNC projects. **Usage**: ``` $ infrahub-sync list [OPTIONS] ``` **Options**: * `--directory TEXT`: Base directory to search for sync configurations * `--help`: Show this message and exit. ## `infrahub-sync diff`[​](#infrahub-sync-diff "Direct link to infrahub-sync-diff") Calculate and print the differences between the source and the destination systems for a given project. **Usage**: ``` $ infrahub-sync diff [OPTIONS] ``` **Options**: * `--name TEXT`: Name of the sync to use * `--config-file TEXT`: File path to the sync configuration YAML file * `--directory TEXT`: Base directory to search for sync configurations * `--branch TEXT`: Branch to use for the diff. * `--show-progress / --no-show-progress`: Show a progress bar (default: auto-detect terminal) * `--adapter-path TEXT`: Paths to look for adapters. Can be specified multiple times. * `--run-id TEXT`: Re-use a specific cache run id. * `--concurrent-load / --no-concurrent-load`: Load source and destination concurrently. Disable when a custom adapter isn't thread-safe. \[default: concurrent-load] * `--full-extract / --no-full-extract`: Re-extract every resource from scratch (default). Pass --no-full-extract to enable the cursor-driven incremental path on warm runs — see docs/reference/incremental-extraction. \[default: full-extract] * `--help`: Show this message and exit. ## `infrahub-sync sync`[​](#infrahub-sync-sync "Direct link to infrahub-sync-sync") Synchronize the data between source and the destination systems for a given project or configuration file. **Usage**: ``` $ infrahub-sync sync [OPTIONS] ``` **Options**: * `--name TEXT`: Name of the sync to use * `--config-file TEXT`: File path to the sync configuration YAML file * `--directory TEXT`: Base directory to search for sync configurations * `--branch TEXT`: Branch to use for the sync. * `--diff / --no-diff`: Print the differences between the source and the destination before syncing \[default: diff] * `--show-progress / --no-show-progress`: Show a progress bar (default: auto-detect terminal) * `--adapter-path TEXT`: Paths to look for adapters. Can be specified multiple times. * `--parallel / --no-parallel`: Sync tier-by-tier using the auto-computed dep graph. Requires order: to be omitted from config.yml. \[default: parallel] * `--allow-rowcount-drop / --no-allow-rowcount-drop`: Skip the rowcount drop guardrail. Use only when you know the source intentionally shrank. \[default: no-allow-rowcount-drop] * `--continue-on-error / --no-continue-on-error`: Log and skip peer relationships whose identifier values are missing instead of aborting. Useful when source data is partial; review the warnings before relying on the result. \[default: no-continue-on-error] * `--concurrent-load / --no-concurrent-load`: Load source and destination concurrently. Disable when a custom adapter isn't thread-safe. \[default: concurrent-load] * `--full-extract / --no-full-extract`: Re-extract every resource from scratch (default). Pass --no-full-extract to enable the cursor-driven incremental path on warm runs — see docs/reference/incremental-extraction. \[default: full-extract] * `--help`: Show this message and exit. ## `infrahub-sync apply`[​](#infrahub-sync-apply "Direct link to infrahub-sync-apply") Apply a previously cached plan against the destination — no source extraction. **Usage**: ``` $ infrahub-sync apply [OPTIONS] ``` **Options**: * `--name TEXT`: Name of the sync to use * `--config-file TEXT`: File path to the sync configuration YAML file * `--directory TEXT`: Base directory to search for sync configurations * `--run-id TEXT`: Cache run id produced by a previous `diff`. \[required] * `--branch TEXT`: Branch to use for the apply. * `--help`: Show this message and exit. ## `infrahub-sync generate`[​](#infrahub-sync-generate "Direct link to infrahub-sync-generate") Generate all the Python files for a given sync based on the configuration. **Usage**: ``` $ infrahub-sync generate [OPTIONS] ``` **Options**: * `--name TEXT`: Name of the sync to use * `--config-file TEXT`: File path to the sync configuration YAML file * `--directory TEXT`: Base directory to search for sync configurations * `--branch TEXT`: Branch to use for the sync. * `--adapter-path TEXT`: Paths to look for adapters. Can be specified multiple times. * `--help`: Show this message and exit. --- # Sync instance configuration The configuration file allows you to define the resources needs for the sync. The file should be formatted as a Yaml file, have the filename `config.yml`. During the generation phase, the Sync adapters, and models will be generated in the same folder. The following settings can be defined: ## Configuration fields[​](#configuration-fields "Direct link to Configuration fields") Describes the overall synchronization configuration. | Property | Type | Description | Mandatory | | ---------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | --------- | | `name` | string | Unique identifier for the sync instance. | Yes | | `store` | `SyncStore` | Configuration for the optional storage mechanism. | No | | `source` | `SyncAdapter` | Configuration for the source adapter. | Yes | | `destination` | `SyncAdapter` | Configuration for the destination adapter. | Yes | | `order` | List of strings | Order in which objects should be synchronized. Optional — when omitted, infrahub-sync auto-computes tiers from schema\_mapping. | No | | `schema_mapping` | List of `SchemaMappingModel` | Defines how data is mapped from source to destination. | Yes | | `diffsync_flags` | List of `DiffSyncFlags` | Instruct Infrahub Sync how to handle some specific situation without changing the data | No | ### Auto-tiered execution[​](#auto-tiered-execution "Direct link to Auto-tiered execution") `order:` is now optional. When it is omitted, infrahub-sync derives a write-order graph from the `reference:` entries in each `schema_mapping` field and groups kinds into **tiers**: * Tier 0: kinds with no outgoing references. * Tier N: kinds whose references all live in tiers `0..N-1`. The flattened tier order replaces the manual `order:` list. Tiers and any optional edges dropped to break cycles are logged at `INFO` level when `diff` or `sync` runs. `infrahub-sync sync` runs with `--parallel` on by default: the engine narrows the destination's `top_level` to one tier at a time so no tier starts before the previous tier's writes have completed. Pass `--no-parallel` to disable the tier boundary and fall back to the legacy single-pass code path. If you must override the computed order (because it doesn't match an adapter quirk), keep the `order:` list — it always wins, and `--parallel` will warn and fall back to serial when an explicit order is set. ### Sync store[​](#sync-store "Direct link to Sync store") Optional configuration for a storage mechanism used for stateful synchronization. | Property | Type | Description | Mandatory | | -------- | ---------- | ---------------------------------------------------- | --------- | | type | string | Type of the store (for example `redis`). | Yes | | settings | Dictionary | Connection details and other settings for the store. | No | ### Source and Destination[​](#source-and-destination "Direct link to Source and Destination") Configuration for source and destination adapters. | Property | Type | Description | Mandatory | | -------- | ---------- | -------------------------------------------------------------------------------------------------------------------- | --------- | | name | string | Identifier for the adapter. | Yes | | settings | Dictionary | Adapter-specific settings like `url` and `token`. If not provided, values will be loaded from environment variables. | No | ### Schema Mapping[​](#schema-mapping "Direct link to Schema Mapping") Defines the mappings from source to destination schemas. The tables below are the structural field reference. For the mapping syntax in depth — direct mappings, references, identifiers, filters, transforms, and worked NetBox and Nautobot examples — see [Schema mapping reference](/sync/reference/schema-mapping.md). #### Mapping models[​](#mapping-models "Direct link to Mapping models") | Property | Type | Description | Mandatory | | ----------- | ------------------------------ | ------------------------------------------------------- | --------- | | name | string | Infrahub model name to be mapped. | Yes | | mapping | string | The source's schema mapping (API) path. | Yes | | identifiers | List of strings | Fields used to uniquely identify an object. | Yes | | fields | List of SchemaMappingField | Describes individual field mappings or transformations. | Yes | | filters | List of SchemaMappingFilter | Filters you want to apply during the sync. | No | | transforms | List of SchemaMappingTransform | Transforms you want to apply during the sync. | No | ##### Mapping field[​](#mapping-field "Direct link to Mapping field") | Property | Type | Description | Mandatory | | --------- | ------ | ------------------------------------------------------------------------------------------- | --------- | | name | string | Name of the field. | Yes | | mapping | string | How the field is mapped from source to destination. | No | | static | Any | A static value to assign to the field, used if `mapping` is not provided. | No | | reference | string | Reference to another object in the configuration, used if direct mapping is not applicable. | No | ##### Mapping filters[​](#mapping-filters "Direct link to Mapping filters") | Property | Type | Description | Mandatory | | --------- | ------ | ------------------------------------------------------------------------------------- | ---------------------------------------------------------- | | field | string | Name of the source field to filter on. | Yes | | operation | string | Which filters will be applied on the field. Available operations are described below. | No | | value | string | The expected value linked to the operation. | Yes except for both is\_empty and is\_not\_empty operation | For the full list of filter operations (`==`, `!=`, `>`, `<`, `>=`, `<=`, `in`, `not in`, `contains`, `not contains`, `is_empty`, `is_not_empty`, `regex`, `is_ip_within`) with examples, see [Available filter operations](/sync/reference/schema-mapping.md#available-filter-operations). ##### Mapping transforms[​](#mapping-transforms "Direct link to Mapping transforms") | Property | Type | Description | Mandatory | | ---------- | ------ | ------------------------------------- | --------- | | field | string | Name of the target field. | Yes | | expression | string | A Jinja filter compatible expression. | Yes | --- # Incremental Extraction `infrahub-sync` can skip re-extracting unchanged data on warm runs by asking each backend "what changed since the last successful run?". ## Default behavior[​](#default-behavior "Direct link to Default behavior") `infrahub-sync` defaults to `--full-extract`: every run re-extracts every resource from scratch. The cursor-driven warm path is **opt-in** because timestamp filters miss deletes and because a fresh extract is the safer posture for a tool that writes to a downstream system. The cache machinery still runs under `--full-extract` — snapshots and cursor sidecars are written under the run dir so that the opt-in warm path is immediately usable when you switch to `--no-full-extract`. See [Cache layout](/sync/reference/cache-layout.md) for the on-disk shape. ## Enabling the incremental warm path[​](#enabling-the-incremental-warm-path "Direct link to Enabling the incremental warm path") ``` uv run infrahub-sync sync --name from-netbox --directory examples/ --no-full-extract ``` When `--no-full-extract` is set, the engine takes the cursor path **if all** of these hold: 1. A prior run exists under `.infrahub-sync-cache//` with `run.json` status `applied` (or `dry-run`). 2. `schema-sub-hash.txt` from that run matches the current schema mapping + destination schema. Any mapping change forces a full extract. 3. The adapter declares a non-NONE cursor tier for the resource (`cursor_tier_for()` — see adapter docs). 4. The run counter has not hit the configured cadence (default: every 10 runs, configurable via `incremental.full_resync_every` in `config.yml`). If any condition fails the engine falls back to the full extract path for that side / resource. ## When to keep `--full-extract`[​](#when-to-keep---full-extract "Direct link to when-to-keep---full-extract") * Investigating a discrepancy and you suspect cached state. * A backend has had data deleted and you want the delete reflected immediately (timestamp filters do not catch deletes — the cadence knob handles this routinely). ## Supported backends[​](#supported-backends "Direct link to Supported backends") | Adapter | Tier | Notes | | -------------------- | --------- | ---------------------------------- | | NetBox source | TIMESTAMP | `last_updated__gte` | | Nautobot source | TIMESTAMP | `last_updated__gte` | | Infrahub destination | TIMESTAMP | `node_metadata__updated_at__after` | | Others | NONE | Always full extract today | ## Soft deletes[​](#soft-deletes "Direct link to Soft deletes") Timestamp-based incremental misses DELETEs (the deleted row has no `last_updated` to match). The engine forces a full extract every N runs (default 10) to reconcile deletes. Set `incremental.full_resync_every: 1` to disable incremental entirely. A future optimization will add an ID-only sweep (`adapter.list_existing_ids`) so deletes are caught on every warm run — the contract is in place but not yet wired into the engine. --- # Schema mapping reference The schema mapping section of a sync project's `config.yml` defines how data translates between the source system and the destination system. Worked examples for NetBox → Infrahub and Nautobot → Infrahub appear at the end. For the full configuration schema (not just the mapping section), see [Sync instance configuration](/sync/reference/config.md). ## Anatomy of a schema mapping[​](#anatomy-of-a-schema-mapping "Direct link to Anatomy of a schema mapping") Each entry in the `schema_mapping` section maps one model from the source system to one model in the destination system. A mapping entry contains: * **Name** — the destination model name. * **Mapping** — the source model name in the source system. * **Identifiers** — the field(s) that uniquely identify an object in this model. * **Fields** — the per-field mappings between source and destination. A minimal example: ``` schema_mapping: - name: Device mapping: dcim.device identifiers: ["name"] fields: - name: name mapping: name - name: serial mapping: serial - name: status mapping: status.value ``` This says: for each `dcim.device` in the source system, create or update a `Device` object in the destination using the `name` field as the unique identifier, and map the `name`, `serial`, and `status.value` fields directly. ## Field mappings[​](#field-mappings "Direct link to Field mappings") Each entry in the `fields` list defines one field mapping. The most common patterns are direct mapping, nested attribute mapping, static values, and references to other models. ### Direct field mapping[​](#direct-field-mapping "Direct link to Direct field mapping") The source field name maps directly to the destination field name: ``` - name: name mapping: name ``` This is the most common case. The destination field `name` gets the value of the source field `name`. ### Renaming fields[​](#renaming-fields "Direct link to Renaming fields") When the destination field has a different name than the source field, list them separately: ``` - name: device_role mapping: role.slug ``` The destination field `device_role` gets the value of the source's `role.slug`. ### Nested attribute mapping[​](#nested-attribute-mapping "Direct link to Nested attribute mapping") For source data with nested structure, use dotted notation to reach into the nested attribute: ``` - name: status mapping: status.value - name: site mapping: site.slug - name: primary_ip mapping: primary_ip4.address ``` ### Static values[​](#static-values "Direct link to Static values") When a destination field should always be set to the same value regardless of source data, use `static`: ``` - name: source_system static: netbox ``` This is useful for fields that record provenance, type discriminators, or any field where the value is determined by the sync configuration rather than the source data. ### References to other models[​](#references-to-other-models "Direct link to References to other models") When a destination field is a relationship to another model that is also being synced, use `reference` to point to that model: ``` - name: device mapping: device.name reference: Device ``` This says: the value of the source's `device.name` field should resolve to the `Device` object with that name in the destination. The referenced model must be synced before this one — see the `order` key in the project configuration. ## Identifiers[​](#identifiers "Direct link to Identifiers") The `identifiers` list defines which field(s) uniquely identify an object of this model. Identifiers are used to determine whether an object already exists in the destination and should be updated, or whether a new object should be created. ### Single-field identifiers[​](#single-field-identifiers "Direct link to Single-field identifiers") For most models, a single field — typically `name` — is enough: ``` identifiers: ["name"] ``` ### Composite identifiers[​](#composite-identifiers "Direct link to Composite identifiers") Some models need multiple fields to be uniquely identified. For example, an IP address is unique by address plus VRF: ``` identifiers: ["address", "vrf"] ``` All listed fields must be present and unique together. Each identifier field must also be mapped in the `fields` list. ## Filters[​](#filters "Direct link to Filters") Filters control which objects from the source get synced. Apply them at the model level to include or exclude specific objects. ``` - name: Device mapping: dcim.device identifiers: ["name"] filters: - field: status.value operation: "==" value: active fields: - name: name mapping: name ``` This syncs only devices with `status.value == "active"`. Other devices are skipped. ### Available filter operations[​](#available-filter-operations "Direct link to Available filter operations") Infrahub Sync supports the following 14 filter operations: | Operation | Description | | -------------- | -------------------------------------------------------------- | | `==` | Equal to the value | | `!=` | Not equal to the value | | `>` | Greater than (numeric) | | `<` | Less than (numeric) | | `>=` | Greater than or equal (numeric) | | `<=` | Less than or equal (numeric) | | `in` | Value is in a list or string | | `not in` | Value is not in a list or string | | `contains` | Field value contains the given value | | `not contains` | Field value does not contain the given value | | `is_empty` | Field is `None` or empty (no `value` argument needed) | | `is_not_empty` | Field is not `None` and not empty (no `value` argument needed) | | `regex` | Field matches the regular expression in `value` | | `is_ip_within` | Field (an IP address) is within the IP range in `value` | Multiple filters on the same model are combined with AND — an object must match all filters to be included. ## Transforms and custom Jinja filters[​](#transforms-and-custom-jinja-filters "Direct link to Transforms and custom Jinja filters") For cases where a field value needs to be transformed during the mapping — uppercase a string, parse a date, compute a derived value — apply a `transforms` entry to the mapping. The transform takes a `field` (the target field name) and an `expression` (a Jinja-compatible expression evaluated against the source object). ``` - name: Device mapping: dcim.device identifiers: ["name"] fields: - name: name mapping: name transforms: - field: name expression: "{{ name | upper }}" ``` Adapters can also register custom Jinja filters via a `_add_custom_filters` class method on the adapter model. The ACI adapter, for example, includes an `aci_device_name` filter for resolving ACI node IDs to device names. See [Local Adapters](/sync/adapters/local-adapters.md) for the implementation pattern, and [Sync instance configuration](/sync/reference/config.md) for the full `transforms` syntax. ## Validating a mapping[​](#validating-a-mapping "Direct link to Validating a mapping") Validate the mapping with the diff command before applying any changes: ``` infrahub-sync diff --name --directory ``` The diff prints every proposed create, update, and delete. Review it model by model: * Are the field values being mapped correctly? * Are references resolving to the right objects in the destination? * Are filters excluding the right objects? * Are there unexpected creates or deletes? Adjust the mapping and re-run the diff until it matches expectations. Only then run the sync. *** Two common source systems — NetBox and Nautobot — illustrate how the syntax above plays out: ## Common patterns for NetBox → Infrahub[​](#common-patterns-for-netbox--infrahub "Direct link to Common patterns for NetBox → Infrahub") The patterns below cover the most common NetBox → Infrahub mappings. Adapt the destination field names to match your Infrahub schema. ### Sites and locations[​](#sites-and-locations "Direct link to Sites and locations") ``` - name: Location mapping: dcim.site identifiers: ["name"] fields: - name: name mapping: name - name: slug mapping: slug - name: description mapping: description - name: status mapping: status.value ``` ### Devices with role and platform references[​](#devices-with-role-and-platform-references "Direct link to Devices with role and platform references") ``` - name: Device mapping: dcim.device identifiers: ["name"] fields: - name: name mapping: name - name: serial mapping: serial - name: location mapping: site.slug reference: Location - name: role mapping: role.slug reference: DeviceRole - name: platform mapping: platform.slug reference: Platform ``` ### Interfaces with device reference[​](#interfaces-with-device-reference "Direct link to Interfaces with device reference") ``` - name: Interface mapping: dcim.interface identifiers: ["name", "device"] fields: - name: name mapping: name - name: device mapping: device.name reference: Device - name: enabled mapping: enabled - name: type mapping: type.value ``` ### IP addresses with VRF reference[​](#ip-addresses-with-vrf-reference "Direct link to IP addresses with VRF reference") ``` - name: IPAddress mapping: ipam.ip-address identifiers: ["address", "vrf"] fields: - name: address mapping: address - name: vrf mapping: vrf.name reference: VRF - name: status mapping: status.value - name: description mapping: description ``` ### Filtering by tenant or tag[​](#filtering-by-tenant-or-tag "Direct link to Filtering by tenant or tag") ``` - name: Device mapping: dcim.device identifiers: ["name"] filters: - field: tenant.slug operation: "==" value: production fields: - name: name mapping: name ``` ## Common patterns for Nautobot → Infrahub[​](#common-patterns-for-nautobot--infrahub "Direct link to Common patterns for Nautobot → Infrahub") Nautobot's API and model structure are similar to NetBox but with some differences in field names and nested structure. The mapping patterns above largely apply; the main differences: * Nautobot uses `display` instead of `name` in some places. * Nautobot status fields are objects rather than strings — use `status.name` instead of `status.value`. * Custom fields and computed fields appear in different sections of the API response. Start from the `examples/nautobot_to_infrahub/` directory in the Infrahub Sync repository and adapt from there. ## Related[​](#related "Direct link to Related") * [Sync instance configuration](/sync/reference/config.md) * [Create a sync project](/sync/creating-a-sync-project.md) * [NetBox adapter](/sync/adapters/netbox.md) * [Nautobot adapter](/sync/adapters/nautobot.md) --- ## [🗃Infrahub Sync](/sync/release-notes/infrahub-sync.md) [3 items](/sync/release-notes/infrahub-sync.md) --- ## [📄️Release 2.0.0](/sync/release-notes/infrahub-sync/release-2_0_0.md) [Release Number](/sync/release-notes/infrahub-sync/release-2_0_0.md) --- # Release 1.5.6 | Release Number | 1.5.6 | | -------------- | -------------------------------------------------------------------- | | Release Date | February 18th, 2026 | | Tag | [1.5.6](https://github.com/opsmill/infrahub-sync/releases/tag/1.5.6) | ## Fixed[​](#fixed "Direct link to Fixed") * Fixed a race in the Slurp’it adapter by processing interface records sequentially instead of concurrently. Slurp’it sync runs now produce deterministic interface output. ([#115](https://github.com/opsmill/infrahub-sync/pull/115)) --- # Release 1.6.0 | Release Number | 1.6.0 | | -------------- | -------------------------------------------------------------------- | | Release Date | March 26th, 2026 | | Tag | [1.6.0](https://github.com/opsmill/infrahub-sync/releases/tag/1.6.0) | Infrahub Sync 1.6.0 is available. This release updates CLI, engine, and adapter messages to use Python logging instead of direct terminal printing. It also adds verbosity and progress controls for scheduled runs and interactive troubleshooting. ## Main changes[​](#main-changes "Direct link to Main changes") ### Automation-friendly logging[​](#automation-friendly-logging "Direct link to Automation-friendly logging") The package CLI, sync engine, and built-in adapters now emit operational messages through Python logging instead of writing directly to the terminal. A run produces one consistent log stream rather than a mix of printed lines and application logs. The default CLI format remains human-readable. When you embed Infrahub Sync in Python automation, CI, cron, or workflow schedulers, attach your own handlers and formatters to route logs alongside the rest of your pipeline. ### Verbosity and progress controls[​](#verbosity-and-progress-controls "Direct link to Verbosity and progress controls") The CLI now includes global verbosity controls: ``` infrahub-sync --verbosity quiet ... infrahub-sync --verbosity verbose ... infrahub-sync -q ... infrahub-sync -v ... ``` Commands that show progress now support `--show-progress / --no-show-progress`. Progress bars are auto-detected for interactive terminals by default. Use `--no-show-progress` in automation that needs stable, machine-readable output. ## Upgrade notes[​](#upgrade-notes "Direct link to Upgrade notes") * No configuration changes are required. * Review any scripts that parse human-readable output from `list`, `diff`, `sync`, or `generate`. Operational messages now flow through Python logging. * Use `--quiet`, `--verbose`, or `--verbosity` to tune log volume for automation and troubleshooting. * Use `--no-show-progress` in non-interactive environments that require stable log output. ## Full changelog[​](#full-changelog "Direct link to Full changelog") ### Added[​](#added "Direct link to Added") * Added global `--verbosity`, `--verbose`/`-v`, and `--quiet`/`-q` controls. ([#117](https://github.com/opsmill/infrahub-sync/pull/117)) * Added `--show-progress / --no-show-progress` to `diff` and `sync`, with terminal auto-detection by default. ([#117](https://github.com/opsmill/infrahub-sync/pull/117)) ### Changed[​](#changed "Direct link to Changed") * Replaced direct `print()` and Rich console output with Python logging in the package CLI, sync engine, and built-in adapters. ([#117](https://github.com/opsmill/infrahub-sync/pull/117)) * Updated adapter documentation command examples from Poetry to uv. ([#117](https://github.com/opsmill/infrahub-sync/pull/117)) * Updated development guidance for the uv-based workflow and Python 3.13 support. ([#117](https://github.com/opsmill/infrahub-sync/pull/117)) --- # Release 2.0.0 | Release Number | 2.0.0 | | -------------- | -------------------------------------------------------------------- | | Release Date | June 8th, 2026 | | Tag | [2.0.0](https://github.com/opsmill/infrahub-sync/releases/tag/2.0.0) | Infrahub Sync 2.0.0 is available. This release focuses on recurring synchronization at scale. Run large syncs on a schedule, automate them safely, and inspect exactly what changed on every run. The focus of 2.0 is operational reliability: faster incremental syncs, automatic dependency ordering, built-in safety guardrails, and on-disk run artifacts. Together these make it practical to run synchronization as a recurring part of infrastructure operations. ## Release highlights[​](#release-highlights "Direct link to Release highlights") * **Schedule recurring syncs without re-reading entire datasets.** Recurring syncs used to re-read the full source and destination on every run. After the first run, `--no-full-extract` lets supported adapters extract only changed records, so large sync jobs stay fast enough to run on a schedule. See [Incremental syncs for large datasets](#incremental-syncs-for-large-datasets). * **Add and evolve models without maintaining a write order.** Sync ordering used to be maintained manually in `config.yml` and updated on every schema change. It is now derived from your schema mapping automatically. Evolve data models, onboard new systems, and extend integrations without updating sync configuration on every change. See [Write ordering derived from schema mapping](#write-ordering-derived-from-schema-mapping). * **Run synchronization safely in unattended environments.** Source system outages, permission issues, or incomplete datasets no longer risk deleting valid destination data. Built-in guardrails detect unexpected source drops and stop the run, or skip the affected records, before writing to the destination. Run scheduled syncs in CI/CD pipelines and cron jobs without risking accidental data loss from source anomalies. See [Row count guardrails for unattended runs](#row-count-guardrails-for-unattended-runs). * **Inspect, audit, and troubleshoot every sync run.** Every `diff` and `sync` used to be discarded after execution. Both are now saved to disk. Review exactly what changed, investigate unexpected results, and build repeatable operational workflows around sync. See [Per-run artifacts for diff and sync](#per-run-artifacts-for-diff-and-sync). What to expect after upgrading Your existing sync projects keep working without changes, and most run faster, because loading and writing now happen in parallel by default. Three situations need action: * If you use a custom adapter that is not thread-safe, pass `--no-concurrent-load`. * If a sync depends on a specific write order, set an explicit `order` list in `config.yml`. * `diff` and `sync` now write a cache under `.infrahub-sync-cache/`. In scheduled environments, plan to clean it up so per-run artifacts do not accumulate. The [upgrade notes](#upgrade-notes) list every default that changed. ## Main changes[​](#main-changes "Direct link to Main changes") ### Incremental syncs for large datasets[​](#incremental-syncs-for-large-datasets "Direct link to Incremental syncs for large datasets") Large synchronization jobs previously required reading both source and destination systems in full on every run, so execution time grew with the dataset. Infrahub Sync now loads both systems concurrently and, after the first run, can extract only changed records on supported adapters. Run large syncs on a schedule and keep systems in sync as datasets change. The approved benchmark for [#127](https://github.com/opsmill/infrahub-sync/pull/127) used the `nautobot-v2` demo dataset with interfaces omitted and about 14 object kinds: | Scenario | Cold | Warm | | ------------------------------------------- | ------ | ------ | | Baseline, serial, no concurrent load | 463.8s | 154.3s | | `--parallel` with `--concurrent-load` | 437.5s | 132.3s | | `--no-full-extract`, cursor-driven warm run | 434.5s | 6.3s | * Pass `--no-concurrent-load` for custom adapters that are not thread-safe. Concurrent loading is on by default and does not change the sync result. * Enable incremental extraction with `--no-full-extract` to read only changed records after the first run. The default `--full-extract` re-reads every resource on each run. On supported adapters it uses timestamp cursors: NetBox and Nautobot use `last_updated__gte`, and Infrahub uses `node_metadata__updated_at__after`. * Infrahub Sync falls back to a full extract when the schema mapping changes, when no prior successful run exists, when an adapter has no cursor for a resource, or when the full-resync cadence is reached. * Timestamp cursors do not detect deletes. Infrahub Sync forces a full extract every 10 runs by default to reconcile them. See the [incremental extraction reference](/sync/reference/incremental-extraction.md) for details. ### Write ordering derived from schema mapping[​](#write-ordering-derived-from-schema-mapping "Direct link to Write ordering derived from schema mapping") Add and evolve models without updating sync configuration. Synchronization writes objects in dependency order, and that order used to be maintained manually in `config.yml` and updated on every schema change. Infrahub Sync now derives it from the `reference` relationships in your schema mapping. * Infrahub Sync builds the dependency graph from the `reference` relationships in your `schema_mapping`. It groups object kinds into tiers; objects within a tier have no cross-dependencies. * When `order` is omitted, Infrahub Sync writes each tier in full before starting the next, so no object is created before what it references. `--parallel` is on by default and writes objects within a tier concurrently. * Set an explicit `order` list when you need a specific sequence; Infrahub Sync logs a warning and uses it. ### Row count guardrails for unattended runs[​](#row-count-guardrails-for-unattended-runs "Direct link to Row count guardrails for unattended runs") Schedule syncs in CI jobs or cron without manually reviewing each run for source anomalies. An outage, a partial restore, or a permissions change can make a source return far fewer records than it holds. Previously, a sync would apply that drop as a deletion. Infrahub Sync now checks record counts against a baseline before writing and stops the run, or skips the affected records, when the drop exceeds the configured threshold. * Pass `--allow-rowcount-drop` only when the source intentionally shrank. By default, the rowcount check stops a run when a resource drops by more than 50 percent from its last successful baseline. * Pass `--continue-on-error` to log and skip a record that links to an object it cannot find, such as a peer relationship with a missing identifier, instead of aborting the run. Use it for partial source data, then review the warnings before relying on the result. ### Per-run artifacts for diff and sync[​](#per-run-artifacts-for-diff-and-sync "Direct link to Per-run artifacts for diff and sync") Review, audit, and troubleshoot any run after it completes. `diff` used to print to the terminal and keep nothing. Every `diff` and `sync` now saves its snapshots and change plan to disk, so you can inspect what changed and investigate an unexpected result. * Find each run's source snapshot, destination snapshot, `plan.parquet`, run metadata, cursors, and schema sub-hash under `.infrahub-sync-cache///`. The sync-level rowcount baseline is stored at `.infrahub-sync-cache//last-successful-rowcounts.json`. See the [cache layout reference](/sync/reference/cache-layout.md) for the file structure and plan columns. * Query the plan with tools such as DuckDB before the next run, or to investigate an unexpected diff: ``` uv run infrahub-sync diff --name from-netbox --directory examples/ duckdb -c "SELECT action, resource, source_id FROM read_parquet('.infrahub-sync-cache/from-netbox//plan.parquet') LIMIT 20" ``` * Use `apply` (preview) to replay a cached plan without re-extracting the source. In 2.0.0 it requires destination adapter support for cached-row application, so use `sync` for built-in adapter workflows until that support is available. `apply` refuses to run when the current schema mapping and destination schema shape no longer match the cached schema sub-hash. ### Type checking uses ty in invoke lint[​](#type-checking-uses-ty-in-invoke-lint "Direct link to Type checking uses ty in invoke lint") Run `invoke lint` with stricter type checking and no override blocks. The project used mypy with overrides that hid known type errors. Migrating to ty removed those overrides and fixed several latent runtime bugs in the process. * [ty](https://github.com/astral-sh/ty) runs alongside ruff, pylint, and yamllint in `invoke lint`. * The migration also fixed latent runtime failures in CLI narrowing, adapter peer resolution, resource-name handling, and Slurp'it conversion return types. ## Upgrade notes[​](#upgrade-notes "Direct link to Upgrade notes") * `order` is now optional when `schema_mapping` contains enough `reference` information to derive the dependency graph. Keep `order` when you need a configured sequence. * `--parallel`, `--concurrent-load`, and `--full-extract` are enabled by default. Use `--no-concurrent-load` for custom adapters that are not thread-safe. * Incremental extraction is opt-in with `--no-full-extract`. * `.infrahub-sync-cache/` is now part of normal operation and is ignored by Git. * Review automation that parses command output. The new cache workflow adds run identifiers and cache paths to successful `diff` and `sync` runs. * Review retention and cleanup for `.infrahub-sync-cache/` in scheduled environments, because recurring runs now persist snapshots and plans. ## Full changelog[​](#full-changelog "Direct link to Full changelog") ### Added[​](#added "Direct link to Added") * Added automatic sync ordering from `schema_mapping` reference relationships, making `order` optional for configurations with enough dependency information. ([#127](https://github.com/opsmill/infrahub-sync/pull/127)) * Added tier-by-tier sync execution with `--parallel` enabled by default when `order` is omitted. ([#127](https://github.com/opsmill/infrahub-sync/pull/127)) * Added per-run cache artifacts under `.infrahub-sync-cache///`, including source and destination snapshots, run metadata, cursors, and schema-sub-hash data. ([#127](https://github.com/opsmill/infrahub-sync/pull/127)) * Added sync-level rowcount baselines under `.infrahub-sync-cache//last-successful-rowcounts.json`. ([#127](https://github.com/opsmill/infrahub-sync/pull/127)) * Added Parquet diff plans for `diff` and `sync`, so run output can be inspected with external tools. ([#127](https://github.com/opsmill/infrahub-sync/pull/127)) * Added the `infrahub-sync apply` command (preview) as the foundation for replaying cached plans with destination adapters that support cached-row application. ([#127](https://github.com/opsmill/infrahub-sync/pull/127)) * Added cursor-based incremental extraction for NetBox, Nautobot, and Infrahub, enabled with `--no-full-extract`. ([#127](https://github.com/opsmill/infrahub-sync/pull/127)) * Added rowcount guardrails that stop `sync` when a resource count drops by more than 50 percent from the previous successful baseline. ([#127](https://github.com/opsmill/infrahub-sync/pull/127)) * Added `--continue-on-error` to skip peer relationships with missing identifier values while logging warnings. ([#127](https://github.com/opsmill/infrahub-sync/pull/127)) ### Changed[​](#changed "Direct link to Changed") * Enabled concurrent source and destination loading by default for `diff` and `sync`, with `--no-concurrent-load` available for custom adapters that are not thread-safe. ([#127](https://github.com/opsmill/infrahub-sync/pull/127)) * Enabled `--parallel` and `--full-extract` by default for `sync`. ([#127](https://github.com/opsmill/infrahub-sync/pull/127)) * Updated example configurations to omit `order` by default and show how to opt back into a manual sequence. ([#127](https://github.com/opsmill/infrahub-sync/pull/127)) * Replaced mypy with ty in the development and CI linting workflow. ([#126](https://github.com/opsmill/infrahub-sync/pull/126)) ### Fixed[​](#fixed "Direct link to Fixed") * Improved peer identifier handling so missing peer identifier values produce clearer errors, or warnings when `--continue-on-error` is used. ([#127](https://github.com/opsmill/infrahub-sync/pull/127)) * Fixed several type-checker-discovered runtime failure paths in CLI narrowing, resource-name handling, Infrahub peer resolution, and Slurp'it conversion return values. ([#126](https://github.com/opsmill/infrahub-sync/pull/126)) --- # Run a sync Learn how to use Infrahub Sync's commands to calculate differences, synchronize data, and apply previously cached plans against your destination. ![Infrahub-Sync process](/assets/images/infrahub_sync_process.excalidraw-77809c6d9cf8697772543001bc5ad1a8.svg) info Before generating the necessary Python code for your sync adapters and models and synchronizing, you need to create a configuration. To create a new configuration, please refer to [Create a sync project](/sync/creating-a-sync-project.md). ## Listing available sync projects[​](#listing-available-sync-projects "Direct link to Listing available sync projects") ``` infrahub-sync list --directory ``` Prints every sync project found under the given directory along with its source, destination, and on-disk location. Useful as a quick sanity check. ## Calculating differences[​](#calculating-differences "Direct link to Calculating differences") The `diff` command compares the source and destination without writing anything to the destination. It also writes a Parquet **plan** to the local cache so you can review the change set and replay it later with `apply`. ### Command[​](#command "Direct link to Command") ``` infrahub-sync diff --name --directory ``` ### Parameters[​](#parameters "Direct link to Parameters") * `--name` — name of the sync project to diff. * `--directory` — directory holding your sync configuration. * `--branch` — Infrahub branch to diff against (default `main`). * `--show-progress / --no-show-progress` — toggle the per-resource progress bar. * `--run-id` — re-use a specific cache run id; useful when you want to overwrite a previous run's plan in place. * `--concurrent-load / --no-concurrent-load` — load source and destination concurrently (default on). Disable if a custom adapter isn't thread-safe; see [Concurrent loads](#concurrent-loads) below. * `--full-extract / --no-full-extract` — default on; re-extract everything every run. Pass `--no-full-extract` to enable the cursor-driven incremental warm path. See [Incremental extraction](/sync/reference/incremental-extraction.md). Each invocation logs a `Cached run at ` line on success. Note that id — you can hand it to `apply` to dispatch the plan without re-extracting the source. ## Synchronizing data[​](#synchronizing-data "Direct link to Synchronizing data") The `sync` command runs `diff` and immediately applies the changes to the destination. ### Command[​](#command-1 "Direct link to Command") ``` infrahub-sync sync --name --directory ``` ### Parameters[​](#parameters-1 "Direct link to Parameters") * `--name` — name of the sync project to run. * `--directory` — directory holding your sync configuration. * `--branch` — Infrahub branch to sync against (default `main`). * `--diff / --no-diff` — print the diff before syncing (default on). * `--show-progress / --no-show-progress` — progress bar during sync. * `--parallel / --no-parallel` — run tier-by-tier using the auto-computed dep graph (default on). Requires `order:` to be omitted from `config.yml` (see [Auto-tiered execution](/sync/reference/config.md#auto-tiered-execution)). Falls back to serial when `order:` is set; a warning is logged so the no-op is visible. * `--allow-rowcount-drop / --no-allow-rowcount-drop` — bypass the rowcount guardrail. Use only when you know the source intentionally shrank — otherwise sync refuses to proceed when any resource's row count has dropped by more than 50% since the last successful run. * `--continue-on-error / --no-continue-on-error` — log and skip peer relationships whose identifier values are missing, instead of aborting the run. Useful when source data is partial; review the warnings before relying on the result. * `--concurrent-load / --no-concurrent-load` — load source and destination concurrently (default on). See [Concurrent loads](#concurrent-loads) below. * `--full-extract / --no-full-extract` — default on. Pass `--no-full-extract` for the cursor-driven incremental warm path; see [Incremental extraction](/sync/reference/incremental-extraction.md). Example: ``` infrahub-sync sync --name my_project --directory configs --diff --show-progress ``` ### Concurrent loads[​](#concurrent-loads "Direct link to Concurrent loads") Source and destination loads run on a 2-thread pool by default. They hit independent services, write to independent in-memory stores, and write to disjoint cache subdirectories (`A/` vs `B/`), so the two loads are safe to run in parallel — and roughly halve the wall-clock time spent in the load phase on real APIs. Disable with `--no-concurrent-load` if a custom adapter you've plugged in isn't thread-safe (most aren't an issue — the built-in NetBox, Nautobot, and Infrahub adapters are all fine). ### Tier-by-tier execution[​](#tier-by-tier-execution "Direct link to Tier-by-tier execution") When `--parallel` is set and `order:` is omitted, Infrahub Sync derives a write-order graph from the `reference:` entries in your `schema_mapping` and groups kinds into tiers. The engine narrows the destination's working set to one tier at a time, so no tier starts until every kind in the previous tier has finished writing. See [Auto-tiered execution](/sync/reference/config.md#auto-tiered-execution) for the full rationale. ### Rowcount guardrail[​](#rowcount-guardrail "Direct link to Rowcount guardrail") After a successful sync, Infrahub Sync writes a per-resource baseline to `.infrahub-sync-cache//last-successful-rowcounts.json`. The next run reads it; if any resource has shrunk by more than 50% the sync refuses to proceed unless you pass `--allow-rowcount-drop`. The threshold catches accidents like a partially-restored source or a credential that lost permissions, where syncing would otherwise wipe legitimate data on the destination. ## Reviewing and applying a cached plan[​](#reviewing-and-applying-a-cached-plan "Direct link to Reviewing and applying a cached plan") The cache pattern lets you split a run into two steps: produce a plan (`diff`), then apply it (`apply`). This is useful when you want a human approval gate, when the destination is briefly unreachable, or when you want to re-apply the same plan without re-fetching the source. ``` # 1. Dry-run — extracts source + destination, writes plan.parquet infrahub-sync diff --name from-netbox --directory examples/ # Look at the logged line: # INFO | infrahub_sync.cli | Cached run 20260518T1430-abc12345 at .infrahub-sync-cache/from-netbox/20260518T1430-abc12345 # # Inspect the diff or query the parquet directly with DuckDB: # duckdb -c "SELECT * FROM read_parquet('.infrahub-sync-cache/from-netbox/20260518T1430-abc12345/plan.parquet')" # 2. Apply the cached plan — no source extraction infrahub-sync apply --name from-netbox --run-id 20260518T1430-abc12345 --directory examples/ ``` `apply` refuses to proceed if the destination's schema shape has drifted since the plan was built — the cached `schema-sub-hash.txt` must match the freshly-computed hash. When it doesn't, re-run `diff` to rebuild the plan. For the full on-disk layout (per-resource Parquet snapshots, sidecar JSON files, the per-pipeline filelock), see the [Cache layout reference](/sync/reference/cache-layout.md). ## Generating sync adapters and models[​](#generating-sync-adapters-and-models "Direct link to Generating sync adapters and models") `infrahub-sync generate` reads your configuration file and emits Python code for the sync adapters and models used at runtime. ``` infrahub-sync generate --name --directory ``` You typically only run this once per configuration (and after editing `config.yml`). --- # Use NetBox or Nautobot with Infrahub If you're adopting Infrahub but already have an established system of record in NetBox or Nautobot — with accumulated data, custom fields, and automation built around it — run Infrahub side-by-side with the existing tool and use Infrahub Sync to keep both systems holding the same data. This is the most common adoption pattern, whether you plan to eventually consolidate on Infrahub or keep both systems running long-term. ## The side-by-side pattern[​](#the-side-by-side-pattern "Direct link to The side-by-side pattern") Both tools continue to operate. The existing system (NetBox or Nautobot) remains the system of record for some data; Infrahub becomes the system of record for the rest. A sync project moves data between them on a defined cadence, so each system holds the data it owns plus a current view of what the other system owns. The side-by-side pattern fits when: * One team is ready to move to Infrahub, but other teams still depend on the existing system. * Existing automation pipelines, dashboards, or scripts read from NetBox or Nautobot and need time to migrate. * The existing system has features with no direct Infrahub equivalent (rack elevation, native cable tracing), and you want to keep using it for those workflows. * You want to evaluate Infrahub on production data. This pattern is also the typical starting point for a gradual migration. See [Migrate from NetBox or Nautobot](/sync/migrating-from-netbox-or-nautobot.md) for the migration-specific walkthrough. ## Choosing which tool owns which data[​](#choosing-which-tool-owns-which-data "Direct link to Choosing which tool owns which data") Decide which system is authoritative for each type of data before configuring a sync project. Sync behavior on each model is controlled by which side is the source and which is the destination, plus the `diffsync_flags` that govern what each run is allowed to change. Common patterns: * **The existing system is authoritative for everything during initial evaluation.** Sync runs NetBox → Infrahub or Nautobot → Infrahub. Infrahub mirrors the existing system; edits in Infrahub are not propagated back. This is the safest starting configuration. * **Infrahub is authoritative for newly modeled data; the existing system stays authoritative for existing data.** The sync project covers only the models that overlap. New data types (intent, design objects, custom schemas) live in Infrahub and are not synced. * **Each system owns specific models.** For example, the existing system owns rack and cable data; Infrahub owns device intent, IP allocations, and configuration data. Sync moves only the models each system needs to consume. Document the ownership decision in the sync project's configuration comments — anyone reading the YAML later needs to understand the boundary. ## Mapping data between the existing system and Infrahub[​](#mapping-data-between-the-existing-system-and-infrahub "Direct link to Mapping data between the existing system and Infrahub") NetBox and Nautobot each have a fixed schema. Infrahub has a custom schema that you define. There is no universal mapping — the YAML configuration depends on the specific Infrahub schema in use. For a typical mapping: * Start from the example configuration in the [Infrahub Sync repository](https://github.com/opsmill/infrahub-sync) under `examples/netbox_to_infrahub/` or `examples/nautobot_to_infrahub/`. The example references the Infrahub demo schema; replace its model and field names with the equivalents in your Infrahub schema. * For each source model you want to sync (sites, racks, devices, interfaces, IP addresses, VLANs, etc.), find the equivalent Infrahub model and map fields directly. * For field name differences (NetBox's `device_role` versus an Infrahub `role` attribute, or Nautobot's `status.name` versus NetBox's `status.value`), the schema mapping section of the YAML handles the translation. * For relationship differences, use `references` in the YAML to resolve them. See [Sync instance configuration](/sync/reference/config.md) for the full syntax. For a step-by-step guide on building the schema mapping itself, see [Schema mapping reference](/sync/reference/schema-mapping.md). ## Schema differences to be aware of[​](#schema-differences-to-be-aware-of "Direct link to Schema differences to be aware of") Some NetBox and Nautobot features have no direct Infrahub equivalent today. For each: keep using the existing system for that workflow, or model the capability in Infrahub with available workarounds. ### Rack elevation views[​](#rack-elevation-views "Direct link to Rack elevation views") NetBox and Nautobot both include a built-in rack elevation visualization. Infrahub does not have a native rack elevation view, and rack capacity calculation (free U positions, weight, power draw) is not automatic. If rack elevation is important to your workflow, the most direct path is to keep managing racks in the existing system and exclude rack data from the sync. Alternatively, model rack data in Infrahub with a custom schema and handle capacity calculation through a generator or GraphQL queries. ### Cable tracing[​](#cable-tracing "Direct link to Cable tracing") NetBox and Nautobot both include a cable tracing feature that follows connections through patch panels and intermediate devices. Infrahub does not have a single-click cable trace view, but the same data is queryable via Infrahub's graph traversal capabilities. For visualization, build a custom artifact or query with GraphQL from external tools. ### Other differences[​](#other-differences "Direct link to Other differences") For any feature where the existing system has a direct capability and Infrahub does not, document the decision in the sync project configuration and update internal runbooks. Keeping a NetBox or Nautobot workflow alongside Infrahub is a valid long-term choice. ## Operational considerations[​](#operational-considerations "Direct link to Operational considerations") * **Run direction matters for conflict handling.** When syncing NetBox → Infrahub or Nautobot → Infrahub, edits made in Infrahub to synced models will be overwritten on the next sync run unless flags are configured otherwise. Make sure everyone on the team understands which side is the source of truth for each model. * **Use `diff` regularly.** Before applying any sync — and especially during initial setup — run `infrahub-sync diff` to verify what would change. The diff is read-only. * **Schedule syncs on a cadence that matches data change rates.** If the source data changes hourly, sync hourly. If it changes daily, sync daily. Infrahub Sync has no built-in scheduler — use cron, CI, or a workflow tool. See [Schedule sync runs](/sync/orchestration.md). * **Set explicit ownership boundaries.** If both tools allow edits to the same model, conflicts will eventually occur. Decide upfront which tool is authoritative; avoid bi-directional syncing of the same model unless conflict resolution is well-defined. ## When to move toward full migration[​](#when-to-move-toward-full-migration "Direct link to When to move toward full migration") The side-by-side pattern is fine as a long-term steady state, but most teams eventually consolidate on Infrahub once it has proven itself and the gaps that kept the existing system in place have been addressed. Signals that you're ready to migrate fully: * All workflows that depended on NetBox-only or Nautobot-only features have been replaced or moved into Infrahub. * Existing automation pipelines have been updated to read from Infrahub. * Data ownership in Infrahub has been stable for a meaningful period — no surprises in what's there. At that point, see [Migrate from NetBox or Nautobot](/sync/migrating-from-netbox-or-nautobot.md) for the full migration walkthrough. ## Related[​](#related "Direct link to Related") * [Migrate from NetBox or Nautobot](/sync/migrating-from-netbox-or-nautobot.md) * [Create a sync project](/sync/creating-a-sync-project.md) * [Schema mapping reference](/sync/reference/schema-mapping.md) * [NetBox adapter](/sync/adapters/netbox.md) * [Nautobot adapter](/sync/adapters/nautobot.md) --- # VSCode Extension Welcome to the comprehensive documentation for the Infrahub VSCode Extension. This extension transforms Visual Studio Code into a powerful development environment for working with [Infrahub](https://docs.infrahub.app), the open-source infrastructure automation platform. ## What is the Infrahub VSCode extension?[​](#what-is-the-infrahub-vscode-extension "Direct link to What is the Infrahub VSCode extension?") The Infrahub VSCode Extension provides intelligent tooling that connects directly to your Infrahub servers, enabling you to develop infrastructure schemas, execute GraphQL queries, run Jinja2 and Python transforms, and manage branch-based workflows without leaving your IDE. It brings the power of Infrahub's graph database and version control directly into your development workflow. ## Documentation structure[​](#documentation-structure "Direct link to Documentation structure") This documentation is organized following the [Diataxis framework](https://diataxis.fr/) to help you find exactly what you need: ### 🎓 Tutorials[​](#-tutorials "Direct link to 🎓 Tutorials") **Learning-oriented** - Start here if you're new to the extension * [Getting Started with Infrahub VSCode Extension](/vscode/tutorials/getting-started.md) - Complete walkthrough from installation to first query ### 📋 How-to guides[​](#-how-to-guides "Direct link to 📋 How-to guides") **Task-oriented** - Practical steps for specific goals * [How to Configure Multiple Infrahub Servers](/vscode/guides/configure-multiple-servers.md) - Set up dev, staging, and production environments * [How to Execute GraphQL Queries](/vscode/guides/execute-graphql-queries.md) - Run queries with variables and branch selection * [How to Run Transforms and Artifacts](/vscode/guides/running-transforms.md) - Execute Jinja2 and Python transforms with automatic command selection * [How to Manage Branches](/vscode/guides/manage-branches.md) - Create, delete, and work with branches * [How to use Infrahub Snippets](/vscode/guides/snippets.md) - Insert and customize Infrahub YAML and automation snippets ### 💡 Topics[​](#-topics "Direct link to 💡 Topics") **Understanding-oriented** - Explanations and background * [Understanding the Extension Architecture](/vscode/topics/extension-architecture.md) - Deep dive into how the extension works * [Schema Validation and YAML Intelligence](/vscode/topics/schema-validation.md) - How intelligent editing and validation work ### 📚 Reference[​](#-reference "Direct link to 📚 Reference") **Information-oriented** - Technical descriptions and specifications * [Extension Commands and Settings Reference](/vscode/reference/commands-settings.md) - Complete list of commands, settings, and configuration options ## Quick start[​](#quick-start "Direct link to Quick start") If you want to get up and running quickly: 1. **Install the extension** from the VSCode Marketplace 2. **Configure a server** in your VSCode settings 3. **Create a schema** file in your project 4. **Execute a query** to test your connection For detailed instructions, see the [Getting Started tutorial](/vscode/tutorials/getting-started.md). ## Key features at a glance[​](#key-features-at-a-glance "Direct link to Key features at a glance") ### 🔗 Multi-server management[​](#-multi-server-management "Direct link to 🔗 Multi-server management") Connect to multiple Infrahub instances simultaneously, perfect for working across development, staging, and production environments. ### 🌳 Visual tree views[​](#-visual-tree-views "Direct link to 🌳 Visual tree views") Navigate your infrastructure configuration with intuitive tree views for servers, branches, and YAML structures. ### 📝 Intelligent Yaml editing[​](#-intelligent-yaml-editing "Direct link to 📝 Intelligent Yaml editing") Get real-time validation, auto-completion, and go-to-definition support for your Infrahub schemas. ### ✂️ Snippets for automation and YAML objects[​](#️-snippets-for-automation-and-yaml-objects "Direct link to ✂️ Snippets for automation and YAML objects") Quickly scaffold Infrahub transforms, scripts, generators, checks, and YAML objects using built-in VSCode snippets. ### 🚀 GraphQL integration[​](#-graphql-integration "Direct link to 🚀 GraphQL integration") Execute queries directly from VSCode with variable support and formatted results display. ### 🔀 Branch management[​](#-branch-management "Direct link to 🔀 Branch management") Create and manage branches for version-controlled infrastructure changes. ### 📊 Real-time status[​](#-real-time-status "Direct link to 📊 Real-time status") Monitor server connections with live status updates in the VSCode status bar. ## Use cases[​](#use-cases "Direct link to Use cases") The extension is designed for several key workflows: * **Schema Development**: Design and validate infrastructure models with immediate feedback * **Query Testing**: Develop and test GraphQL queries against live data * **Branch Workflows**: Manage infrastructure changes across multiple branches * **Multi-Environment Development**: Work seamlessly across different Infrahub environments * **Infrastructure Automation**: Build automation workflows with validated schemas ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before using the extension, ensure you have: * Visual Studio Code version 1.99.0 or higher * Access to at least one Infrahub server (local or remote) * Basic familiarity with YAML and GraphQL (helpful but not required) ## Getting help[​](#getting-help "Direct link to Getting help") ### Within the extension[​](#within-the-extension "Direct link to Within the extension") * **Problems Panel** (`Ctrl+Shift+M`): View validation errors and warnings * **Output Panel**: Check extension logs for debugging * **Command Palette** (`Ctrl+Shift+P`): Search for Infrahub commands ### External resources[​](#external-resources "Direct link to External resources") * **Infrahub Documentation**: [docs.infrahub.app](https://docs.infrahub.app) * **Extension Repository**: [GitHub.com/opsmill/infrahub-vscode](https://github.com/opsmill/infrahub-vscode) * **Issue Tracker**: [GitHub Issues](https://github.com/opsmill/infrahub-vscode/issues) * **Community Support**: [Infrahub Discord](https://discord.gg/infrahub) ## Common tasks[​](#common-tasks "Direct link to Common tasks") ### First time setup[​](#first-time-setup "Direct link to First time setup") Start with the [Getting Started tutorial](/vscode/tutorials/getting-started.md) for a complete walkthrough. ### Working with multiple environments[​](#working-with-multiple-environments "Direct link to Working with multiple environments") See [How to Configure Multiple Servers](/vscode/guides/configure-multiple-servers.md) for setting up dev, staging, and production. ### Testing queries[​](#testing-queries "Direct link to Testing queries") Learn query execution in [How to Execute GraphQL Queries](/vscode/guides/execute-graphql-queries.md). ### Understanding how it works[​](#understanding-how-it-works "Direct link to Understanding how it works") Explore [Understanding the Extension Architecture](/vscode/topics/extension-architecture.md) for technical details. ## Contributing[​](#contributing "Direct link to Contributing") We welcome contributions to both the extension and its documentation! Visit our [GitHub repository](https://github.com/opsmill/infrahub-vscode) to: * Report bugs or request features * Submit pull requests * Improve documentation ## Version information[​](#version-information "Direct link to Version information") * **Current Extension Version**: 0.0.2 * **Minimum VSCode Version**: 1.99.0 * **Recommended Infrahub Version**: 0.15.0+ ## License[​](#license "Direct link to License") The Infrahub VSCode Extension is part of the Infrahub project. See the [LICENSE](https://github.com/opsmill/infrahub-vscode/blob/main/LICENSE) file for details. *** *Built with ❤️ by the [OpsMill](https://opsmill.com) team and the Infrahub community.* --- # How to Configure Multiple Infrahub Servers If you need to work with multiple Infrahub environments (development, staging, production), this guide shows you how to configure and manage multiple server connections in the VSCode extension. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Infrahub VSCode extension installed * Access to multiple Infrahub servers * API tokens for each server (if authentication is required) ## Step 1: Open extension settings[​](#step-1-open-extension-settings "Direct link to Step 1: Open extension settings") Open your VSCode settings configuration: 1. Press `Ctrl+Shift+P` (or `Cmd+Shift+P` on macOS) 2. Type "Preferences: Open Settings (JSON)" 3. Press Enter ## Step 2: Add multiple server configurations[​](#step-2-add-multiple-server-configurations "Direct link to Step 2: Add multiple server configurations") Add your server configurations to the settings file: ``` { "infrahub-vscode.servers": [ { "name": "Development", "address": "http://localhost:8000", "api_token": "${env:INFRAHUB_DEV_TOKEN}" }, { "name": "Staging", "address": "https://staging.infrahub.example.com", "api_token": "${env:INFRAHUB_STAGING_TOKEN}" }, { "name": "Production", "address": "https://infrahub.example.com", "api_token": "${env:INFRAHUB_PROD_TOKEN}" } ] } ``` tip If you're using a development server with a self-signed certificate, you can disable certificate verification by adding `"tls_insecure": true` to the server configuration. **This should only be used in development environments.** ## Step 3: Set up environment variables[​](#step-3-set-up-environment-variables "Direct link to Step 3: Set up environment variables") For security, use environment variables for API tokens: ### On macOS and Linux[​](#on-macos-and-linux "Direct link to On macOS and Linux") Add to your shell configuration file (`~/.bashrc`, `~/.zshrc`, etc.): ``` export INFRAHUB_DEV_TOKEN="your-dev-token" export INFRAHUB_STAGING_TOKEN="your-staging-token" export INFRAHUB_PROD_TOKEN="your-prod-token" ``` Then reload your shell or restart VSCode. ### On windows[​](#on-windows "Direct link to On windows") Using PowerShell: ``` [System.Environment]::SetEnvironmentVariable('INFRAHUB_DEV_TOKEN', 'your-dev-token', 'User') [System.Environment]::SetEnvironmentVariable('INFRAHUB_STAGING_TOKEN', 'your-staging-token', 'User') [System.Environment]::SetEnvironmentVariable('INFRAHUB_PROD_TOKEN', 'your-prod-token', 'User') ``` Restart VSCode after setting environment variables. ## Step 4: Verify all connections[​](#step-4-verify-all-connections "Direct link to Step 4: Verify all connections") 1. Open the Infrahub Servers tree view in the Activity Bar 2. You should see all three servers listed 3. Expand each server to verify it can retrieve branches 4. Check the status bar - it shows the first server's connection status ## Step 5: Switch between servers for queries[​](#step-5-switch-between-servers-for-queries "Direct link to Step 5: Switch between servers for queries") When executing GraphQL queries: 1. Select the play icon next to the query 2. You'll be prompted to select a server from your configured list 3. Choose the appropriate environment 4. Select the branch to query against ## Advanced configuration[​](#advanced-configuration "Direct link to Advanced configuration") ### Using different schemas per environment[​](#using-different-schemas-per-environment "Direct link to Using different schemas per environment") Configure different schema directories for each environment: ``` { "infrahub-vscode.servers": [ { "name": "Development", "address": "http://localhost:8000", "api_token": "${env:INFRAHUB_DEV_TOKEN}" }, { "name": "Production", "address": "https://infrahub.example.com", "api_token": "${env:INFRAHUB_PROD_TOKEN}" } ] } ``` ### Server groups with naming conventions[​](#server-groups-with-naming-conventions "Direct link to Server groups with naming conventions") Use clear naming conventions to organize servers: ``` { "infrahub-vscode.servers": [ { "name": "[LOCAL] Development", "address": "http://localhost:8000" }, { "name": "[AWS] US-East-1 Staging", "address": "https://staging-us-east-1.infrahub.example.com", "api_token": "${env:INFRAHUB_AWS_STAGING_TOKEN}" }, { "name": "[AWS] US-East-1 Production", "address": "https://us-east-1.infrahub.example.com", "api_token": "${env:INFRAHUB_AWS_PROD_TOKEN}" }, { "name": "[Azure] Europe-West Production", "address": "https://eu-west.infrahub.example.com", "api_token": "${env:INFRAHUB_AZURE_PROD_TOKEN}" } ] } ``` ### Working with self-signed certificates[​](#working-with-self-signed-certificates "Direct link to Working with self-signed certificates") For development or testing environments using self-signed SSL certificates, you can disable certificate verification: ``` { "infrahub-vscode.servers": [ { "name": "Development (Self-Signed)", "address": "https://dev.infrahub.local", "api_token": "${env:INFRAHUB_DEV_TOKEN}", "tls_insecure": true } ] } ``` warning The `tls_insecure` option disables TLS certificate verification, which makes connections vulnerable to man-in-the-middle attacks. **Never use this option for production servers.** Only use it in controlled development environments with self-signed certificates. ## Validation[​](#validation "Direct link to Validation") To verify your configuration is working correctly: 1. **Check Status Bar**: The first server's status appears in the status bar 2. **Tree View**: All servers should appear in the Infrahub Servers tree view 3. **Branch Listing**: Expanding each server should show its branches 4. **Query Execution**: Test a query against each server ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Server not appearing in tree view[​](#server-not-appearing-in-tree-view "Direct link to Server not appearing in tree view") * Save your settings.json file * Reload VSCode window (`Ctrl+R` or `Cmd+R`) * Check for JSON syntax errors in settings ### Authentication failures[​](#authentication-failures "Direct link to Authentication failures") * Verify environment variables are set correctly: ``` echo $INFRAHUB_DEV_TOKEN # macOS/Linux echo %INFRAHUB_DEV_TOKEN% # Windows CMD ``` * Ensure tokens have necessary permissions * Check token expiration dates ### Connection timeouts[​](#connection-timeouts "Direct link to Connection timeouts") * Verify server URLs are accessible from your network * Check for proxy/firewall restrictions * Ensure VPN connection if required ### Certificate verification errors[​](#certificate-verification-errors "Direct link to Certificate verification errors") If you encounter certificate errors with development servers: * For self-signed certificates, add `"tls_insecure": true` to the server configuration * For production servers with certificate issues, fix the certificate rather than disabling verification * Check that your system's CA certificate store is up to date ## Related resources[​](#related-resources "Direct link to Related resources") * [How to Execute GraphQL Queries](/vscode/guides/execute-graphql-queries.md) * [How to Manage Branches](/vscode/guides/manage-branches.md) * [Security Configuration and Best Practices](/vscode/topics/security-configuration.md) * [Extension Commands Reference](/vscode/reference/commands-settings.md) --- # How to Execute GraphQL Queries If you want to test GraphQL queries against your Infrahub instance without leaving VSCode, this guide shows you how to write, organize, and execute queries using the extension. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Infrahub VSCode extension installed and configured * At least one Infrahub server configured * A workspace with `.infrahub.yml` file ## Step 1: Create query files[​](#step-1-create-query-files "Direct link to Step 1: Create query files") Create a directory for your GraphQL queries: ``` mkdir queries ``` Create a GraphQL query file `queries/get_devices.gql`: ``` query GetDevices($status: String = "active") { NetworkDevice(status__value: $status) { edges { node { id hostname { value } model { value } status { value } } } } } ``` ## Step 2: Register queries in .infrahub.yml[​](#step-2-register-queries-in-infrahubyml "Direct link to Step 2: Register queries in .infrahub.yml") Create or update your `.infrahub.yml` file to reference your queries: ``` --- queries: - name: get_active_devices file: queries/get_devices.gql - name: get_interfaces file: queries/get_interfaces.gql - name: topology_report file: queries/topology.gql ``` ## Step 3: Execute a query[​](#step-3-execute-a-query "Direct link to Step 3: Execute a query") 1. Open the Infrahub YAML tree view in the Activity Bar 2. Expand your `.infrahub.yml` file 3. Navigate to the queries section 4. Right-click on "get\_active\_devices" 5. Select **Execute GraphQL Query** 6. Choose your server when prompted 7. Select the branch (usually "main") 8. View results in the new panel that opens ## Step 4: Work with query variables[​](#step-4-work-with-query-variables "Direct link to Step 4: Work with query variables") For queries with variables, create `queries/get_devices_by_location.gql`: ``` query GetDevicesByLocation( $location: String!, $limit: Int = 10, $offset: Int = 0 ) { NetworkDevice( location__name__value: $location, limit: $limit, offset: $offset ) { edges { node { hostname { value } location { node { name { value } } } } } } } ``` When executing this query: 1. You'll be prompted for required variables (`$location`) 2. Optional variables (`$limit`, `$offset`) can be left empty to use defaults 3. Enter values in the input prompts that appear ## Step 5: Query different branches[​](#step-5-query-different-branches "Direct link to Step 5: Query different branches") To execute queries against specific branches: 1. Right-click on your query in the tree view 2. Select **Execute GraphQL Query** 3. Choose your server 4. You'll see a list of available branches 5. Select the branch you want to query 6. The query executes against that branch's data ## Step 6: Complex query examples[​](#step-6-complex-query-examples "Direct link to Step 6: Complex query examples") ### Query with nested relationships[​](#query-with-nested-relationships "Direct link to Query with nested relationships") ``` query GetDeviceDetails($device_id: String!) { NetworkDevice(id: $device_id) { edges { node { hostname { value } interfaces { edges { node { name { value } ip_addresses { edges { node { address { value } prefix { node { prefix { value } } } } } } } } } } } } } ``` ### Mutation example[​](#mutation-example "Direct link to Mutation example") ``` mutation CreateDevice( $hostname: String!, $model: String!, $location_id: String! ) { NetworkDeviceCreate( data: { hostname: { value: $hostname } model: { value: $model } location: { id: $location_id } } ) { ok object { id hostname { value } } } } ``` ## Step 7: Organize queries by category[​](#step-7-organize-queries-by-category "Direct link to Step 7: Organize queries by category") Structure your `.infrahub.yml` for better organization: ``` --- queries: # Inventory Queries - name: inventory_all_devices file: queries/inventory/all_devices.gql - name: inventory_by_location file: queries/inventory/by_location.gql # Network Topology - name: topology_layer2 file: queries/topology/layer2.gql - name: topology_layer3 file: queries/topology/layer3.gql # Reporting - name: report_capacity file: queries/reports/capacity.gql - name: report_compliance file: queries/reports/compliance.gql ``` ## Validation[​](#validation "Direct link to Validation") To verify your queries are working: 1. **Syntax Check**: The extension validates GraphQL syntax 2. **Test Execution**: Run each query with sample variables 3. **Branch Testing**: Execute against different branches 4. **Result Verification**: Check the output panel for expected data ## Advanced usage[​](#advanced-usage "Direct link to Advanced usage") ### Using fragments[​](#using-fragments "Direct link to Using fragments") Create reusable fragments in `queries/fragments.gql`: ``` fragment DeviceBasicInfo on NetworkDevice { id hostname { value } model { value } status { value } } query GetDevicesWithFragment { NetworkDevice { edges { node { ...DeviceBasicInfo location { node { name { value } } } } } } } ``` ### Batch operations[​](#batch-operations "Direct link to Batch operations") For multiple related queries, create a batch file: ``` query BatchDeviceQuery( $location: String!, $status: String = "active" ) { devices: NetworkDevice( location__name__value: $location, status__value: $status ) { count edges { node { hostname { value } } } } interfaces: NetworkInterface( device__location__name__value: $location ) { count } locations: LocationSite( name__value: $location ) { edges { node { name { value } } } } } ``` ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Query execution fails[​](#query-execution-fails "Direct link to Query execution fails") * Verify the GraphQL syntax is correct * Check that all required variables are provided * Ensure the schema types exist in your Infrahub instance * Verify you have permission to access the queried data ### Variables not prompting[​](#variables-not-prompting "Direct link to Variables not prompting") * Check variable definitions in your query * Required variables use `!` (for example, `$location: String!`) * Optional variables have defaults (for example, `$limit: Int = 10`) ### No results returned[​](#no-results-returned "Direct link to No results returned") * Verify data exists on the selected branch * Check filters in your query * Ensure proper permissions for the data ## Related resources[​](#related-resources "Direct link to Related resources") * [How to Manage Branches](/vscode/guides/manage-branches.md) * [Understanding Schema Validation](/vscode/topics/schema-validation.md) * [Extension Commands Reference](/vscode/reference/commands-settings.md) --- # How to Manage Branches If you need to work with Infrahub's branch-based version control system, this guide shows you how to create, delete, and manage branches using the VSCode extension. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Infrahub VSCode extension installed * At least one configured Infrahub server * Appropriate permissions to create/delete branches on your server ## Step 1: View existing branches[​](#step-1-view-existing-branches "Direct link to Step 1: View existing branches") To see all branches on a server: 1. Open the Infrahub icon in the Activity Bar 2. Expand the "Infrahub Servers" tree view 3. Click on your server to expand it 4. You'll see all branches listed with: * Branch name * "(default)" marker for the main branch * Branch metadata when available ## Step 2: Create a new branch[​](#step-2-create-a-new-branch "Direct link to Step 2: Create a new branch") ### From the tree view[​](#from-the-tree-view "Direct link to From the tree view") 1. Right-click on your server in the Infrahub Servers view 2. Select **New Branch** from the context menu 3. Enter the branch name (for example, "feature-network-update") 4. Optionally add a description 5. Press Enter to create the branch ### Naming conventions[​](#naming-conventions "Direct link to Naming conventions") Follow these branch naming patterns for clarity: * `feature-[description]` - New features * `fix-[issue-number]` - Bug fixes * `update-[component]` - Updates to existing components * `test-[scenario]` - Testing branches * `dev-[username]` - Personal development branches Example: `feature-datacenter-schema` ## Step 3: Delete a branch[​](#step-3-delete-a-branch "Direct link to Step 3: Delete a branch") To remove a branch you no longer need: 1. Expand your server in the tree view 2. Right-click on the branch to delete 3. Select **Delete Branch** 4. Confirm the deletion when prompted > **Warning**: Branch deletion is permanent. Ensure you've merged or saved any important changes before deleting. ## Step 4: Work with branch-specific data[​](#step-4-work-with-branch-specific-data "Direct link to Step 4: Work with branch-specific data") ### Execute queries on specific branches[​](#execute-queries-on-specific-branches "Direct link to Execute queries on specific branches") When running GraphQL queries: 1. Right-click on a query in the Infrahub YAML view 2. Select **Execute GraphQL Query** 3. Choose your server 4. Select the target branch from the list 5. The query runs against that branch's data ### Compare data across branches[​](#compare-data-across-branches "Direct link to Compare data across branches") To compare data between branches, execute the same query on different branches: ``` query BranchComparison { NetworkDevice { count edges { node { hostname { value } _updated_at } } } } ``` Run this query on: 1. Main branch - baseline data 2. Feature branch - modified data 3. Compare the results to see differences ## Step 5: Branch workflow examples[​](#step-5-branch-workflow-examples "Direct link to Step 5: Branch workflow examples") ### Feature development workflow[​](#feature-development-workflow "Direct link to Feature development workflow") 1. **Create Feature Branch** ``` Name: feature-add-firewall-schema Description: Adding firewall device schema and relationships ``` 2. **Develop and Test** * Modify schemas * Test queries against the branch * Validate changes 3. **Merge Process** * Review changes * Merge via Infrahub UI or API * Delete feature branch after merge ### Hotfix workflow[​](#hotfix-workflow "Direct link to Hotfix workflow") 1. **Create Hotfix Branch** ``` Name: fix-device-status-issue Description: Urgent fix for device status validation ``` 2. **Apply Fix** * Make necessary corrections * Test on hotfix branch 3. **Deploy** * Merge to main * Verify fix * Clean up branch ## Step 6: Advanced branch management[​](#step-6-advanced-branch-management "Direct link to Step 6: Advanced branch management") ### Working with long-lived branches[​](#working-with-long-lived-branches "Direct link to Working with long-lived branches") For branches that persist over time: ``` # Document branch purposes in your project # branches.yml branches: - name: develop purpose: Integration branch for ongoing development merge_target: main - name: staging purpose: Pre-production testing merge_target: main - name: feature-q4-updates purpose: Q4 feature development merge_target: develop ``` ### Branch metadata tracking[​](#branch-metadata-tracking "Direct link to Branch metadata tracking") Track branch information in your queries: ``` query BranchInfo { Branch { edges { node { name description created_at branched_from is_default has_schema_changes } } } } ``` ## Validation[​](#validation "Direct link to Validation") To ensure branch operations are working: 1. **Creation Verification**: New branch appears in tree view immediately 2. **Query Testing**: Execute a query against the new branch 3. **Deletion Confirmation**: Deleted branch disappears from tree view 4. **Refresh Check**: Tree view updates every 10 seconds automatically ## Best practices[​](#best-practices "Direct link to Best practices") ### Branch lifecycle[​](#branch-lifecycle "Direct link to Branch lifecycle") 1. **Create** with descriptive names 2. **Develop** in isolation 3. **Test** thoroughly on the branch 4. **Review** changes before merging 5. **Merge** when ready 6. **Delete** after successful merge ### Naming strategy[​](#naming-strategy "Direct link to Naming strategy") Use consistent prefixes: * `feature-` for new functionality * `fix-` for bug fixes * `update-` for updates * `test-` for experiments * `release-` for release preparation ### Documentation[​](#documentation "Direct link to Documentation") Document active branches: ``` ## Active Branches ### feature-network-redesign - **Created**: 2024-01-15 - **Owner**: Network Team - **Purpose**: Redesigning network schema for multi-vendor support - **Target Merge**: 2024-02-01 ### fix-validation-error - **Created**: 2024-01-20 - **Owner**: DevOps - **Purpose**: Fix schema validation for IP addresses - **Target Merge**: ASAP ``` ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Branch creation fails[​](#branch-creation-fails "Direct link to Branch creation fails") * Check server permissions * Verify branch name doesn't already exist * Ensure valid characters in branch name (alphanumeric, hyphens, underscores) ### Branch not appearing[​](#branch-not-appearing "Direct link to Branch not appearing") * Wait 10 seconds for automatic refresh * Manually reload VSCode window if needed * Check server connectivity ### Cannot delete branch[​](#cannot-delete-branch "Direct link to Cannot delete branch") * Verify you have deletion permissions * Ensure branch isn't protected * Check if branch is the default branch (cannot be deleted) ## Related resources[​](#related-resources "Direct link to Related resources") * [How to Execute GraphQL Queries](/vscode/guides/execute-graphql-queries.md) * [Understanding the Extension Architecture](/vscode/topics/extension-architecture.md) * [Extension Commands Reference](/vscode/reference/commands-settings.md) --- # How to Run Transforms and Artifacts This guide shows you how to execute Infrahub transforms (both Jinja2 and Python) directly from VSCode. The extension automatically detects transform types and uses the appropriate `infrahubctl` command for execution. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Infrahub VSCode extension installed and configured * At least one Infrahub server configured * A workspace with `.infrahub.yml` file containing transforms and artifact definitions * `infrahubctl` CLI tool installed and available in your system PATH ## Understanding Transform Types[​](#understanding-transform-types "Direct link to Understanding Transform Types") The extension supports two types of transforms: * **Jinja2 Transforms**: Template-based transforms using Jinja2 syntax (executed with `infrahubctl render`) * **Python Transforms**: Code-based transforms using Python classes (executed with `infrahubctl transform`) ## Step 1: Configure transforms in .infrahub.yml[​](#step-1-configure-transforms-in-infrahubyml "Direct link to Step 1: Configure transforms in .infrahub.yml") ### Jinja2 Transforms[​](#jinja2-transforms "Direct link to Jinja2 Transforms") Define Jinja2 transforms in your `.infrahub.yml`: ``` jinja2_transforms: - name: topology_clab description: Template to generate a containerlab topology query: topology_simulator template_path: templates/clab_topology.j2 ``` ### Python Transforms[​](#python-transforms "Direct link to Python Transforms") Define Python transforms in your `.infrahub.yml`: ``` python_transforms: - name: leaf class_name: Leaf file_path: transforms/leaf.py - name: spine class_name: Spine file_path: transforms/spine.py - name: edge class_name: Edge file_path: transforms/edge.py ``` ### Artifact Definitions[​](#artifact-definitions "Direct link to Artifact Definitions") Define artifact definitions that reference your transforms: ``` artifact_definitions: - name: leaf_config artifact_name: leaf content_type: text/plain targets: leafs transformation: leaf # References python_transforms parameters: device: name__value - name: Containerlab Topology artifact_name: containerlab-topology content_type: text/plain targets: topologies_clab transformation: topology_clab # References jinja2_transforms parameters: name: name__value ``` ## Step 2: Execute transforms from VSCode[​](#step-2-execute-transforms-from-vscode "Direct link to Step 2: Execute transforms from VSCode") ### Method 1: From Artifact Definitions[​](#method-1-from-artifact-definitions "Direct link to Method 1: From Artifact Definitions") 1. Open the **Infrahub YAML** tree view in VSCode 2. Expand your `.infrahub.yml` file 3. Navigate to **artifact\_definitions** 4. You'll see each artifact with its transform type displayed in parentheses: * `leaf_config (python)` * `Containerlab Topology (jinja)` 5. Click the play icon next to the desired artifact ### Method 2: From Transform Definitions[​](#method-2-from-transform-definitions "Direct link to Method 2: From Transform Definitions") 1. In the **Infrahub YAML** tree view, navigate to: * **jinja2\_transforms** for Jinja2 templates, or * **python\_transforms** for Python transforms 2. Select the play icon to run the transform directly ### Transform Execution Process[​](#transform-execution-process "Direct link to Transform Execution Process") When you run a transform, the extension will: 1. **Auto-detect transform type**: The extension automatically determines whether to use `infrahubctl render` (Jinja2) or `infrahubctl transform` (Python) 2. **Prompt for branch selection**: Choose which Infrahub branch to execute against 3. **Collect transform variables**: Enter any required variables in `key=value` format: ``` site=nyc device=router01 environment=production ``` 4. **Execute the appropriate command**: * For Jinja2: `infrahubctl render topology_clab site=nyc --branch main` * For Python: `infrahubctl transform leaf device=router01 --branch main` ## Step 3: Working with transform variables[​](#step-3-working-with-transform-variables "Direct link to Step 3: Working with transform variables") ### Adding Variables[​](#adding-variables "Direct link to Adding Variables") When prompted for variables: 1. Enter each variable in `key=value` format 2. Press Enter to add another variable 3. Leave empty and press Enter to finish ### Variable Examples[​](#variable-examples "Direct link to Variable Examples") ``` # Network configuration site=atl01 rack=A12 vlan=100 # Device specifics device=spine01 role=spine asn=65001 # Environment settings environment=production region=us-east ``` ### Variable Validation[​](#variable-validation "Direct link to Variable Validation") The extension validates variable format: * ✅ `device=router01` (valid) * ✅ `site=nyc` (valid) * ❌ `device=` (invalid - empty value) * ❌ `=router01` (invalid - empty key) * ❌ `devicerouter01` (invalid - missing =) ## Step 4: Understanding command execution[​](#step-4-understanding-command-execution "Direct link to Step 4: Understanding command execution") ### Automatic Command Selection[​](#automatic-command-selection "Direct link to Automatic Command Selection") The extension intelligently chooses the correct `infrahubctl` command: | Transform Type | Command Used | Example | | -------------- | ----------------------- | --------------------------------------------------------- | | Jinja2 | `infrahubctl render` | `infrahubctl render topology_clab --branch main` | | Python | `infrahubctl transform` | `infrahubctl transform leaf device=spine01 --branch main` | ### Transform Type Detection[​](#transform-type-detection "Direct link to Transform Type Detection") The extension determines transform types by: 1. **For artifact definitions**: Looking up the `transformation` field in both `jinja2_transforms` and `python_transforms` sections 2. **For direct transforms**: Using the section they're defined in (`jinja2_transforms` vs `python_transforms`) ### Terminal Integration[​](#terminal-integration "Direct link to Terminal Integration") Commands execute in the VSCode integrated terminal, allowing you to: * See real-time output * Monitor progress * Debug any errors * Access command history ## Step 5: Example workflow[​](#step-5-example-workflow "Direct link to Step 5: Example workflow") Here's a complete example of setting up and running transforms: ### 1. Create directory structure[​](#1-create-directory-structure "Direct link to 1. Create directory structure") ``` mkdir -p transforms templates ``` ### 2. Define transforms in .infrahub.yml[​](#2-define-transforms-in-infrahubyml "Direct link to 2. Define transforms in .infrahub.yml") ``` --- jinja2_transforms: - name: device_config description: Generate device configuration query: device_query template_path: templates/device.j2 python_transforms: - name: topology_builder class_name: TopologyBuilder file_path: transforms/topology.py artifact_definitions: - name: router_config artifact_name: router-config content_type: text/plain targets: routers transformation: device_config # Jinja2 transform parameters: device: name__value - name: network_topology artifact_name: topology content_type: application/json targets: networks transformation: topology_builder # Python transform parameters: network: name__value ``` ### 3. Execute from VSCode[​](#3-execute-from-vscode "Direct link to 3. Execute from VSCode") 1. Navigate to **artifact\_definitions** → **router\_config (jinja)** 2) Click play icon 3) Select branch: `main` 4) Add variables: `device=router01`, `site=nyc` 5) Command executes: `infrahubctl render device_config device=router01 site=nyc --branch main` ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Common Issues[​](#common-issues "Direct link to Common Issues") **"Transform type not determined"** * Verify the `transformation` field matches a name in `jinja2_transforms` or `python_transforms` * Check YAML syntax and indentation **"infrahubctl command not found"** * Ensure `infrahubctl` is installed: `pip install infrahubctl` * Verify it's in your system PATH * Restart VSCode after installation **"No transform selected"** * Ensure your artifact definition has a valid `transformation` field * Verify the referenced transform exists in your configuration **Transform execution fails** * Check the terminal output for specific error messages * Verify branch exists and is accessible * Ensure required variables are provided * Check transform syntax (Jinja2 templates or Python code) ### Best Practices[​](#best-practices "Direct link to Best Practices") 1. **Organize transforms logically**: Group related transforms in clearly named sections 2. **Use descriptive names**: Transform names should clearly indicate their purpose 3. **Document variables**: Add comments in your `.infrahub.yml` describing expected variables 4. **Test incrementally**: Start with simple transforms and add complexity gradually 5. **Version control**: Keep transform files and `.infrahub.yml` in version control ## Next Steps[​](#next-steps "Direct link to Next Steps") * **[Managing Branches](/vscode/guides/manage-branches.md)**: Learn how to work with different Infrahub branches * **[Configure Multiple Servers](/vscode/guides/configure-multiple-servers.md)**: Set up development, staging, and production environments * **[Extension Commands Reference](/vscode/reference/commands-settings.md)**: Complete list of available commands ## Further Resources[​](#further-resources "Direct link to Further Resources") * [Infrahub Transforms Documentation](https://docs.infrahub.app/topics/transformation) * [infrahubctl CLI Reference](https://docs.infrahub.app/reference/infrahub-cli) * [Jinja2 Template Documentation](https://jinja.palletsprojects.com/) --- # How to use Infrahub snippets in VSCode This guide shows you how to quickly insert and customize Infrahub YAML objects and automation scripts using built-in snippets in Visual Studio Code. By following these steps, you’ll save time and reduce errors when authoring Infrahub resources. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Infrahub VSCode extension installed * Workspace containing YAML or Python files * Basic familiarity with editing files in VSCode ## Steps[​](#steps "Direct link to Steps") ### 1. Insert an Infrahub object snippet[​](#1-insert-an-infrahub-object-snippet "Direct link to 1. Insert an Infrahub object snippet") 1. Open any `.yaml` or `.yml` file in your project. 2. Type `infrahubobject` and select the snippet from the suggestion list. 3. The following template will be inserted: ``` --- apiVersion: infrahub.app/v1 kind: Object spec: kind: ${1:kind} data: - name: "${2:name}" ``` 4. Replace the placeholder values (`kind`, `name`) with your desired values. ### 2. Insert a Infrahub Python Snippet[​](#2-insert-a-infrahub-python-snippet "Direct link to 2. Insert a Infrahub Python Snippet") 1. Open a `.py` file in your workspace. 2. Type one of the following snippet prefixes and select it from the suggestion list: * `infrahubtransform` for a transform * `infrahubscript` for a script * `infrahubgenerator` for a generator * `infrahubcheck` for a check 3. Fill in the placeholders as needed to scaffold your automation script. ## Related resources[​](#related-resources "Direct link to Related resources") * [How to Configure Multiple Servers](/vscode/guides/configure-multiple-servers.md) * [How to Execute GraphQL Queries](/vscode/guides/execute-graphql-queries.md) --- # How to Visualize Your Schema If you want to explore and understand the structure of your Infrahub schema, this guide shows you how to use the Schema Visualizer to view nodes, relationships, and attributes in an interactive graph. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Infrahub VSCode extension installed and configured * At least one Infrahub server configured and online * Network access to your Infrahub instance ## Step 1: Open the Schema Visualizer[​](#step-1-open-the-schema-visualizer "Direct link to Step 1: Open the Schema Visualizer") 1. Open the **Infrahub Servers** panel in the Activity Bar 2. Locate your connected server (indicated by a green status dot) 3. Click the graph icon next to the server name ![Infrahub Server Tree with Graph Icon](/assets/images/infrahub-server-tree-graph-e9daa17f9434791d44bef34236681290.png) Alternatively, right-click on the server and select **Visualize Schema** from the context menu. ## Step 2: Select a branch[​](#step-2-select-a-branch "Direct link to Step 2: Select a branch") When prompted, select the branch whose schema you want to visualize. The extension fetches the schema directly from the server for the selected branch. ## Step 3: Explore the schema graph[​](#step-3-explore-the-schema-graph "Direct link to Step 3: Explore the schema graph") The visualizer displays your schema as an interactive graph with nodes representing schema types and edges representing relationships. ![Schema Visualizer Main View](/assets/images/infrahub-graph-b80d49e589dc544429944cd11d0d1f37.png) The **Schema Overview** panel in the top-left shows statistics: * **Visible**: Number of schemas currently displayed * **Total**: Total schemas in the branch * **Nodes**: Regular schema nodes * **Profiles**: Profile configurations * **Templates**: Template definitions * **Generics**: Generic/reusable types ### Navigate the graph[​](#navigate-the-graph "Direct link to Navigate the graph") * **Pan**: Click and drag on the background * **Zoom**: Use mouse scroll or the zoom controls in the bottom toolbar * **Move nodes**: Click and drag individual schema nodes * **Select node**: Click on a node to view its details ## Step 4: Filter schemas[​](#step-4-filter-schemas "Direct link to Step 4: Filter schemas") Click the filter icon in the bottom toolbar to open the Filter Schemas panel. ![Filter Panel](/assets/images/infrahub-filter-panel-6e10589ec24e6830d665ce493024b8f0.png) The filter panel provides: * **Search**: Type to find schemas by name, label, or kind * **Namespace toggles**: Show or hide entire namespaces * **Individual toggles**: Enable or disable specific nodes By default, the visualizer hides schemas from the Core and Builtin namespaces to reduce visual clutter. Enable them in the filter panel if needed. ## Step 5: View node details[​](#step-5-view-node-details "Direct link to Step 5: View node details") Click on any schema node to open the details panel on the right side. ![Node Details Panel](/assets/images/infrahub-graph-node-detail-a7e773143f278158cb543563f2b51294.png) The details panel shows: ### Properties[​](#properties "Direct link to Properties") * **Namespace**: The schema's namespace * **Name**: The schema name * **Kind**: Full identifier (Namespace + Name) * **Description**: What this schema represents * **Inherit from**: Parent schemas this type inherits from ### Attributes[​](#attributes "Direct link to Attributes") Lists all attributes with their: * Name and type (Text, Number, Boolean, Dropdown) * Flags: optional, unique, read-only * Inheritance status (inherited attributes shown with "inherited" label) ### Relationships[​](#relationships "Direct link to Relationships") Lists all relationships with: * Relationship name * Cardinality: `one` or `many` * Target schema type * Inheritance status ## Step 6: Use the toolbar controls[​](#step-6-use-the-toolbar-controls "Direct link to Step 6: Use the toolbar controls") The bottom toolbar provides visualization controls: ![Bottom Toolbar](/assets/images/infrahub-bottom-menu-bar-8f6aeee413fcf312c53367525a712645.png) From left to right: | Control | Function | | ------------------ | ------------------------------------------------------ | | **-** | Zoom out | | **Fit** | Fit all nodes in view | | **+** | Zoom in | | **Smooth/Stepped** | Toggle edge style between curved and right-angle lines | | **→** | Auto-layout horizontally (left to right) | | **↓** | Auto-layout vertically (top to bottom) | | **Filter** | Toggle the filter panel | | **Reset** | Reset view to default state | | **Export** | Download as PNG or SVG | ## Step 7: Export the visualization[​](#step-7-export-the-visualization "Direct link to Step 7: Export the visualization") To save your schema visualization: 1. Arrange the graph as desired 2. Click the export button (download icon) in the toolbar 3. Choose PNG or SVG format 4. The image downloads with your current view Exported images are useful for documentation, architecture reviews, and sharing schema designs with team members. ## Validation[​](#validation "Direct link to Validation") To verify the visualizer is working correctly: 1. **Connection check**: Ensure your server shows a green status indicator 2. **Branch selection**: Verify you can see and select branches 3. **Schema loading**: The overview panel should show non-zero counts 4. **Interaction**: Click nodes to confirm the details panel opens ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Visualize Schema option is disabled[​](#visualize-schema-option-is-disabled "Direct link to Visualize Schema option is disabled") * The server must be online (green status indicator) * Check your network connection to the Infrahub instance * Verify server credentials in extension settings ### Schema shows zero nodes[​](#schema-shows-zero-nodes "Direct link to Schema shows zero nodes") * The selected branch may have no custom schemas defined * Enable Core and Builtin namespaces in the filter panel * Check if the branch exists and has schema data ### Graph is too cluttered[​](#graph-is-too-cluttered "Direct link to Graph is too cluttered") * Use the filter panel to hide unnecessary namespaces * Use auto-layout to organize nodes automatically * Zoom out to see the full structure ### Export produces blank image[​](#export-produces-blank-image "Direct link to Export produces blank image") * Ensure nodes are visible in the current view * Try using "Fit" before exporting * Check that your browser allows downloads ## Related resources[​](#related-resources "Direct link to Related resources") * [How to Manage Branches](/vscode/guides/manage-branches.md) * [Understanding Schema Validation](/vscode/topics/schema-validation.md) * [Extension Commands Reference](/vscode/reference/commands-settings.md) --- # Extension Commands and Settings Reference This reference document provides comprehensive information about all available commands, settings, keyboard shortcuts, and configuration options in the Infrahub VSCode extension. ## Commands[​](#commands "Direct link to Commands") ### Available Commands[​](#available-commands "Direct link to Available Commands") The extension registers the following commands that can be executed via the Command Palette (`Ctrl+Shift+P` / `Cmd+Shift+P`): | Command ID | Title | Description | Context | | ------------------------------ | --------------------- | ---------------------------------------------------------------------- | ---------------------------- | | `infrahub.editInfrahubYaml` | Edit file | Opens the selected YAML file at a specific location | Tree view item | | `infrahub.editGqlQuery` | Edit GraphQL Query | Opens the GraphQL query file for editing | Query tree item | | `infrahub.executeGraphQLQuery` | Execute GraphQL Query | Runs a GraphQL query against selected server/branch | Query tree item | | `infrahub.runTransform` | Run Transform | Executes a Jinja2 or Python transform with automatic command selection | Transform/Artifact tree item | | `infrahub.newBranch` | New Branch | Creates a new branch on the selected server | Server tree item | | `infrahub.deleteBranch` | Delete Branch | Deletes the selected branch | Branch tree item | | `infrahub.visualizeSchema` | Visualize Schema | Opens an interactive graph visualization of the server's schema | Server tree item (online) | ### Command Execution[​](#command-execution "Direct link to Command Execution") #### From Command Palette[​](#from-command-palette "Direct link to From Command Palette") 1. Press `Ctrl+Shift+P` (Windows/Linux) or `Cmd+Shift+P` (macOS) 2. Type "Infrahub" to filter commands 3. Select the desired command #### From Context Menus[​](#from-context-menus "Direct link to From Context Menus") Right-click on tree view items to access contextual commands: * **Server Items**: New Branch, Visualize Schema * **Branch Items**: Delete Branch * **Query Items**: Execute Query, Edit Query * **Transform/Artifact Items**: Run Transform (automatically detects Jinja2 vs Python) * **YAML Items**: Edit File #### Programmatic Execution[​](#programmatic-execution "Direct link to Programmatic Execution") Commands can be executed programmatically: ``` // Execute a command from another extension vscode.commands.executeCommand('infrahub.executeGraphQLQuery', queryItem); ``` ## Extension Settings[​](#extension-settings "Direct link to Extension Settings") ### Configuration Properties[​](#configuration-properties "Direct link to Configuration Properties") All settings are prefixed with `infrahub-vscode.`: | Setting | Type | Default | Description | | ----------------- | ------ | ----------- | ----------------------------------------- | | `servers` | array | `[]` | List of Infrahub server configurations | | `schemaDirectory` | string | `"schemas"` | Path to directory containing schema files | ### Server Configuration Schema[​](#server-configuration-schema "Direct link to Server Configuration Schema") Each server in the `servers` array follows this structure: ``` interface ServerConfig { name: string; // Display name for the server address: string; // Server URL (http/https) api_token?: string; // Optional API token for authentication tls_insecure?: boolean; // Optional: Disable TLS certificate verification (default: false) } ``` #### TLS configuration details[​](#tls-configuration-details "Direct link to TLS configuration details") The `tls_insecure` property controls TLS certificate verification behavior: * **Default value**: `false` (secure, certificates are verified) * **When `true`**: Disables certificate verification for development environments * **Scope**: Affects all HTTPS connections when any server has this enabled * **Security impact**: Makes connections vulnerable to man-in-the-middle attacks **Use cases for `tls_insecure: true`**: * Development servers with self-signed certificates * Internal testing environments with custom CA certificates * Docker containers with self-signed certificates **Never use in production** as it compromises security. ### Example Configuration[​](#example-configuration "Direct link to Example Configuration") ``` { "infrahub-vscode.servers": [ { "name": "Local Development", "address": "http://localhost:8000" }, { "name": "Production", "address": "https://infrahub.example.com", "api_token": "inf_1234567890abcdef" }, { "name": "Development (Self-Signed)", "address": "https://dev.infrahub.local", "api_token": "inf_dev_token", "tls_insecure": true } ], "infrahub-vscode.schemaDirectory": "infrastructure/schemas" } ``` warning Setting `tls_insecure: true` disables certificate verification and is **not recommended for production environments**. Only use this option in development/testing environments with self-signed certificates. ### Environment Variable Substitution[​](#environment-variable-substitution "Direct link to Environment Variable Substitution") Settings support environment variable substitution using `${env:VARIABLE_NAME}`: ``` { "infrahub-vscode.servers": [ { "name": "Production", "address": "${env:INFRAHUB_SERVER_URL}", "api_token": "${env:INFRAHUB_API_TOKEN}" } ] } ``` ## Tree View Components[​](#tree-view-components "Direct link to Tree View Components") ### Infrahub Servers Tree View[​](#infrahub-servers-tree-view "Direct link to Infrahub Servers Tree View") **View ID**: `InfrahubServerTreeView` **Location**: Activity Bar → Infrahub Container #### Tree Structure[​](#tree-structure "Direct link to Tree Structure") ``` 📁 Infrahub Servers └─ 🖥️ [Server Name] ├─ 🌿 main (default) ├─ 🌿 feature-branch-1 └─ 🌿 feature-branch-2 ``` #### Item Types[​](#item-types "Direct link to Item Types") | Item Type | View Item ID | Available Actions | | ---------------- | ------------------------ | ---------------------------- | | Server (online) | `infrahubServer:online` | New Branch, Visualize Schema | | Server (offline) | `infrahubServer:offline` | New Branch | | Branch (default) | `infrahubBranch-default` | Delete Branch | | Branch (regular) | `infrahubBranch` | Delete Branch | ### Infrahub YAML Tree View[​](#infrahub-yaml-tree-view "Direct link to Infrahub YAML Tree View") **View ID**: `infrahubYamlTreeView` **Location**: Activity Bar → Infrahub Container #### Tree Structure[​](#tree-structure-1 "Direct link to Tree Structure") ``` 📁 Infrahub YAML └─ 📄 .infrahub.yml ├─ 📁 schemas │ ├─ 📄 network.yml │ └─ 📄 location.yml ├─ 📁 queries │ ├─ 📊 get_devices │ └─ 📊 topology_report └─ 📁 checks └─ 🔍 validate_hostnames ``` #### Item Types[​](#item-types-1 "Direct link to Item Types") | Item Type | View Item ID Pattern | Available Actions | | --------- | -------------------- | ------------------------- | | YAML File | `file` | Edit File | | Query | `queries/*` | Execute Query, Edit Query | | Schema | `schemas/*` | Edit File | | Check | `checks/*` | Edit File | ## Status Bar[​](#status-bar "Direct link to Status Bar") ### Status Bar Item[​](#status-bar-item "Direct link to Status Bar Item") **Alignment**: Left **Priority**: 100 #### Status States[​](#status-states "Direct link to Status States") | State | Display Text | Background Color | | ----------- | --------------------------------- | -------------------- | | Connected | `Infrahub: v[version] ([server])` | Default | | No Server | `Infrahub: No server set` | No folder background | | Unreachable | `Infrahub: Server unreachable` | Error background | #### Update Interval[​](#update-interval "Direct link to Update Interval") * Status updates every 10 seconds * Displays first configured server's status ## Activation Events[​](#activation-events "Direct link to Activation Events") The extension activates when: ``` "activationEvents": [ "workspaceContains:.infrahub.yml", "workspaceContains:.infrahub.yaml" ] ``` ## File Associations[​](#file-associations "Direct link to File Associations") ### YAML Validation[​](#yaml-validation "Direct link to YAML Validation") Files matching these patterns receive schema validation: ``` "yamlValidation": [ { "fileMatch": [ "models/**/*.yml", "models/**/*.yaml", "schemas/**/*.yml", "schemas/**/*.yaml" ], "url": "https://schema.infrahub.app/infrahub/schema/latest.json" } ] ``` ## Language Features[​](#language-features "Direct link to Language Features") ### Definition Provider[​](#definition-provider "Direct link to Definition Provider") **Language**: YAML **Feature**: Go-to-definition (`F12` or `Ctrl+Click`) Navigates to: * Schema definitions * Referenced nodes * Relationship targets ### Document Symbol Provider[​](#document-symbol-provider "Direct link to Document Symbol Provider") **Language**: YAML **Feature**: Document outline Provides symbols for: * Nodes * Attributes * Relationships * Queries * Checks ## Context Menu Integration[​](#context-menu-integration "Direct link to Context Menu Integration") ### View Item Context Menus[​](#view-item-context-menus "Direct link to View Item Context Menus") Commands appear in context menus based on `when` clauses: ``` "menus": { "view/item/context": [ { "command": "infrahub.newBranch", "when": "view == InfrahubServerTreeView && viewItem == infrahubServer" }, { "command": "infrahub.deleteBranch", "when": "view == InfrahubServerTreeView && viewItem =~ /^infrahubBranch/" } ] } ``` ## Extension Dependencies[​](#extension-dependencies "Direct link to Extension Dependencies") ### Required Extensions[​](#required-extensions "Direct link to Required Extensions") ``` "extensionDependencies": [ "redhat.vscode-yaml" // YAML language support ] ``` The Red Hat YAML extension must be installed for schema validation to work. ## API Token Management[​](#api-token-management "Direct link to API Token Management") ### Token Format[​](#token-format "Direct link to Token Format") Infrahub API tokens typically follow this format: ``` inf_[32-character-alphanumeric-string] ``` ### Token Security Best Practices[​](#token-security-best-practices "Direct link to Token Security Best Practices") 1. **Never commit tokens**: Use environment variables 2. **Rotate regularly**: Change tokens periodically 3. **Minimal permissions**: Use read-only tokens when possible 4. **Separate environments**: Different tokens per environment ### Token Configuration Methods[​](#token-configuration-methods "Direct link to Token Configuration Methods") #### Method 1: Direct in settings (not recommended)[​](#method-1-direct-in-settings-not-recommended "Direct link to Method 1: Direct in settings (not recommended)") ``` { "infrahub-vscode.servers": [ { "api_token": "inf_direct_token_not_secure" } ] } ``` #### Method 2: Environment variables (recommended)[​](#method-2-environment-variables-recommended "Direct link to Method 2: Environment variables (recommended)") ``` { "infrahub-vscode.servers": [ { "api_token": "${env:INFRAHUB_TOKEN}" } ] } ``` #### Method 3: VSCode secrets (future)[​](#method-3-vscode-secrets-future "Direct link to Method 3: VSCode secrets (future)") Planned support for VSCode's secret storage API. ## Troubleshooting Reference[​](#troubleshooting-reference "Direct link to Troubleshooting Reference") ### Common Issues and Solutions[​](#common-issues-and-solutions "Direct link to Common Issues and Solutions") | Issue | Possible Cause | Solution | | ----------------------------- | ----------------------------------- | ----------------------------------------------------------------------------------- | | Extension not activating | No `.infrahub.yml` file | Create `.infrahub.yml` in workspace root | | Server unreachable | Invalid URL or network issue | Check server URL and network connection | | Commands not appearing | Wrong context | Ensure correct tree view item is selected | | Validation not working | Missing YAML extension | Install Red Hat YAML extension | | Token not working | Incorrect format or permissions | Verify token format and permissions | | TLS certificate errors | Self-signed or invalid certificates | Add `"tls_insecure": true` for development servers | | CERT\_HAS\_EXPIRED | Expired SSL certificate | Renew certificate or use `tls_insecure` for dev | | SELF\_SIGNED\_CERT\_IN\_CHAIN | Self-signed certificate | Use `"tls_insecure": true` for development | ### TLS error messages[​](#tls-error-messages "Direct link to TLS error messages") The extension provides specific error messages for common TLS issues: * **"TLS Certificate expired - check tls\_insecure setting"**: The server's certificate has expired * **"Self-signed certificate - check tls\_insecure setting"**: The server uses a self-signed certificate * **"TLS Verification failed - check tls\_insecure setting"**: General certificate verification failure These messages appear in the server tree view when connection attempts fail due to certificate issues. ### Debug Output[​](#debug-output "Direct link to Debug Output") Enable debug logging: 1. Open VSCode Developer Tools: `Help → Toggle Developer Tools` 2. Check Console tab for extension logs 3. Look for messages starting with "Infrahub Extension" ## Keyboard Shortcuts[​](#keyboard-shortcuts "Direct link to Keyboard Shortcuts") The extension doesn't define default keyboard shortcuts, but you can add custom ones: ### Adding Custom Shortcuts[​](#adding-custom-shortcuts "Direct link to Adding Custom Shortcuts") 1. Open Keyboard Shortcuts: `Ctrl+K Ctrl+S` (Windows/Linux) or `Cmd+K Cmd+S` (macOS) 2. Search for "Infrahub" 3. Click the `+` icon to add a keybinding ### Suggested Shortcuts[​](#suggested-shortcuts "Direct link to Suggested Shortcuts") ``` { "key": "ctrl+alt+q", "command": "infrahub.executeGraphQLQuery", "when": "view == infrahubYamlTreeView" }, { "key": "ctrl+alt+b", "command": "infrahub.newBranch", "when": "view == InfrahubServerTreeView" } ``` ## Performance Settings[​](#performance-settings "Direct link to Performance Settings") ### Refresh Intervals[​](#refresh-intervals "Direct link to Refresh Intervals") | Component | Interval | Configurable | | ---------------- | ---------- | -------------- | | Server Tree View | 10 seconds | No (hardcoded) | | Status Bar | 10 seconds | No (hardcoded) | ### Resource Limits[​](#resource-limits "Direct link to Resource Limits") * Maximum servers: No limit (performance may degrade with >10) * Query result size: Limited by VSCode webview memory * Tree view items: No hard limit ## Version Compatibility[​](#version-compatibility "Direct link to Version Compatibility") ### VSCode Version[​](#vscode-version "Direct link to VSCode Version") * **Minimum**: 1.99.0 * **Recommended**: Latest stable version ### Infrahub Server Version[​](#infrahub-server-version "Direct link to Infrahub Server Version") * **Minimum**: 0.14.0 (basic functionality) * **Recommended**: 0.15.0+ (all features) ### Node.js Runtime[​](#nodejs-runtime "Direct link to Node.js Runtime") * **Target**: ES2022 * **Module System**: Node16 ## Extension Metadata[​](#extension-metadata "Direct link to Extension Metadata") ### Publisher Information[​](#publisher-information "Direct link to Publisher Information") * **Publisher ID**: OpsMill * **Publisher Name**: OpsMill * **Homepage**: ### Gallery Banner[​](#gallery-banner "Direct link to Gallery Banner") ``` "galleryBanner": { "color": "#2183F7", "theme": "dark" } ``` ## Further Information[​](#further-information "Direct link to Further Information") * [Getting Started Tutorial](/vscode/tutorials/getting-started.md) * [How to Visualize Your Schema](/vscode/guides/visualize-schema.md) * [Understanding the Extension Architecture](/vscode/topics/extension-architecture.md) * [Security Configuration and Best Practices](/vscode/topics/security-configuration.md) * [GitHub Repository](https://github.com/opsmill/infrahub-vscode) --- # Understanding the Extension Architecture This document explains the architecture of the Infrahub VSCode extension, its design decisions, and how the various components interact to provide a seamless development experience for infrastructure automation. ## Overview[​](#overview "Direct link to Overview") The Infrahub VSCode extension follows a modular architecture pattern that separates concerns into distinct components. This design enables maintainability, testability, and extensibility while providing real-time connectivity to Infrahub servers. ## Core architecture components[​](#core-architecture-components "Direct link to Core architecture components") ### Extension activation[​](#extension-activation "Direct link to Extension activation") The extension activates when VSCode detects specific conditions in your workspace: ``` "activationEvents": [ "workspaceContains:.infrahub.yml", "workspaceContains:.infrahub.yaml" ] ``` This lazy loading approach ensures the extension only consumes resources when you're working with Infrahub projects. The activation triggers when VSCode finds an `.infrahub.yml` or `.infrahub.yaml` file in your workspace, signaling that this is an Infrahub-enabled project. ### Component hierarchy[​](#component-hierarchy "Direct link to Component hierarchy") The extension architecture consists of several interconnected layers: ``` ┌─────────────────────────────────────┐ │ VSCode Extension API │ ├─────────────────────────────────────┤ │ Extension Entry Point │ │ (extension.ts) │ ├─────────────────────────────────────┤ │ Tree View Providers │ Language │ │ │ Providers │ ├─────────────────────┼────────────────┤ │ Commands │ Utilities │ ├─────────────────────┴────────────────┤ │ Infrahub SDK Client │ ├─────────────────────────────────────┤ │ Infrahub Server (Remote) │ └─────────────────────────────────────┘ ``` ## Key architectural patterns[​](#key-architectural-patterns "Direct link to Key architectural patterns") ### Provider pattern[​](#provider-pattern "Direct link to Provider pattern") The extension uses VSCode's provider pattern extensively. Providers are classes that implement specific interfaces to extend VSCode's functionality: #### Tree data providers[​](#tree-data-providers "Direct link to Tree data providers") Tree data providers manage the hierarchical views in the sidebar: * **InfrahubServerTreeViewProvider**: Manages server connections and branch listings * **InfrahubYamlTreeViewProvider**: Parses and displays `.infrahub.yml` structure These providers implement the `TreeDataProvider` interface, which requires: * `getTreeItem()`: Returns the UI representation of an item * `getChildren()`: Returns child elements for expandable items * `onDidChangeTreeData`: Event emitter for refreshing the tree #### Language providers[​](#language-providers "Direct link to Language providers") Language providers enhance the editing experience: * **YamlDefinitionProvider**: Enables go-to-definition for schema references * **YamlDocumentSymbolProvider**: Creates document outline/symbols ### Event-driven updates[​](#event-driven-updates "Direct link to Event-driven updates") The extension uses an event-driven architecture for real-time updates: ``` // Automatic refresh every 10 seconds setInterval(() => InfrahubServerTreeView.refresh(), 10000); // Configuration change listener vscode.workspace.onDidChangeConfiguration(e => { if (e.affectsConfiguration('infrahub-vscode.servers')) { this.refresh(); } }); ``` This approach ensures: * Status bar updates reflect current server state * Tree views stay synchronized with server changes * Configuration changes take effect immediately ### Client management[​](#client-management "Direct link to Client management") The extension maintains a client pool for server connections: ``` private clients: Map = new Map(); ``` Each server configuration creates a dedicated client instance, allowing: * Concurrent connections to multiple servers * Isolated authentication per server * Efficient connection reuse ## Design decisions and rationale[​](#design-decisions-and-rationale "Direct link to Design decisions and rationale") ### Why tree views?[​](#why-tree-views "Direct link to Why tree views?") Tree views were chosen as the primary UI component because: 1. **Familiar Paradigm**: Developers are accustomed to file explorers and tree structures 2. **Information Hierarchy**: Natural representation of servers → branches → schemas 3. **Contextual Actions**: Right-click menus provide discoverable functionality 4. **Space Efficiency**: Collapsible nodes manage complex information efficiently ### Status bar integration[​](#status-bar-integration "Direct link to Status bar integration") The status bar provides ambient awareness of connection state: * **Always Visible**: Users see server status without opening panels * **Non-Intrusive**: Doesn't interrupt workflow * **Quick Feedback**: Color coding indicates connection health instantly ### Configuration strategy[​](#configuration-strategy "Direct link to Configuration strategy") The extension uses VSCode's built-in settings system rather than custom configuration files: **Advantages**: * Integrated with VSCode's settings UI * Supports workspace and user-level settings * Environment variable substitution for security * Settings sync across devices **Trade-offs**: * Limited to JSON structure * No runtime configuration changes * Requires VSCode restart for some changes ### SDK integration[​](#sdk-integration "Direct link to SDK integration") The extension uses the official `infrahub-sdk` package: ``` import { InfrahubClient, InfrahubClientOptions } from 'infrahub-sdk'; ``` **Benefits**: * Consistent API with other Infrahub tools * Maintained by the Infrahub team * Type safety with TypeScript * Automatic API version compatibility ## Mental models[​](#mental-models "Direct link to Mental models") ### Connection lifecycle[​](#connection-lifecycle "Direct link to Connection lifecycle") Understanding how connections are established and maintained: 1. **Initialization**: Server configurations are loaded from settings 2. **Client Creation**: Each server gets an InfrahubClient instance 3. **Health Checking**: Status bar polls server health every 10 seconds 4. **Query Execution**: Clients are reused for GraphQL operations 5. **Cleanup**: Connections persist until VSCode closes ### Data flow[​](#data-flow "Direct link to Data flow") How data moves through the extension: ``` User Action → Command Handler → Client Method → Server API ↓ ↓ UI Update ← Result Processing ← API Response ``` Example: Executing a GraphQL query: 1. User selects play icon next to query in tree view 2. Command handler prompts for variables 3. Client sends query to selected server/branch 4. Results are formatted and displayed in webview 5. Tree view updates if data changed ### State management[​](#state-management "Direct link to State management") The extension maintains minimal state: * **Server Configurations**: Read from settings, cached in memory * **Client Instances**: Stored in Map, reused across operations * **Tree View State**: Managed by VSCode, persists across sessions * **No Persistent Storage**: Extension doesn't write to disk ## Integration points[​](#integration-points "Direct link to Integration points") ### VSCode extension API[​](#vscode-extension-api "Direct link to VSCode extension API") The extension integrates with multiple VSCode APIs: * **Commands API**: Registers executable commands * **Tree View API**: Creates custom sidebar views * **Language API**: Provides IntelliSense features * **Webview API**: Displays query results * **Status Bar API**: Shows server connection status ### Infrahub server[​](#infrahub-server "Direct link to Infrahub server") Communication with Infrahub servers occurs through: * **GraphQL Endpoint**: Query and mutation execution * **REST API**: Server version and health checks ### File system integration[​](#file-system-integration "Direct link to File system integration") The extension interacts with the workspace: * **Schema Files**: Validates YAML in configured directories * **Query Files**: Reads `.gql` files for execution * **Configuration**: Parses `.infrahub.yml` for project structure ## Performance considerations[​](#performance-considerations "Direct link to Performance considerations") ### Lazy loading[​](#lazy-loading "Direct link to Lazy loading") Components load only when needed: * Tree views populate on expansion * Clients connect on first use * Validation runs on file save ### Caching strategy[​](#caching-strategy "Direct link to Caching strategy") The extension implements strategic caching: * Server configurations cached until settings change * Branch lists refresh every 10 seconds * Client connections persist across operations ### Resource management[​](#resource-management "Direct link to Resource management") Efficient resource usage through: * Single client instance per server * Debounced validation on typing * Incremental tree view updates ## Extension boundaries[​](#extension-boundaries "Direct link to Extension boundaries") ### What the extension does[​](#what-the-extension-does "Direct link to What the extension does") * **Client-Side Operations**: All processing happens locally * **Read Operations**: Primarily queries data * **UI Enhancement**: Improves developer experience * **Validation**: Schema and syntax checking ### What the extension doesn't do[​](#what-the-extension-doesnt-do "Direct link to What the extension doesn't do") * **Server Management**: Cannot start/stop Infrahub servers * **Data Persistence**: Doesn't store data locally * **Background Sync**: No automatic data synchronization * **Conflict Resolution**: Merge conflicts handled server-side ## Further reading[​](#further-reading "Direct link to Further reading") * [Schema Validation and YAML Intelligence](/vscode/topics/schema-validation.md) * [Extension Commands Reference](/vscode/reference/commands-settings.md) * [VSCode Extension API Documentation](https://code.visualstudio.com/api) --- # Schema Validation and YAML Intelligence This document explores how the Infrahub VSCode extension provides intelligent YAML editing capabilities, real-time schema validation, and navigation features that enhance the developer experience when working with infrastructure schemas. ## Overview[​](#overview "Direct link to Overview") Schema validation and YAML intelligence are fundamental features that transform VSCode into a powerful Infrahub development environment. These capabilities help catch errors early, provide contextual assistance, and accelerate schema development through intelligent code completion and navigation. ## The Role of Schemas in Infrahub[​](#the-role-of-schemas-in-infrahub "Direct link to The Role of Schemas in Infrahub") ### Infrastructure as Schema[​](#infrastructure-as-schema "Direct link to Infrastructure as Schema") In Infrahub, schemas define the structure of your infrastructure data: ``` version: '1.0' nodes: - name: Device namespace: Network attributes: - name: hostname kind: Text unique: true ``` This schema-first approach means: * **Type Safety**: Data must conform to defined structures * **Consistency**: All infrastructure follows the same patterns * **Validation**: Invalid data is rejected before it causes issues * **Documentation**: Schemas serve as living documentation ### Why Validation Matters[​](#why-validation-matters "Direct link to Why Validation Matters") Schema validation prevents several categories of errors: 1. **Syntax Errors**: Malformed YAML that won't parse 2. **Type Mismatches**: Wrong data types for attributes 3. **Constraint Violations**: Breaking uniqueness or required fields 4. **Relationship Errors**: Invalid references between objects 5. **Version Conflicts**: Incompatible schema versions ## How Schema Validation Works[​](#how-schema-validation-works "Direct link to How Schema Validation Works") ### Validation Pipeline[​](#validation-pipeline "Direct link to Validation Pipeline") The extension implements a multi-stage validation pipeline: ``` File Change → YAML Parse → Schema Load → Validation → Diagnostics ↓ ↓ ↓ ↓ ↓ Detected Structure Reference Rules Display Checking Resolution Applied Errors ``` ### Real-Time Validation[​](#real-time-validation "Direct link to Real-Time Validation") Validation occurs at multiple points: 1. **On Type**: Incremental validation as you type (debounced) 2. **On Save**: Full validation when file is saved 3. **On Open**: Initial validation when file is opened 4. **On Focus**: Re-validation when switching between files ### Validation Scope[​](#validation-scope "Direct link to Validation Scope") The extension validates files based on location: ``` # Automatically validated directories models/ # Infrastructure models schemas/ # Schema definitions ``` These directories are configured through: ``` { "infrahub-vscode.schemaDirectory": "schemas" } ``` ## YAML Intelligence Features[​](#yaml-intelligence-features "Direct link to YAML Intelligence Features") ### Syntax Highlighting[​](#syntax-highlighting "Direct link to Syntax Highlighting") The extension provides enhanced syntax highlighting for Infrahub-specific constructs: * **Keywords**: `version`, `nodes`, `attributes`, `relationships` * **Types**: `Text`, `Number`, `Boolean`, `IPHost`, `IPNetwork` * **Modifiers**: `unique`, `optional`, `default_value` ### Auto-Completion[​](#auto-completion "Direct link to Auto-Completion") Context-aware suggestions appear as you type: ``` nodes: - name: Router attributes: - name: hostname kind: | # Cursor here triggers type suggestions # Text, Number, Boolean, IPHost, etc. ``` ### Go-to-Definition[​](#go-to-definition "Direct link to Go-to-Definition") Navigate between related schemas with `F12` or `Ctrl+Click`: ``` relationships: - name: interfaces peer: NetworkInterface # Ctrl+Click jumps to NetworkInterface definition kind: Component ``` ### Document Symbols[​](#document-symbols "Direct link to Document Symbols") The outline view shows schema structure: ``` 📄 network.yml └─ nodes └─ Device ├─ attributes │ ├─ hostname │ └─ model └─ relationships └─ interfaces ``` ## Schema Validation Rules[​](#schema-validation-rules "Direct link to Schema Validation Rules") ### Structural Validation[​](#structural-validation "Direct link to Structural Validation") The extension enforces schema structure: ``` # Valid structure version: '1.0' nodes: - name: Device namespace: Network # Invalid - missing version nodes: # ❌ Error: Schema must include version - name: Device ``` ### Attribute Validation[​](#attribute-validation "Direct link to Attribute Validation") Attributes must follow specific rules: ``` attributes: - name: hostname kind: Text # ✅ Valid kind unique: true # ✅ Valid modifier - name: port kind: String # ❌ Error: Invalid kind 'String', use 'Text' - name: ip_address kind: IPHost mandatory: true # ❌ Error: Use 'optional: false' instead ``` ### Relationship Validation[​](#relationship-validation "Direct link to Relationship Validation") Relationships are validated for consistency: ``` relationships: - name: location peer: LocationSite # Must reference existing node kind: Parent # Must be valid relationship kind cardinality: one # Must be valid cardinality - name: interfaces peer: NonExistent # ❌ Error: Node 'NonExistent' not found kind: Invalid # ❌ Error: Invalid relationship kind ``` ## Understanding Validation Messages[​](#understanding-validation-messages "Direct link to Understanding Validation Messages") ### Error Categories[​](#error-categories "Direct link to Error Categories") Validation produces different message types: 1. **Errors** (Red): Must be fixed before schema works * Missing required fields * Invalid types * Syntax errors 2. **Warnings** (Yellow): Should be addressed but won't break functionality * Deprecated patterns * Performance concerns * Best practice violations 3. **Information** (Blue): Helpful suggestions * Optimization opportunities * Alternative approaches * Documentation hints ### Message Structure[​](#message-structure "Direct link to Message Structure") Each validation message contains: ``` [Severity] [Location] Message Example: Error at line 15: Attribute kind 'String' is not valid ``` ### Common Validation Errors[​](#common-validation-errors "Direct link to Common Validation Errors") #### Missing Required Fields[​](#missing-required-fields "Direct link to Missing Required Fields") ``` nodes: - namespace: Network # ❌ Error: Missing required field 'name' ``` **Solution**: Add the missing field: ``` nodes: - name: Device namespace: Network ``` #### Invalid Attribute Kind[​](#invalid-attribute-kind "Direct link to Invalid Attribute Kind") ``` attributes: - name: count kind: Integer # ❌ Error: Use 'Number' instead of 'Integer' ``` **Solution**: Use correct type: ``` attributes: - name: count kind: Number ``` #### Circular Dependencies[​](#circular-dependencies "Direct link to Circular Dependencies") ``` nodes: - name: A relationships: - peer: B kind: Parent - name: B relationships: - peer: A kind: Parent # ❌ Error: Circular dependency detected ``` **Solution**: Redesign relationship hierarchy ## Advanced Validation Concepts[​](#advanced-validation-concepts "Direct link to Advanced Validation Concepts") ### Schema Inheritance[​](#schema-inheritance "Direct link to Schema Inheritance") Understanding how schemas inherit from base definitions: ``` nodes: - name: Device namespace: Network inherit_from: - BuiltinDevice # Inherits attributes and relationships attributes: - name: custom_field # Adds to inherited attributes kind: Text ``` The validator checks: * Base schema exists * No attribute conflicts * Relationship compatibility ### Cross-File Validation[​](#cross-file-validation "Direct link to Cross-File Validation") The extension validates references across files: ``` # File: schemas/network.yml nodes: - name: Device namespace: Network # File: schemas/interface.yml nodes: - name: Interface namespace: Network relationships: - name: device peer: NetworkDevice # Validated across files ``` ### Version Compatibility[​](#version-compatibility "Direct link to Version Compatibility") Schema versions affect validation rules: ``` version: '1.0' # Original schema format version: '1.1' # Supports additional features generics: # New in 1.1 - name: GenericDevice ``` ## Performance Considerations[​](#performance-considerations "Direct link to Performance Considerations") ### Incremental Validation[​](#incremental-validation "Direct link to Incremental Validation") The extension uses incremental validation for performance: 1. **Parse Caching**: YAML AST cached between edits 2. **Partial Updates**: Only changed sections re-validated 3. **Debouncing**: Validation delayed during rapid typing 4. **Background Processing**: Validation doesn't block UI ### Large File Handling[​](#large-file-handling "Direct link to Large File Handling") For large schema files: * **Lazy Loading**: Schemas loaded on-demand * **Chunked Processing**: Large files processed in segments * **Priority Validation**: Visible content validated first * **Throttling**: Rate-limited validation for huge files ## Customizing Validation[​](#customizing-validation "Direct link to Customizing Validation") ### Validation Settings[​](#validation-settings "Direct link to Validation Settings") Configure validation behavior: ``` { "yaml.schemas": { "https://schema.infrahub.app/infrahub/schema/latest.json": [ "schemas/**/*.yml", "models/**/*.yaml" ] }, "yaml.validate": true, "yaml.customTags": [ "!include", "!secret" ] } ``` ### Disabling Validation[​](#disabling-validation "Direct link to Disabling Validation") To temporarily disable validation: 1. **Per File**: Add comment at top ``` # yaml-language-server: $schema=none ``` 2. **Per Workspace**: Modify settings ``` { "yaml.validate": false } ``` ## Integration with Other Tools[​](#integration-with-other-tools "Direct link to Integration with Other Tools") ### YAML Language Server[​](#yaml-language-server "Direct link to YAML Language Server") The extension leverages the Red Hat YAML extension: * **Schema Association**: Links files to Infrahub schemas * **JSON Schema**: Uses JSON Schema for validation * **Custom Tags**: Supports Infrahub-specific tags ### Linting Integration[​](#linting-integration "Direct link to Linting Integration") Works alongside linting tools: * **yamllint**: Style and formatting checks * **Prettier**: Code formatting * **Vale**: Documentation linting ## Troubleshooting Validation Issues[​](#troubleshooting-validation-issues "Direct link to Troubleshooting Validation Issues") ### Validation Not Working[​](#validation-not-working "Direct link to Validation Not Working") If validation isn't functioning: 1. **Check File Location**: Ensure file is in configured directory 2. **Verify Extension**: Confirm YAML extension is installed 3. **Schema Association**: Check schema URL is correct 4. **Reload Window**: Try reloading VSCode ### False Positives[​](#false-positives "Direct link to False Positives") If seeing incorrect errors: 1. **Update Extension**: Ensure latest version 2. **Check Schema Version**: Verify schema compatibility 3. **Clear Cache**: Reload VSCode window 4. **Report Issue**: File bug report if persistent ### Performance Issues[​](#performance-issues "Direct link to Performance Issues") If validation is slow: 1. **File Size**: Consider splitting large files 2. **Disable Real-time**: Turn off on-type validation 3. **Increase Debounce**: Adjust validation delay 4. **Check Extensions**: Disable conflicting extensions ## Further Reading[​](#further-reading "Direct link to Further Reading") * [Understanding the Extension Architecture](/vscode/topics/extension-architecture.md) * [How to Execute GraphQL Queries](/vscode/guides/execute-graphql-queries.md) * [Extension Commands Reference](/vscode/reference/commands-settings.md) --- # Security Configuration and Best Practices This document covers security considerations, TLS configuration, API token management, and best practices when using the Infrahub VSCode extension across different environments. ## Overview[​](#overview "Direct link to Overview") The Infrahub VSCode extension handles connections to Infrahub servers which may contain sensitive infrastructure data. Proper security configuration ensures your connections are encrypted and authenticated appropriately for your environment. ## TLS certificate configuration[​](#tls-certificate-configuration "Direct link to TLS certificate configuration") ### Understanding TLS in the extension[​](#understanding-tls-in-the-extension "Direct link to Understanding TLS in the extension") The extension connects to Infrahub servers over HTTPS using TLS encryption. By default, the extension verifies server certificates to ensure secure connections. However, development environments often use self-signed certificates that fail standard verification. ### Production environment (default secure settings)[​](#production-environment-default-secure-settings "Direct link to Production environment (default secure settings)") For production environments, always use properly signed certificates: ``` { "infrahub-vscode.servers": [ { "name": "Production", "address": "https://infrahub.company.com", "api_token": "${env:INFRAHUB_PROD_TOKEN}" // tls_insecure defaults to false - secure mode } ] } ``` **Security Benefits:** * Prevents man-in-the-middle attacks * Ensures server identity verification * Maintains encryption integrity * Complies with enterprise security policies ### Development environment (insecure mode)[​](#development-environment-insecure-mode "Direct link to Development environment (insecure mode)") For development environments with self-signed certificates: ``` { "infrahub-vscode.servers": [ { "name": "Development", "address": "https://dev.infrahub.local", "api_token": "${env:INFRAHUB_DEV_TOKEN}", "tls_insecure": true } ] } ``` **When to Use `tls_insecure: true`:** * Local development servers with self-signed certificates * Docker containers with self-generated certificates * Internal testing environments with custom CA certificates * Development environments where proper certificates aren't feasible **Security Implications:** * Disables certificate verification * Vulnerable to man-in-the-middle attacks * Should never be used in production * Affects all HTTPS connections in the VSCode process ### Mixed environment configuration[​](#mixed-environment-configuration "Direct link to Mixed environment configuration") You can configure different TLS settings for different environments: ``` { "infrahub-vscode.servers": [ { "name": "Production", "address": "https://infrahub.company.com", "api_token": "${env:INFRAHUB_PROD_TOKEN}" // Secure by default }, { "name": "Staging", "address": "https://staging.infrahub.company.com", "api_token": "${env:INFRAHUB_STAGING_TOKEN}" // Uses proper certificates }, { "name": "Development", "address": "https://dev.infrahub.local:8000", "api_token": "${env:INFRAHUB_DEV_TOKEN}", "tls_insecure": true // Allows self-signed certificates } ] } ``` ### How TLS configuration works internally[​](#how-tls-configuration-works-internally "Direct link to How TLS configuration works internally") When any server has `tls_insecure: true`: 1. **Environment Variable**: Sets `NODE_TLS_REJECT_UNAUTHORIZED = '0'` 2. **Global Effect**: Affects all HTTPS connections in the VSCode process 3. **Restoration**: Original setting restored when extension deactivates 4. **Dynamic Updates**: Changes apply immediately when configuration updates This approach was chosen to ensure compatibility with the Infrahub SDK and provide reliable certificate bypassing when needed. ## API token security[​](#api-token-security "Direct link to API token security") ### Token storage best practices[​](#token-storage-best-practices "Direct link to Token storage best practices") #### ❌ Don't store tokens directly[​](#-dont-store-tokens-directly "Direct link to ❌ Don't store tokens directly") ``` // NEVER do this - tokens visible in settings { "infrahub-vscode.servers": [ { "api_token": "inf_1234567890abcdef" // Visible in settings! } ] } ``` #### ✅ Use Environment Variables[​](#-use-environment-variables "Direct link to ✅ Use Environment Variables") ``` // Secure approach - tokens in environment variables { "infrahub-vscode.servers": [ { "api_token": "${env:INFRAHUB_API_TOKEN}" // References environment } ] } ``` ### Setting up environment variables[​](#setting-up-environment-variables "Direct link to Setting up environment variables") #### macOS and Linux[​](#macos-and-linux "Direct link to macOS and Linux") Add to your shell configuration file (`~/.zshrc`, `~/.bashrc`, etc.): ``` # Development environment export INFRAHUB_DEV_TOKEN="inf_dev_1234567890abcdef" # Staging environment export INFRAHUB_STAGING_TOKEN="inf_staging_1234567890abcdef" # Production environment export INFRAHUB_PROD_TOKEN="inf_prod_1234567890abcdef" ``` #### Windows[​](#windows "Direct link to Windows") Using PowerShell: ``` # Set user-level environment variables [System.Environment]::SetEnvironmentVariable('INFRAHUB_DEV_TOKEN', 'inf_dev_1234567890abcdef', 'User') [System.Environment]::SetEnvironmentVariable('INFRAHUB_STAGING_TOKEN', 'inf_staging_1234567890abcdef', 'User') [System.Environment]::SetEnvironmentVariable('INFRAHUB_PROD_TOKEN', 'inf_prod_1234567890abcdef', 'User') ``` ### Token management best practices[​](#token-management-best-practices "Direct link to Token management best practices") #### Token rotation[​](#token-rotation "Direct link to Token rotation") Regularly rotate API tokens: ``` # Example rotation script #!/bin/bash # Generate new token via Infrahub API or web interface NEW_TOKEN="inf_new_token_here" # Update environment variable export INFRAHUB_PROD_TOKEN="$NEW_TOKEN" # Update your shell configuration echo "export INFRAHUB_PROD_TOKEN=\"$NEW_TOKEN\"" >> ~/.zshrc ``` #### Minimal permissions[​](#minimal-permissions "Direct link to Minimal permissions") Create tokens with minimal required permissions: * **Read-only tokens** for query execution * **Limited scope tokens** for specific resources * **Separate tokens** for each environment * **Short-lived tokens** for temporary access #### Token security checklist[​](#token-security-checklist "Direct link to Token security checklist") * Tokens stored in environment variables, not settings files * Different tokens for each environment (dev, staging, prod) * Tokens have minimal required permissions * Regular token rotation schedule established * Tokens excluded from version control (`.env` files in `.gitignore`) * Access to tokens limited to authorized personnel ## Network security considerations[​](#network-security-considerations "Direct link to Network security considerations") ### Firewall and proxy configuration[​](#firewall-and-proxy-configuration "Direct link to Firewall and proxy configuration") #### Corporate proxy settings[​](#corporate-proxy-settings "Direct link to Corporate proxy settings") If behind a corporate proxy, VSCode inherits system proxy settings. For manual configuration: ``` { "http.proxy": "http://proxy.company.com:8080", "http.proxyStrictSSL": true, "http.proxyAuthorization": null } ``` #### Network access requirements[​](#network-access-requirements "Direct link to Network access requirements") The extension requires outbound HTTPS access to: * Infrahub server endpoints (configured addresses) * Schema validation endpoints (`https://schema.infrahub.app/`) ### VPN and network isolation[​](#vpn-and-network-isolation "Direct link to VPN and network isolation") #### VPN-protected servers[​](#vpn-protected-servers "Direct link to VPN-protected servers") For servers behind VPN: ``` { "infrahub-vscode.servers": [ { "name": "Internal Production", "address": "https://internal.infrahub.corp", "api_token": "${env:INFRAHUB_INTERNAL_TOKEN}" // Ensure VPN is connected before use } ] } ``` #### Network segmentation[​](#network-segmentation "Direct link to Network segmentation") Consider network segmentation best practices: * Development servers on isolated networks * Production servers with restricted access * API tokens with network-based restrictions ## Compliance and audit considerations[​](#compliance-and-audit-considerations "Direct link to Compliance and audit considerations") ### Logging and monitoring[​](#logging-and-monitoring "Direct link to Logging and monitoring") The extension logs connection attempts and errors. Monitor logs for: * Failed authentication attempts * Certificate verification bypasses * Unusual connection patterns * Token usage patterns ### Compliance requirements[​](#compliance-requirements "Direct link to Compliance requirements") For regulated environments: #### SOC 2 / ISO 27001[​](#soc-2--iso-27001 "Direct link to SOC 2 / ISO 27001") * Document TLS configuration decisions * Maintain token rotation procedures * Log security-relevant events * Regular security configuration reviews #### GDPR / data privacy[​](#gdpr--data-privacy "Direct link to GDPR / data privacy") * Understand what data is transmitted to servers * Ensure proper encryption in transit * Document data processing activities * Implement data retention policies ### Security scanning integration[​](#security-scanning-integration "Direct link to Security scanning integration") #### VS Code security extensions[​](#vs-code-security-extensions "Direct link to VS Code security extensions") Consider installing complementary security extensions: ``` { "recommendations": [ "ms-vscode.vscode-json", "redhat.vscode-yaml", "ms-python.python" ] } ``` #### Static analysis[​](#static-analysis "Direct link to Static analysis") Regularly scan configuration files for: * Hardcoded tokens or secrets * Insecure TLS configurations * Overly permissive settings ## Troubleshooting security issues[​](#troubleshooting-security-issues "Direct link to Troubleshooting security issues") ### Common TLS errors and solutions[​](#common-tls-errors-and-solutions "Direct link to Common TLS errors and solutions") #### Certificate expired[​](#certificate-expired "Direct link to Certificate expired") **Error**: `CERT_HAS_EXPIRED` **Solutions**: 1. Renew the server certificate (production) 2. Add `"tls_insecure": true` (development only) #### Self-signed certificate[​](#self-signed-certificate "Direct link to Self-signed certificate") **Error**: `SELF_SIGNED_CERT_IN_CHAIN` **Solutions**: 1. Install proper CA-signed certificate (production) 2. Add `"tls_insecure": true` (development) 3. Add certificate to system trust store #### Certificate verification failed[​](#certificate-verification-failed "Direct link to Certificate verification failed") **Error**: `UNABLE_TO_VERIFY_LEAF_SIGNATURE` **Solutions**: 1. Check certificate chain configuration 2. Update system CA certificate store 3. Use `"tls_insecure": true` for development ### Authentication issues[​](#authentication-issues "Direct link to Authentication issues") #### Token not working[​](#token-not-working "Direct link to Token not working") **Symptoms**: `401 Unauthorized` errors **Check**: 1. Token format (should start with `inf_`) 2. Token permissions and scope 3. Token expiration date 4. Environment variable substitution #### Environment variable not found[​](#environment-variable-not-found "Direct link to Environment variable not found") **Error**: Token resolves to literal `${env:VARIABLE_NAME}` **Solutions**: 1. Verify environment variable is set 2. Restart VSCode after setting variables 3. Check variable name spelling ### Network connectivity issues[​](#network-connectivity-issues "Direct link to Network connectivity issues") #### Proxy interference[​](#proxy-interference "Direct link to Proxy interference") **Symptoms**: Connection timeouts or SSL errors **Solutions**: 1. Configure VSCode proxy settings 2. Add Infrahub server to proxy bypass list 3. Verify proxy supports HTTPS CONNECT #### Firewall blocking[​](#firewall-blocking "Direct link to Firewall blocking") **Symptoms**: Connection refused or timeouts **Solutions**: 1. Verify outbound HTTPS (443) access 2. Add Infrahub server to firewall allowlist 3. Check for deep packet inspection interference ## Security best practices summary[​](#security-best-practices-summary "Direct link to Security best practices summary") ### Development environment[​](#development-environment "Direct link to Development environment") * ✅ Use `tls_insecure: true` for self-signed certificates * ✅ Use separate development API tokens * ✅ Limit development token permissions * ✅ Regular token rotation * ❌ Never use production tokens in development ### Staging environment[​](#staging-environment "Direct link to Staging environment") * ✅ Use proper certificates when possible * ✅ Separate staging API tokens * ✅ Mirror production security settings * ✅ Test certificate configurations * ❌ Don't use `tls_insecure` unless necessary ### Production environment[​](#production-environment "Direct link to Production environment") * ✅ Always use proper CA-signed certificates * ✅ Never use `tls_insecure: true` * ✅ Implement token rotation procedures * ✅ Monitor and log access * ✅ Regular security reviews * ❌ Never compromise on certificate verification ### Token management[​](#token-management "Direct link to Token management") * ✅ Store in environment variables * ✅ Use minimal required permissions * ✅ Regular rotation schedule * ✅ Audit token usage * ❌ Never commit tokens to version control * ❌ Don't share tokens between environments ## Related documentation[​](#related-documentation "Direct link to Related documentation") * [Configure Multiple Servers Guide](/vscode/guides/configure-multiple-servers.md) * [Commands and Settings Reference](/vscode/reference/commands-settings.md) * [Getting Started Tutorial](/vscode/tutorials/getting-started.md) --- # Getting Started with Infrahub VSCode Extension This tutorial will walk you through installing the Infrahub VSCode extension, connecting to your first Infrahub server, and exploring the core features. By the end of this tutorial, you'll be ready to start developing infrastructure schemas and executing GraphQL queries directly from VSCode. ## What you'll learn[​](#what-youll-learn "Direct link to What you'll learn") * How to install the Infrahub extension * Setting up your first server connection * Navigating the extension interface * Creating your first schema * Executing a GraphQL query ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before starting this tutorial, ensure you have: * Visual Studio Code version 1.99.0 or higher installed * Access to an Infrahub server (either local or remote) * Basic familiarity with VSCode extensions * (Optional) An API token for your Infrahub server ## Step 1: Install the extension[​](#step-1-install-the-extension "Direct link to Step 1: Install the extension") ### Option A: from VSCode marketplace[​](#option-a-from-vscode-marketplace "Direct link to Option A: from VSCode marketplace") 1. Open Visual Studio Code 2. Click on the Extensions icon in the Activity Bar (`Ctrl+Shift+X` on Windows/Linux, `Cmd+Shift+X` on macOS) 3. Search for "Infrahub" 4. Find the extension published by "opsmill" 5. Click the **Install** button ### Option B: VSCode based editors[​](#option-b-vscode-based-editors "Direct link to Option B: VSCode based editors") 1. We publish the extension to [Open VSX](https://open-vsx.org/extension/opsmill/infrahub) which is often used by VSCode Forks like Cursor. 2. In your editor of choice search Infrahub. 3. Install 4. If this is not an option download the VSIX file and import manually. ### Option C: from a VSIX file[​](#option-c-from-a-vsix-file "Direct link to Option C: from a VSIX file") If you have a `.vsix` file: 1. Open VSCode 2. Press `Ctrl+Shift+P` (or `Cmd+Shift+P` on macOS) to open the Command Palette 3. Type "Extensions: Install from VSIX" 4. Navigate to your `.vsix` file and select it ## Step 2: Configure your first server[​](#step-2-configure-your-first-server "Direct link to Step 2: Configure your first server") Once installed, you'll need to connect to an Infrahub server. 1. Open VSCode Settings: * Press `Ctrl+,` (Windows/Linux) or `Cmd+,` (macOS) * Or go to File → Preferences → Settings 2. Search for "Infrahub" in the settings search bar 3. Find the **Infrahub-vscode: Servers** setting 4. Click **Edit in settings.json** 5. Add your server configuration: ``` { "infrahub-vscode.servers": [ { "name": "Local Development", "address": "http://localhost:8000" } ] } ``` If your server requires authentication, add an API token: ``` { "infrahub-vscode.servers": [ { "name": "Local Development", "address": "http://localhost:8000", "api_token": "your-api-token-here" } ] } ``` > **Tip:** You can use environment variables for sensitive tokens: > > ``` > "api_token": "${env:INFRAHUB_API_TOKEN}" > ``` ### TLS configuration for HTTPS servers[​](#tls-configuration-for-https-servers "Direct link to TLS configuration for HTTPS servers") If you're connecting to an HTTPS server with a self-signed certificate (common in development environments), you may need to disable certificate verification: ``` { "infrahub-vscode.servers": [ { "name": "Development (Self-Signed)", "address": "https://dev.infrahub.local:8000", "api_token": "${env:INFRAHUB_DEV_TOKEN}", "tls_insecure": true } ] } ``` warning The `tls_insecure` option disables TLS certificate verification and should **only be used in development environments** with self-signed certificates. Never use this option for production servers as it makes connections vulnerable to security attacks. ## Step 3: Verify your connection[​](#step-3-verify-your-connection "Direct link to Step 3: Verify your connection") After configuring your server: 1. Look at the VSCode status bar (bottom of the window) 2. You should see "Infrahub: v\[version] (Local Development)" 3. If you see "Infrahub: Server unreachable", check your server address and network connection The extension checks the connection every 10 seconds and updates the status automatically. ## Step 4: Explore the extension interface[​](#step-4-explore-the-extension-interface "Direct link to Step 4: Explore the extension interface") ### Activity bar[​](#activity-bar "Direct link to Activity bar") Click on the Infrahub icon in the Activity Bar (left side of VSCode). You'll see two tree views: 1. **Infrahub Servers**: Shows your configured servers and their branches 2. **Infrahub YAML**: Displays the structure of `.infrahub.yml` files in your workspace ### Server tree view[​](#server-tree-view "Direct link to Server tree view") 1. Expand your server in the Infrahub Servers view 2. You'll see a list of branches available on that server 3. The default branch is marked with "(default)" ### Context menu actions[​](#context-menu-actions "Direct link to Context menu actions") Right-click on elements in the tree views to see available actions: * Servers: Create new branches * Branches: Delete branches * YAML items: Edit files, execute queries ## Step 5: Create your first schema[​](#step-5-create-your-first-schema "Direct link to Step 5: Create your first schema") Let's create a network device schema. 1. Create a new directory called `schemas` in your project root: ``` mkdir schemas ``` 2. Create a new file `schemas/device.yml`: 3. Add the following schema definition: ``` --- version: '1.0' nodes: - name: Device namespace: Network description: Basic network device attributes: - name: hostname kind: Text unique: true description: Device hostname - name: model kind: Text optional: true description: Device model number - name: status kind: Text default_value: "active" description: Operational status ``` 4. Save the file. The extension will automatically validate your schema. 5. If there are any validation errors, they'll appear in the Problems panel (`Ctrl+Shift+M`) ## Step 6: Execute your first GraphQL query[​](#step-6-execute-your-first-graphql-query "Direct link to Step 6: Execute your first GraphQL query") Now let's query the devices in your Infrahub instance. 1. Create a `.infrahub.yml` file in your project root: ``` --- queries: - name: list_devices file: queries/list_devices.gql ``` 2. Create the query file `queries/list_devices.gql`: ``` query ListDevices { NetworkDevice { edges { node { hostname { value } model { value } status { value } } } } } ``` 3. In the Infrahub YAML tree view, expand your `.infrahub.yml` file 4. Find the "list\_devices" query 5. Right-click and select **Execute GraphQL Query** 6. When prompted: * Select your server * Choose a branch (usually "main") 7. The query results will appear in a new panel ## Step 7: Working with branches[​](#step-7-working-with-branches "Direct link to Step 7: Working with branches") Let's create a new branch for development work. 1. In the Infrahub Servers tree view, right-click on your server 2. Select **New Branch** 3. Enter a branch name (for example, "feature-network-schema") 4. Optionally add a description 5. The new branch will appear under your server in the tree You can now execute queries against this branch by selecting it when prompted during query execution. ## Next steps[​](#next-steps "Direct link to Next steps") Congratulations! You've successfully: * Installed and configured the Infrahub VSCode extension * Connected to an Infrahub server * Created a schema with validation * Executed a GraphQL query * Created a development branch ### What to explore next[​](#what-to-explore-next "Direct link to What to explore next") * **[How to Run Transforms and Artifacts](/vscode/guides/running-transforms.md)**: Execute Jinja2 and Python transforms directly from VSCode * **[How to Configure Multiple Servers](/vscode/guides/configure-multiple-servers.md)**: Work with development, staging, and production environments * **[Understanding Schema Validation](/vscode/topics/schema-validation.md)**: Deep dive into how the extension validates your schemas * **[Security Configuration and Best Practices](/vscode/topics/security-configuration.md)**: Comprehensive guide to TLS settings, API tokens, and security practices * **[Extension Commands Reference](/vscode/reference/commands-settings.md)**: Complete list of available commands and settings ### Troubleshooting tips[​](#troubleshooting-tips "Direct link to Troubleshooting tips") If you encounter issues: * **Server connection fails**: Verify the server URL and check if the server is running * **Schema validation errors**: Check the Problems panel for specific error messages * **GraphQL queries fail**: Ensure the schema is loaded in Infrahub and you're querying the correct branch * **TLS certificate errors**: If connecting to a server with self-signed certificates, add `"tls_insecure": true` to your server configuration * **"Server unreachable" status**: Check network connectivity, firewall settings, and ensure the Infrahub server is accessible ## Further resources[​](#further-resources "Direct link to Further resources") * [Infrahub Documentation](https://docs.infrahub.app) * [Extension GitHub Repository](https://github.com/opsmill/infrahub-vscode) * [Infrahub Community Discord](https://discord.gg/infrahub) --- # Welcome to Infrahub academy Infrahub Academy offers a collection of practical, hands-on tutorials designed to help you master Infrahub's powerful capabilities. These step-by-step guides will walk you through key features and workflows, providing real-world experience with the platform. ## About our tutorials[​](#about-our-tutorials "Direct link to About our tutorials") Each tutorial in the Academy is: * **Hands-on**: Learn by doing with practical exercises * **Progressive**: Start with basics and build to advanced topics * **Scenario-based**: Focused on real-world use cases * **Self-contained**: Complete each tutorial at your own pace ## Tutorial categories[​](#tutorial-categories "Direct link to Tutorial categories") Our tutorials are organized to support your learning journey: * **Getting started**: Essential tutorials for new users to learn Infrahub basics * **Advanced tutorials**: Complex scenarios and specialized use cases tip Each tutorial includes estimated completion time and prerequisites, so you can choose the right learning path for your current knowledge level and available time. --- # Generate and deploy your first network configuration using Infrahub | | | | ----------------------- | ---------------------------------------------------------- | | **Prerequisites:** | Basic experience with Infrahub, Ansible, Jinja and GraphQL | | **Time to Completion:** | Approximately 1 hour | Hands-on Learning This tutorial is supported by an Instruqt track. Instruqt is an online lab platform providing a pre-configured environment so you can focus on learning with hands-on experience. ## Introduction[​](#introduction "Direct link to Introduction") Infrastructure as Code is essential for modern infrastructure management, but traditional tools often lack proper data organization and version control capabilities. Infrahub solves this challenge by providing a centralized hub where infrastructure data, templates, and deployment logic work together seamlessly. In this tutorial, you'll learn how to leverage Infrahub's artifact generation system to create and deploy network device configurations based on structured data and templates. ## What you'll learn[​](#what-youll-learn "Direct link to What you'll learn") By the end of this hands-on tutorial, you'll master the following skills: * Creating focused GraphQL queries to extract specific infrastructure data * Building reusable Jinja2 templates for generating device configurations * Integrating queries and templates using Infrahub's Transformation system * Testing configuration changes safely using Infrahub's branching capabilities * Validating generated artifacts before deployment to production ## Get started[​](#get-started "Direct link to Get started") Ready to begin your journey with network configuration in Infrahub? Click the link below to access the hands-on laboratory environment: [Generate Network Configurations with Infrahubhttps://play.instruqt.com/opsmill/invite/hqia0uwvqe94](https://play.instruqt.com/opsmill/invite/hqia0uwvqe94) --- # Infrahub introduction | | | | ----------------------- | ------------------------------------------ | | **Prerequisites:** | No prior experience with Infrahub required | | **Time to Completion:** | Approximately 30 minutes | Hands-on Learning This tutorial is supported by an Instruqt track. Instruqt is an online lab platform providing a pre-configured environment so you can focus on learning with hands-on experience. ## Introduction[​](#introduction "Direct link to Introduction") Infrahub unifies infrastructure management by combining Git-like version control with a flexible graph database. This solves the common challenges of fragmented tools, inconsistent data, and difficult collaboration. This tutorial provides a ready-to-use environment where you can explore Infrahub's core features without setup overhead. You'll engage with practical exercises that demonstrate how Infrahub centralizes and streamlines infrastructure data management. Perfect for both curious newcomers and technical evaluators, this introduction builds a foundation for understanding how Infrahub can transform your infrastructure workflows. ## What you'll learn[​](#what-youll-learn "Direct link to What you'll learn") In this introductory track, we've designed a focused learning experience that will help you: * Understand how Infrahub's core concepts work in practice * Determine whether Infrahub aligns with your infrastructure management needs * Gain the fundamental knowledge needed to explore more advanced topics * Experience the platform's key features through hands-on exercises Whether you're a network engineer, a DevOps professional, or an infrastructure architect, this introduction will provide valuable insights into how Infrahub can transform your approach to infrastructure management. ## Get started[​](#get-started "Direct link to Get started") Ready to begin your Infrahub journey? Click the link below to access the hands-on laboratory environment: [Getting Started with Infrahubhttps://play.instruqt.com/opsmill/invite/7jdp4cuqbcvb](https://play.instruqt.com/opsmill/invite/7jdp4cuqbcvb) --- # Build a check in Infrahub By the end of this tutorial you will have built, deployed, and validated a custom check that enforces a naming convention on tags. You will set up sample data, write a GraphQL query, implement the check logic in Python, configure it in `.infrahub.yml`, deploy it via a connected Git repository, and verify it runs against a proposed change. Checks in Infrahub allow you to validate data based on custom business logic. They are defined in [external repositories](/git-integration/overview.md) and can be developed and tested locally using [infrahubctl check](/infrahubctl/infrahubctl-check.md). Global vs. Targeted Checks If you want to know more about the differences between these two types, refer to the topic on [Checks & Validation](/checks/overview.md). **TL;DR:** * **Global Checks**: Apply to all instances of a specific object type in the database * **Targeted Checks**: Apply only to specific instances that you select (by adding them to a group) ## What you'll build[​](#what-youll-build "Direct link to What you'll build") In this guide, you'll build a check that: 1. Reads information about tag objects from the database 2. Validates that the tags follow specific naming conventions using regex patterns 3. Automatically runs when changes to tags are proposed ## Steps overview[​](#steps-overview "Direct link to Steps overview") 1. Prepare sample data in your Infrahub instance 2. Create a GraphQL query to fetch data from Infrahub 3. Implement a Python check that validates the data 4. Configure the check in an `.infrahub.yml` file 5. Test the check locally with infrahubctl 6. Deploy the check to Infrahub 7. Validate that the check works in Infrahub's CI pipeline ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before you begin, you'll need: * Basic knowledge of Infrahub, Python, GraphQL, YAML, and Git * Familiarity with Checks and how they work in Infrahub (see [Checks & Validation](/checks/overview.md)) * An Infrahub instance running locally or remotely * A Git repository connected to Infrahub (see [Connect a repository](/git-integration/connect-repository.md)) * [infrahubctl](/infrahubctl/infrahubctl.md) installed and configured locally * The repository cloned where you'll develop the check ## Setting up your workspace[​](#setting-up-your-workspace "Direct link to Setting up your workspace") Version Control & Branching We recommend creating a new branch in your Git repository for your check development to avoid affecting the main branch. In this guide, we'll use a branch named `create-tag-check`. ``` git checkout -b create-tag-check git push --set-upstream origin create-tag-check ``` ## Step 1: Preparing sample data[​](#step-1-preparing-sample-data "Direct link to Step 1: Preparing sample data") First, let's create some sample tag data to test our check against. warning This step is only necessary if you don't already have the objects affected by the check in your instance. important For targeted checks you will need to create a group that the check will target. Create three tags with different naming patterns: * `color-red` - Follows the "color-\[name]" pattern (will pass validation) * `color-green` - Follows the "color-\[name]" pattern (will pass validation) * `blue` - Doesn't follow the pattern (will fail our validation check) You can create these tags using the Infrahub UI or in batch using object file: * Targeted Check * Global Check For targeted checks, you will also need to create a group that includes the tags you want to validate. * Using Object File * Using the UI 1. Create a file named `groups.yml` in the `objects` directory of your repository: objects/groups.yml ``` --- apiVersion: infrahub.app/v1 kind: Object spec: kind: CoreStandardGroup data: - name: colored_tags description: Group for colored tags ``` 2. Create a file named `tags.yml` in the `objects` directory of your repository: objects/tags.yml ``` --- apiVersion: infrahub.app/v1 kind: Object spec: kind: BuiltinTag data: - name: "color-red" description: "The red tag" member_of_groups: - ["colored_tags"] - name: "color-green" description: "The green tag" member_of_groups: - ["colored_tags"] - name: "blue" description: "The blue tag" member_of_groups: - ["colored_tags"] ``` 2. Use infrahubctl to load the objects: ``` infrahubctl object load objects/groups.yml --branch=create-tag-check infrahubctl object load objects/tags.yml --branch=create-tag-check ``` 1. Open the **Infrahub UI** in your browser 2. Select the branch `create-tag-check` 3. Navigate to the **Groups** view (Object Management > Groups) 4. Create a group * **Name**: `colored_tags`, **Description**: `Group for colored tags` 5. Navigate to the **Tags** view (Other > Tags) 6. Create the tags * **Name**: `color-red`, **Description**: `The red tag`, **Member of Group**: `colored_tags` * **Name**: `color-green`, **Description**: `The green tag`, **Member of Group**: `colored_tags` * **Name**: `blue`, **Description**: `The blue tag`, **Member of Group**: `colored_tags` * Using Object File * Using the UI 1. Create a file named `tags.yml` in the `objects` directory of your repository: objects/tags.yml ``` --- apiVersion: infrahub.app/v1 kind: Object spec: kind: BuiltinTag data: - name: "color-red" description: "The red tag" - name: "color-green" description: "The green tag" - name: "blue" description: "The blue tag" ``` 2. Use infrahubctl to load the objects: ``` infrahubctl object load objects/tags.yml --branch=create-tag-check ``` 1. Open the **Infrahub UI** in your browser 2. Select the branch `create-tag-check` 3. Navigate to the **Tags** view (Other > Tags) 4. Create the tags * **Name**: `color-red`, **Description**: `The red tag` * **Name**: `color-green`, **Description**: `The green tag` * **Name**: `blue`, **Description**: `The blue tag` success To verify the tags were created successfully, navigate to the Tags view in the Infrahub UI (Other > Tags), select the `create-tag-check` branch, and check that the tags appear in the list. ## Step 2: Create the GraphQL query[​](#step-2-create-the-graphql-query "Direct link to Step 2: Create the GraphQL query") Next, create a GraphQL query that fetches the data your check needs to process. ### Create a query to fetch the tag data[​](#create-a-query-to-fetch-the-tag-data "Direct link to Create a query to fetch the tag data") Create a `tags_query.gql` file in the `queries` directory of your repository: * Targeted Check * Global Check queries/tags\_query.gql ``` query TagsQuery($name: String!) { BuiltinTag(name__value: $name) { edges { node { name { value } description { value } } } } } ``` queries/tags\_query.gql ``` query TagsQuery { BuiltinTag { edges { node { name { value } description { value } } } } } ``` ### Test the query[​](#test-the-query "Direct link to Test the query") To test the query you can use **Infrahub's GraphQL Sandbox**. GraphQL Sandbox Access the sandbox by clicking your user icon in the bottom left corner and selecting **GraphQL Sandbox**. This tool allows you to run GraphQL queries and provides an interactive way to explore the schema. * Targeted Check * Global Check 1. Copy the above GraphQL query in the main section 2. In the **variables** section, add: ``` { "name": "blue" } ``` 3. Click the **Execute** button, this should return a response like: ``` { "data": { "BuiltinTag": { "edges": [ { "node": { "name": { "value": "blue" }, "description": { "value": "The blue tag" } } } ] } } } ``` 1. Copy the above GraphQL query in the main section 2. Click the **Execute** button, this should return a response like: ``` { "data": { "BuiltinTag": { "edges": [ { "node": { "name": { "value": "blue" }, "description": { "value": "The blue tag" } } }, { "node": { "name": { "value": "color-green" }, "description": { "value": "The green tag" } } }, { "node": { "name": { "value": "color-red" }, "description": { "value": "The red tag" } } } ] } } } ``` success You now have a working GraphQL query that retrieves the necessary data from Infrahub. You might want to keep the result of the query for later reference, as it will be useful when implementing the check. ## Step 3: Implement the check logic[​](#step-3-implement-the-check-logic "Direct link to Step 3: Implement the check logic") Now create a Python class that implements your check logic. The check makes sure that every tag's name follows the pattern `color-[name]`. The class must: * Inherit from `InfrahubCheck` from the [Python SDK](/python-sdk/introduction.md) * Define a `query` class attribute that references your GraphQL query by name * Implement a `validate()` method that processes data from your GraphQL query * Log errors using `log_error()` when validation fails 1. Create a file called `tags_check.py` in your `checks` directory: checks/tags\_check.py ``` import re from infrahub_sdk.checks import InfrahubCheck RE_TAG = re.compile(r"^color-[a-z]+") class ColorTagsCheck(InfrahubCheck): query = "tags_query" # This references the query name in .infrahub.yml def validate(self, data): for tag in data["BuiltinTag"]["edges"]: if not RE_TAG.match(tag["node"]["name"]["value"]): self.log_error( message=f"Invalid tag name: {tag['node']['name']['value']}. Tag names must follow pattern 'color-[name]'", object_id=tag["node"]["name"]["value"], object_type="BuiltinTag" ) ``` ## Step 4: Configure the check in the .infrahub.yml file[​](#step-4-configure-the-check-in-the-infrahubyml-file "Direct link to Step 4: Configure the check in the .infrahub.yml file") Now that you have your GraphQL query and Python check, edit your [.infrahub.yml](/git-integration/infrahub-yml.md) file to tie everything together. 1. Add the following configuration to the file `.infrahub.yml`: * Targeted Check * Global Check .infrahub.yml ``` --- queries: - name: tags_query file_path: queries/tags_query.gql check_definitions: - name: check_color_tags_name class_name: ColorTagsCheck file_path: checks/tags_check.py targets: colored_tags # This specifies the group to target parameters: name: "name__value" ``` .infrahub.yml ``` --- queries: - name: tags_query file_path: queries/tags_query.gql check_definitions: - name: check_color_tags_name class_name: ColorTagsCheck file_path: checks/tags_check.py ``` For a complete explanation of the `.infrahub.yml` file format, see the [infrahub.yml topic](/git-integration/infrahub-yml.md). 2. Verify the configuration Check that your `.infrahub.yml` file is correctly formatted by listing available checks: ``` infrahubctl check --list ``` success If successful, you'll see output like: * Targeted Check * Global Check ``` Python checks defined in repository: 1 check_color_tags_name (checks/tags_check.py::ColorTagsCheck) Target: colored_tags ``` ``` Python checks defined in repository: 1 check_color_tags_name (checks/tags_check.py::ColorTagsCheck) Target: -global- ``` ## Step 5: Test the check locally[​](#step-5-test-the-check-locally "Direct link to Step 5: Test the check locally") Before deploying your check to Infrahub, test it locally using the `infrahubctl` command-line tool. 1. First, verify that your check is correctly configured: ``` infrahubctl check --list ``` 2. Run the check against your branch by specifying its name: ``` infrahubctl check check_color_tags_name --branch=create-tag-check ``` success If successful, you'll see output like: * Targeted Check * Global Check ``` INFO HTTP Request: GET http://localhost:8000/api/schema?branch=create-tag-check2 "HTTP/1.1 200 OK" INFO HTTP Request: POST http://localhost:8000/graphql/create-tag-check2 "HTTP/1.1 200 OK" INFO HTTP Request: POST http://localhost:8000/graphql/create-tag-check2 "HTTP/1.1 200 OK" INFO HTTP Request: POST http://localhost:8000/graphql/create-tag-check2 "HTTP/1.1 200 OK" INFO HTTP Request: POST http://localhost:8000/graphql/create-tag-check2 "HTTP/1.1 200 OK" INFO tags_check::ColorTagsCheck: PASSED - {'name': 'color-red'} INFO HTTP Request: POST http://localhost:8000/graphql/create-tag-check2 "HTTP/1.1 200 OK" INFO tags_check::ColorTagsCheck: PASSED - {'name': 'color-green'} INFO HTTP Request: POST http://localhost:8000/graphql/create-tag-check2 "HTTP/1.1 200 OK" ERROR tags_check::ColorTagsCheck: FAILED - {'name': 'blue'} ERROR Invalid tag name: blue. Tag names must follow pattern 'color-[name]' ``` ``` INFO HTTP Request: POST http://localhost:8000/graphql/create-tag-check "HTTP/1.1 200 OK" ERROR tags_check::ColorTagsCheck: FAILED ERROR Invalid tag name: blue. Tag names must follow pattern 'color-[name]' ``` The error message shows that our check correctly identified the tag named "blue" as invalid because it doesn't match our required naming pattern. ## Step 6: Deploy the check to Infrahub[​](#step-6-deploy-the-check-to-infrahub "Direct link to Step 6: Deploy the check to Infrahub") Now that you've tested your check and confirmed it works locally, deploy it to Infrahub by pushing your code to the repository. ### Verify repository structure[​](#verify-repository-structure "Direct link to Verify repository structure") Ensure your repository has the following structure before committing: ``` your-repository/ ├── .infrahub.yml ├── checks/ │ └── tags_check.py └── queries/ └── tags_query.gql ``` note Depending on your organization, you might also have `schemas` and `objects` directories in your repository. ### Commit and push your code[​](#commit-and-push-your-code "Direct link to Commit and push your code") Upload your check code to the repository: ``` git add . git commit -m "Add tags naming convention check" git push ``` ### Confirm the check is imported[​](#confirm-the-check-is-imported "Direct link to Confirm the check is imported") After pushing your changes, confirm that the check is imported correctly by checking the Infrahub UI: 1. Open the **Infrahub UI** in your browser 2. Select the `create-tag-check` branch 3. Navigate to the **Check Definition view** (Actions > Check Definitions) 4. You should see your `check_color_tags_name` check listed there warning If you don't see your check, verify the repository status in the **Repository view** (Integrations > Git Repositories) and ensure the sync status is `synced`. success Your check is now successfully deployed to Infrahub. ## Step 7: Validate that the check works[​](#step-7-validate-that-the-check-works "Direct link to Step 7: Validate that the check works") Let's verify that your check correctly validates data by creating a proposed change from your development branch `create-tag-check` to `main`. ### Create a proposed change[​](#create-a-proposed-change "Direct link to Create a proposed change") 1. In the Infrahub UI, navigate to the **Proposed Changes** view 2. Click **New proposed change** 3. Create the **Proposed Change** with the following parameters: * Source branch: `create-tag-check` * Destination branch: `main` * Name: `Add tag naming convention check` ### Verify check results[​](#verify-check-results "Direct link to Verify check results") After the proposed change is created: 1. Navigate to the **Checks** tab of your proposed change 2. Wait for the **User** CI check to complete success You should see that the check has failed because the tag "blue" doesn't match the expected pattern. ![Check details in a proposed change](/assets/images/check_proposed_change-9eae6433a402959650c2febf94c00388.png) ### Fix the validation issue and merge[​](#fix-the-validation-issue-and-merge "Direct link to Fix the validation issue and merge") Now let's fix the issue with the invalid tag and complete the merge: 1. Navigate to **Tags** view (Other > Tags) 2. Select the `create-tag-check` branch 3. Delete the tag `blue` (as it doesn't follow the naming convention) * Alternatively, you could rename it to `color-blue` to match the pattern * For a targeted check you could also remove the tag `blue` from the `colored_tags` group 4. Return to the **Proposed Change** view 5. Navigate to the **Data** and **Files** tabs to review the changes 6. Navigate to the **Checks** 7. Hit the **Retry** button to re-run the check 8. Verify that all checks now pass 9. On the **Overview** tab, click the **Merge** button to merge your changes to main success Your check is now deployed to Infrahub in the `main` branch and ready to be used. Any future tag creations will be validated against your naming convention rule. ## Next steps[​](#next-steps "Direct link to Next steps") Now that you've created a basic check for data validation, you can expand your validation capabilities: * Create more complex checks that validate relationships between objects * Build checks that validate business logic across multiple object types * Add detailed error messages with recommendations to help users understand and fix validation failures success Checks are a powerful way to enforce business rules in your data. By implementing validation rules that match your organization's requirements, you ensure data consistency and prevent configuration errors before they reach production environments. --- # Build your first schema By the end of this tutorial you will have a working schema for network devices and interfaces, covering nodes, attributes, relationships, and generic abstractions. You'll load each version into a branch and verify the results. This tutorial uses a network device example to demonstrate key schema concepts. Real-world schemas involve additional complexity, but the patterns here apply broadly. For conceptual background on schemas and design patterns, see [About schema](/schema/overview.md). ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Infrahub running locally or in your environment * [`infrahubctl`](/infrahubctl/infrahubctl.md) command-line tool installed * Basic understanding of YAML syntax Schema validation Enable [schema validation](/reference/schema-validation.md) in your editor to catch errors while developing schema files. ## Alternative learning resources[​](#alternative-learning-resources "Direct link to Alternative learning resources") ### Hands-on lab[​](#hands-on-lab "Direct link to Hands-on lab") Try the interactive [Infrahub Schema Introduction](https://opsmill.instruqt.com/pages/labs) lab for hands-on practice with schemas. ### Video tutorials[​](#video-tutorials "Direct link to Video tutorials") Learn advanced schema concepts in this livestream recording: ## Step 1: Create basic nodes with attributes[​](#step-1-create-basic-nodes-with-attributes "Direct link to Step 1: Create basic nodes with attributes") Create a file named `schema_guide.yml` in a location you can access easily, such as `/tmp/schema_guide.yml`. Start by defining two basic nodes in the `Network` namespace: * `Device`: Represents network devices with hostname and model attributes * `Interface`: Represents network interfaces with name and description attributes ``` --- version: "1.0" nodes: - name: Device namespace: Network human_friendly_id: ['hostname__value'] attributes: - name: hostname kind: Text unique: true - name: model kind: Text - name: Interface namespace: Network attributes: - name: name kind: Text - name: description kind: Text optional: true ``` Human-friendly IDs The `human_friendly_id` on the `hostname` attribute lets you use the hostname value instead of the system-generated ID in queries and mutations. ### Load the schema into Infrahub[​](#load-the-schema-into-infrahub "Direct link to Load the schema into Infrahub") Create a new branch for your schema changes: ``` infrahubctl branch create network-device-schema ``` Load the schema into the branch: ``` infrahubctl schema load --branch network-device-schema /tmp/schema_guide.yml ``` View your schema in the [Web UI](http://localhost:8000/schema?branch=network-device-schema) under Object Management > Schemas. ![Schema page screenshot](/assets/images/create_schema_1-0c77748efef18604d3451c6b3609991d.png) ### Create test data[​](#create-test-data "Direct link to Create test data") Test your schema by creating a device and interface: * GraphQL * Web interface * cURL Open the GraphQL sandbox (bottom left of the web interface) and execute: ``` mutation { NetworkDeviceCreate(data: {hostname: {value: "atl1-edge1"}, model: {value: "Cisco ASR1002-HX"}}) { ok object { id } } NetworkInterfaceCreate(data: {name: {value: "Ethernet1"}, description: {value: "WAN interface"}}) { ok object { id } } } ``` 1. Navigate to **Objects > Device** in the left menu 2. Click **Add Device** 3. Enter `atl1-edge1` as Hostname and `Cisco ASR1002-HX` as Model 4. Click **Save** 5. Navigate to **Objects > Interface** 6. Create an interface with `Ethernet1` as Name and `WAN interface` as Description Replace the IP address and API key with your actual values: ``` curl -X POST http://localhost:8000/graphql/network-device-schema \ -H "Content-Type: application/json" \ -H "X-INFRAHUB-KEY: 1802eed5-eeb7-cc45-2e4d-c51de9d66cba" \ -d '{"query": "mutation { NetworkDeviceCreate(data: {hostname: {value: \"atl1-edge1\"}, model: {value: \"Cisco ASR1002-HX\"}}) { ok object { id } } NetworkInterfaceCreate(data: {name: {value: \"Ethernet1\"}, description: {value: \"WAN interface\"}}) { ok object { id } } }"}' ``` Verify your objects were created by navigating to **Objects** and selecting **Device** or **Interface**. ## Step 2: Add relationships between nodes[​](#step-2-add-relationships-between-nodes "Direct link to Step 2: Add relationships between nodes") Connect your device and interface nodes with relationships. This creates meaningful associations between your data. Add these relationships to your schema: 1. **Device to interfaces**: One device can have many interfaces (component relationship) 2. **Interface to device**: Each interface belongs to one device (parent relationship) Replace your `schema_guide.yml` content with: ``` --- version: "1.0" nodes: - name: Device namespace: Network human_friendly_id: ['hostname__value'] attributes: - name: hostname kind: Text unique: true - name: model kind: Text relationships: - name: interfaces cardinality: many peer: NetworkInterface kind: Component - name: Interface namespace: Network attributes: - name: name kind: Text - name: description kind: Text optional: true relationships: - name: device cardinality: one peer: NetworkDevice optional: false kind: Parent ``` ### Load the updated schema[​](#load-the-updated-schema "Direct link to Load the updated schema") Create a new branch for the relationship changes: ``` infrahubctl branch create network-device-relations ``` Load the updated schema: ``` infrahubctl schema load --branch network-device-relations /tmp/schema_guide.yml ``` ### Test the relationships[​](#test-the-relationships "Direct link to Test the relationships") Create connected objects using this GraphQL mutation: ``` mutation { NetworkDeviceCreate(data: {hostname: {value: "atl1-edge1"}, model: {value: "Cisco ASR1002-HX"}}) { ok object { id } } NetworkInterfaceCreate(data: {name: {value: "Ethernet1"}, description: {value: "WAN interface"}, device: {hfid: "atl1-edge1"}}) { ok object { id } } } ``` In the Web UI, view the device details to see the relationship to the Ethernet1 interface. ![Schema page screenshot](/assets/images/create_schema_3-cc070529d09bc7eb1df4e51fe3a556eb.png) ## Step 3: Create generic nodes for abstraction[​](#step-3-create-generic-nodes-for-abstraction "Direct link to Step 3: Create generic nodes for abstraction") Real network devices have different interface types with shared and unique characteristics. Use generic nodes to model this abstraction. Physical interfaces (like Ethernet1) have properties like speed and cable connections. Logical interfaces (like Vlan1) don't have these physical properties but share common attributes like name and description. ### Create a generic interface[​](#create-a-generic-interface "Direct link to Create a generic interface") Replace your schema with this structure using generics: ``` --- version: "1.0" generics: - name: Interface namespace: Network attributes: - name: name kind: Text - name: description kind: Text optional: true relationships: - name: device cardinality: one peer: NetworkDevice kind: Parent optional: false nodes: - name: Device namespace: Network human_friendly_id: ['hostname__value'] attributes: - name: hostname kind: Text unique: true - name: model kind: Text relationships: - name: interfaces cardinality: many peer: NetworkInterface kind: Component - name: PhysicalInterface namespace: Network inherit_from: - NetworkInterface attributes: - name: speed kind: Number - name: LogicalInterface namespace: Network inherit_from: - NetworkInterface ``` ### Load the generic schema[​](#load-the-generic-schema "Direct link to Load the generic schema") Create a new branch for generic changes: ``` infrahubctl branch create network-device-generics ``` Load the schema: ``` infrahubctl schema load --branch network-device-generics /tmp/schema_guide.yml ``` ### Test different interface types[​](#test-different-interface-types "Direct link to Test different interface types") Create both physical and logical interfaces: ``` mutation { NetworkDeviceCreate(data: {hostname: {value: "atl1-edge1"}, model: {value: "Cisco ASR1002-HX"}}) { ok object { id } } NetworkPhysicalInterfaceCreate(data: {name: {value: "Ethernet1"}, description: {value: "WAN interface"}, speed: {value: 1000000000}, device: {hfid: "atl1-edge1"}}) { ok object { id } } NetworkLogicalInterfaceCreate(data: {name: {value: "Vlan1"}, description: {value: "SVI for VLAN 1"}, device: {hfid: "atl1-edge1"}}) { ok object { id } } } ``` View the device in the Web UI to see both interface types connected to the same device. ![Schema page screenshot](/assets/images/create_schema_generics-eecd428f91e064e125bd10e3edfe2fcd.png) ## Step 4: Improve the schema with migrations[​](#step-4-improve-the-schema-with-migrations "Direct link to Step 4: Improve the schema with migrations") Refine your schema using Infrahub's schema migration features. These changes demonstrate how to evolve schemas while preserving existing data. Make these improvements: 1. Add `mtu` and `enabled` attributes to the generic `NetworkInterface` 2. Remove the `description` attribute from the generic `NetworkInterface` 3. Set a default value for the `speed` attribute of `NetworkPhysicalInterface` 4. Rename the `model` attribute to `device_type` 5. Add labels for better user experience 6. Define uniqueness constraints ### Get the attribute ID for migration[​](#get-the-attribute-id-for-migration "Direct link to Get the attribute ID for migration") Attribute ID required To rename an attribute, you need its current ID. Find the `model` attribute ID on the [NetworkDevice schema page](http://localhost:8000/schema?branch=network-device-generics\&kind=NetworkDevice) in the Web UI. Replace your schema content (update the `id` value with the actual model attribute ID): ``` --- version: "1.0" generics: - name: Interface namespace: Network attributes: - name: name kind: Text label: Name - name: description state: absent kind: Text optional: true label: Description - name: mtu kind: Number label: MTU optional: false default_value: 1500 - name: enabled label: Enabled kind: Boolean optional: false default_value: false relationships: - name: device label: Device cardinality: one peer: NetworkDevice kind: Parent optional: false nodes: - name: Device namespace: Network human_friendly_id: ['hostname__value'] attributes: - name: hostname kind: Text label: Hostname unique: true - name: device_type label: Device Type kind: Text id: 17bcf8a7-9c03-4a6a-3295-c51345cb1c33 relationships: - name: interfaces label: Interfaces cardinality: many peer: NetworkInterface kind: Component - name: PhysicalInterface namespace: Network uniqueness_constraints: - ["device", "name__value"] inherit_from: - NetworkInterface attributes: - name: speed label: Speed (bps) kind: Number default_value: 1000000000 - name: LogicalInterface namespace: Network uniqueness_constraints: - ["device", "name__value"] inherit_from: - NetworkInterface ``` ### Preview schema changes[​](#preview-schema-changes "Direct link to Preview schema changes") Use the check command to see what changes will be applied: ``` infrahubctl schema check --branch network-device-generics /tmp/schema_guide.yml ``` This shows a diff of changes before applying them. ### Apply the migration[​](#apply-the-migration "Direct link to Apply the migration") Load the improved schema: ``` infrahubctl schema load --branch network-device-generics /tmp/schema_guide.yml ``` Migration complete Infrahub automatically migrates existing data to match the new schema structure. View the updated schema in the [Web UI](http://localhost:8000/schema?branch=network-device-generics). ## Next steps[​](#next-steps "Direct link to Next steps") Now that you've created a basic schema: * Learn about [schema validation](/reference/schema-validation.md) for development workflows * Explore [Nodes & attributes](/schema/nodes-and-attributes.md) for advanced concepts * Explore [Relationships](/schema/relationships.md) for relationship kinds and direction * Learn how to [create and load schema files](/schema/create-and-load.md) from existing sources * Set up [external repositories](/git-integration/connect-repository.md) to manage schemas with Git --- # Build chained generators By the end of this tutorial you will have wired two layers of [modular Generators](/generators/modular.md) together — fabric → pod → rack — using a checksum attribute and Infrahub's [event framework](/events/overview.md). You will have added a `GeneratorTarget` generic with a `checksum` attribute to your downstream node kinds, implemented a `GeneratorMixin` that writes the checksum to downstream targets as the last step of `generate()`, built downstream Generators that validate upstream completeness before doing any work, and created `CoreGeneratorAction` and `CoreNodeTriggerRule` objects so the chain runs automatically when an upstream layer finishes. You will leave with a concrete model of how a checksum makes Generator chaining idempotent — re-runs that produce the same output do not re-trigger the next layer. **Prerequisites:** * Familiarity with [Generators](/generators/overview.md) and [Build your first generator](/academy/tutorials/generators/build-your-first-generator.md) * Understanding of the [modular Generators concept](/generators/modular.md) * Basic understanding of [event rules and actions](/events/overview.md) ## How the pattern works[​](#how-the-pattern-works "Direct link to How the pattern works") **The problem:** Generator A creates objects that Generator B depends on. Generator B's targets exist before A runs, but B should only execute after A has finished its work. **The solution:** Generator A computes a hash (checksum) of all the node IDs it created or touched during execution. It writes this checksum to an attribute on Generator B's target objects. A `CoreNodeTriggerRule` watches for checksum changes on those targets and fires a `CoreGeneratorAction` to run Generator B. **Why a checksum?** It's idempotent. If Generator A runs again and produces the same output, the checksum doesn't change, so Generator B doesn't re-trigger. If the output differs (new nodes, removed nodes), the checksum changes and execution continues to the next layer. This makes re-runs safe by default. ``` Generator A finishes → calculates checksum of all created/touched node IDs → writes checksum to each downstream target (e.g., pod.checksum = "abc123") → Infrahub emits infrahub.node.updated event for each target → CoreNodeTriggerRule matches: kind=NetworkPod, attribute=checksum, value_match=any → CoreGeneratorAction runs Generator B for the updated target ``` ## Step 1: Add a checksum attribute to target objects[​](#step-1-add-a-checksum-attribute-to-target-objects "Direct link to Step 1: Add a checksum attribute to target objects") To signal downstream Generators, the target objects need an attribute that the upstream Generator can write to. The recommended approach is to define a schema generic with a `checksum` attribute and have target nodes inherit from it. ### Define the generic[​](#define-the-generic "Direct link to Define the generic") ``` # schemas/generator.yml version: "1.0" generics: - name: Target namespace: Generator include_in_menu: false attributes: - name: checksum kind: Text optional: true ``` ### Inherit from the generic[​](#inherit-from-the-generic "Direct link to Inherit from the generic") Any node that an upstream Generator should be able to trigger inherits from `GeneratorTarget`: ``` # schemas/logical_design.yml nodes: - name: Pod namespace: Network inherit_from: - NetworkBuildingBlock - GeneratorTarget # Adds the checksum attribute # ... rest of the node definition ``` **Why a generic?** It keeps the pattern reusable. Any node kind that participates in a modular Generator setup just inherits from `GeneratorTarget` — no need to manually add checksum attributes to each schema. ## Step 2: Calculate and write the checksum in your Generator[​](#step-2-calculate-and-write-the-checksum-in-your-generator "Direct link to Step 2: Calculate and write the checksum in your Generator") After the Generator creates or modifies its objects, it needs to compute a checksum and write it to the downstream targets. This should be the **last step** in your `generate()` method — only signal completion after all work is done. ### The checksum mixin[​](#the-checksum-mixin "Direct link to The checksum mixin") The tracking context (`self.client.group_context`) automatically collects the IDs of all nodes and groups the Generator interacted with during execution. These make an ideal checksum input. ``` # src/your_bundle/generator.py import hashlib class GeneratorMixin: def calculate_checksum(self) -> str: """Compute a checksum from all node IDs touched during this generator run.""" related_ids = ( self.client.group_context.related_group_ids + self.client.group_context.related_node_ids ) sorted_ids = sorted(related_ids) joined = ",".join(sorted_ids) return hashlib.sha256(joined.encode("utf-8")).hexdigest() ``` ### Using the mixin in a Generator[​](#using-the-mixin-in-a-generator "Direct link to Using the mixin in a Generator") ``` # generators/generate_fabric.py from infrahub_sdk.generator import InfrahubGenerator from your_bundle.generator import GeneratorMixin from your_bundle.protocols import NetworkPod class FabricGenerator(InfrahubGenerator, GeneratorMixin): async def generate(self, data: dict) -> None: fabric_id = data["NetworkFabric"]["edges"][0]["node"]["id"] # ... create fabric-level objects (super spines, IP pools, etc.) ... # Last step: signal downstream targets await self.update_checksum(fabric_id) async def update_checksum(self, fabric_id: str) -> None: pods = await self.client.filters(kind=NetworkPod, parent__ids=[fabric_id]) checksum = self.calculate_checksum() for pod in pods: if pod.checksum.value != checksum: pod.checksum.value = checksum await pod.save(allow_upsert=True) ``` **Key points:** * The `if pod.checksum.value != checksum` guard prevents unnecessary saves — and unnecessary triggers — when the Generator is re-run with the same result. * `update_checksum()` must be the **last step** in `generate()`. Only signal downstream after all work is complete. * The checksum naturally reflects everything the Generator touched, because it's derived from the SDK tracking context. ## Step 3: Validate upstream dependencies before generating[​](#step-3-validate-upstream-dependencies-before-generating "Direct link to Step 3: Validate upstream dependencies before generating") In a modular Generator setup there is no central orchestrator sequencing execution. A checksum change fires a trigger per target — which means a downstream Generator could start before the upstream Generator has finished creating all its objects. Each Generator must therefore protect itself by validating that upstream work is complete before proceeding. Without this validation, a Generator might run against partial data and produce incomplete results or cryptic errors. ### The pattern[​](#the-pattern "Direct link to The pattern") Before doing its work, a downstream Generator should: 1. Query for expected upstream objects (for example, spine switches created by the pod Generator) 2. Compare the actual count against the expected count from configuration 3. Raise a clear `RuntimeError` if validation fails — don't proceed with partial data ### Example: PodGenerator validating the fabric layer[​](#example-podgenerator-validating-the-fabric-layer "Direct link to Example: PodGenerator validating the fabric layer") ``` # generators/generate_pod.py from infrahub_sdk.generator import InfrahubGenerator from your_bundle.generator import GeneratorMixin from your_bundle.protocols import NetworkDevice class PodGenerator(InfrahubGenerator, GeneratorMixin): async def generate(self, data: dict) -> None: # ... extract pod data from query ... # Guard: skip unsupported pod roles if self.pod_role in EXCLUDED_POD_ROLES: raise ValueError( f"Cannot run pod generator on {self.pod_name}: " f"{self.pod_role} is not supported by the generator!" ) # Validate upstream: are all super spine switches present? super_spine_switches = await self.client.filters( kind=NetworkDevice, pod__ids=[fabric_pod_id], role__value="super_spine" ) if self.expected_super_spine_count != len(super_spine_switches): raise RuntimeError( f"Cannot start pod generator on {self.pod_name}: " f"the fabric doesn't seem to be fully generated yet!" ) # Validate required configuration if not self.pod_spine_switch_template: raise RuntimeError( f"Cannot start pod generator on {self.pod_name}: " f"no spine switch template defined!" ) # ... proceed with generation ... # Last step: write checksum to downstream targets await self.update_checksum(self.pod_id) ``` ### Example: RackGenerator validating the pod layer[​](#example-rackgenerator-validating-the-pod-layer "Direct link to Example: RackGenerator validating the pod layer") ``` # generators/generate_rack.py from infrahub_sdk.generator import InfrahubGenerator from your_bundle.protocols import NetworkDevice class RackGenerator(InfrahubGenerator): async def generate(self, data: dict) -> None: # ... extract rack data from query ... # Validate upstream: are all spine switches present? spine_switches = await self.client.filters( kind=NetworkDevice, pod__ids=[self.pod_id], role__value="spine" ) if self.expected_spine_count != len(spine_switches): raise RuntimeError( f"Cannot start rack generator on {self.rack_name}: " f"the pod doesn't seem to be fully generated!" ) # ... proceed with generation ... ``` ### Why this matters[​](#why-this-matters "Direct link to Why this matters") * **Checksum fires per-target.** A pod's checksum could update before the fabric Generator has created all super spines. Without validation, the pod Generator would run against incomplete data. * **Compare count vs. expected.** The target's schema typically stores the expected count (for example, `amount_of_super_spines` on the fabric node). Query for actual objects and compare. * **Fail with a clear error.** Use `RuntimeError` with a message that identifies the target and what's missing. This shows up in task logs and makes debugging straightforward. * **This replaces orchestration.** Each Generator polices itself. No need for a central controller to sequence execution — if the upstream layer isn't ready, the Generator fails safely and will succeed on the next trigger. ### Two types of validation[​](#two-types-of-validation "Direct link to Two types of validation") | Type | What to check | Example | | -------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------ | | **Upstream completeness** | Are the expected objects present? | Compare `len(super_spine_switches)` against `expected_super_spine_count` | | **Required configuration** | Are necessary templates, pools, or settings defined? | Check that `pod_spine_switch_template` is not `None` | Both checks should happen at the top of `generate()`, before any objects are created. ## Step 4: Create trigger rules and actions[​](#step-4-create-trigger-rules-and-actions "Direct link to Step 4: Create trigger rules and actions") With the checksum being written to downstream targets, you need trigger rules to detect the change and fire the next Generator. You can define these as object files in your repository (recommended) or create them via the UI. ### Using object files (recommended)[​](#using-object-files-recommended "Direct link to Using object files (recommended)") Object files make trigger configuration version-controlled alongside your Generators. #### Define Generator actions[​](#define-generator-actions "Direct link to Define Generator actions") ``` # objects/triggers.yml --- apiVersion: infrahub.app/v1 kind: Object spec: kind: CoreGeneratorAction data: - name: run-pod-generator generator: generate-pod - name: run-rack-generator generator: generate-rack ``` #### Define trigger rules[​](#define-trigger-rules "Direct link to Define trigger rules") ``` # objects/triggers.yml (continued) --- apiVersion: infrahub.app/v1 kind: Object spec: kind: CoreNodeTriggerRule data: - name: trigger-pod-generator-checksum branch_scope: "other_branches" node_kind: NetworkPod mutation_action: "updated" action: run-pod-generator matches: kind: CoreNodeTriggerAttributeMatch data: - attribute_name: checksum value_match: any - name: trigger-rack-generator-checksum branch_scope: "other_branches" node_kind: LocationRack mutation_action: "updated" action: run-rack-generator matches: kind: CoreNodeTriggerAttributeMatch data: - attribute_name: checksum value_match: any ``` #### Reference in .infrahub.yml[​](#reference-in-infrahubyml "Direct link to Reference in .infrahub.yml") ``` # .infrahub.yml objects: - file_path: objects/triggers.yml ``` **Key configuration explained:** | Field | Value | Why | | ----------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `branch_scope` | `"other_branches"` | **Recommended.** Triggers only fire on non-default branches (within proposed changes). This means all Generators run in a branch where results can be reviewed before merging. Alternatives: `"all_branches"` (fires everywhere) or `"default_branch"` (fires only on main). | | `mutation_action` | `"updated"` | Fires when the target node is updated (the checksum write counts as an update). | | `value_match` | `any` | Fires on any change to the attribute, regardless of the new value. | | `action` | name of the action | References the `CoreGeneratorAction` by name. | Trigger rules loaded from object files are **active by default** — no separate activation step is needed. ### Beyond checksums: triggering on user-facing attributes[​](#beyond-checksums-triggering-on-user-facing-attributes "Direct link to Beyond checksums: triggering on user-facing attributes") The checksum handles the connection between Generators, but you may also want Generators to re-run when a user changes a relevant attribute directly. For example, if a user changes the number of spines on a pod: ``` - name: trigger-pod-generator-amount-of-spines branch_scope: "other_branches" node_kind: NetworkPod mutation_action: "updated" action: run-pod-generator matches: kind: CoreNodeTriggerAttributeMatch data: - attribute_name: amount_of_spines value_match: any - name: trigger-pod-generator-role branch_scope: "other_branches" node_kind: NetworkPod mutation_action: "updated" action: run-pod-generator matches: kind: CoreNodeTriggerAttributeMatch data: - attribute_name: role value_match: any ``` This gives you a complete event-driven system: downstream Generators run automatically when upstream Generators finish, **and** when users make relevant changes in the UI. ### Repeat for each layer[​](#repeat-for-each-layer "Direct link to Repeat for each layer") For each pair of (upstream Generator, downstream Generator), define one `CoreGeneratorAction` and one or more `CoreNodeTriggerRule` entries. A three-layer setup (fabric to pod to rack) needs two actions and at least two trigger rules. ### Using the UI (alternative)[​](#using-the-ui-alternative "Direct link to Using the UI (alternative)") If you prefer to create trigger rules via the Infrahub UI, follow the [creating event trigger rules and actions](/events/overview.md) guide. The configuration is the same: 1. Create a **Generator Action** (Actions > Create > Generator Action) pointing to the downstream Generator 2. Create a **Node Trigger Rule** (Trigger Rules > Create > Node Trigger) for the downstream target kind, with mutation action `updated` 3. Add an **Attribute Match** on the `checksum` attribute with value match `any` 4. Set the trigger to **active** UI-created triggers work identically but aren't version-controlled. ## Step 5: Disable CI-based execution (optional)[​](#step-5-disable-ci-based-execution-optional "Direct link to Step 5: Disable CI-based execution (optional)") Generators that are entirely event-driven should have their CI execution disabled to avoid double-runs: ``` # .infrahub.yml generator_definitions: - name: generate-pod file_path: "./generators/generate_pod.py" query: generate_pod targets: pods parameters: pod_name: name__value class_name: PodGenerator convert_query_response: false execute_in_proposed_change: false # Triggered by events, not CI execute_after_merge: false # Triggered by events, not CI ``` Set both `execute_in_proposed_change` and `execute_after_merge` to `false` for any Generator that's triggered via the checksum/trigger pattern. This prevents the Generator from running both as a CI check and as an event-triggered action. info This is a design choice. Some teams keep CI execution enabled for the first Generator (the entry point) and only use event triggers for downstream Generators. ## Step 6: Test the modular setup[​](#step-6-test-the-modular-setup "Direct link to Step 6: Test the modular setup") ### Run the first Generator manually[​](#run-the-first-generator-manually "Direct link to Run the first Generator manually") ``` infrahubctl generator generate-fabric --branch=my-test-branch fabric_name=my-fabric ``` ### Verify the checksum was written[​](#verify-the-checksum-was-written "Direct link to Verify the checksum was written") Check the downstream targets in the UI or via GraphQL: ``` query { NetworkPod(parent__name__value: "my-fabric") { edges { node { name { value } checksum { value } } } } } ``` You should see a checksum value on each pod. ### Verify downstream Generators triggered[​](#verify-downstream-generators-triggered "Direct link to Verify downstream Generators triggered") 1. Check if the downstream Generator ran by looking at the objects it should have created 2. Inspect the task logs in Infrahub for Generator execution entries 3. For each subsequent layer, verify that: * The upstream Generator wrote checksums to downstream targets * The trigger fired and the next Generator ran * The expected objects were created ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") | Symptom | Likely cause | Fix | | ------------------------------------------ | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Trigger doesn't fire | Trigger rule is not active, or `branch_scope` doesn't match the branch you're working on | Check trigger rule status. If `branch_scope` is `other_branches`, the trigger won't fire on the default branch — work in a non-default branch. | | Generator runs but creates nothing | Downstream Generator's upstream validation is failing | Check Generator logs. The Generator likely detects that upstream objects are incomplete and raises an error. | | Generator runs in a loop | The downstream Generator modifies an attribute that triggers the upstream Generator | Ensure Generators only write checksums to *downstream* targets, never upstream. The checksum guard (`if checksum != old`) should also prevent repeated triggers for the same output. | | Checksum unchanged after re-run | Generator produced the same output as before | This is **expected behavior** — execution correctly stops when nothing changed. | | Trigger fires but Generator errors | Generator definition name doesn't match the action's Generator reference | Verify that the `generator` field in `CoreGeneratorAction` matches the `name` field in your Generator definition in `.infrahub.yml`. | | Generator fails with "not fully generated" | Upstream validation caught incomplete data — the upstream Generator hasn't finished yet | This is the validation from Step 3 working as intended. The Generator will succeed on the next trigger once the upstream layer is complete. | ## What you learned[​](#what-you-learned "Direct link to What you learned") You now have a working two-stage Generator chain: * A `GeneratorTarget` generic that adds a `checksum` attribute to any downstream node kind * A `GeneratorMixin` that derives a checksum from the SDK tracking context and writes it to downstream targets only as the last step of `generate()` * Downstream Generators that validate upstream completeness before doing any work, failing fast and clearly when the upstream layer isn't ready * `CoreGeneratorAction` and `CoreNodeTriggerRule` objects loaded from object files, scoped to non-default branches so cascades run inside proposed changes ## Next steps[​](#next-steps "Direct link to Next steps") * See [best practices for modular Generators](/generators/modular-best-practices.md) for additional debugging techniques, pool scoping strategies, and operational guidance for running cascades in production * Revisit [modular Generators](/generators/modular.md) for the conceptual framing of why splitting Generators across layers is worth the overhead --- # Build your first generator By the end of this tutorial you will have built a working Generator end-to-end: modeled two object kinds, written a GraphQL query, implemented a Python Generator class, registered it in `.infrahub.yml`, run it locally with `infrahubctl`, and verified it runs automatically as part of a proposed change. You will leave with a `Widget` and `Resource` schema loaded into Infrahub and a Generator that creates `Resource` objects from each `Widget`'s `count` attribute. For conceptual background, see [Generators](/generators/overview.md). For a recipe-form how-to without the running example, see [Build a generator](/generators/build.md). ## What you'll build[​](#what-youll-build "Direct link to What you'll build") You'll build a basic Generator that: 1. Reads information about Widget objects from the database 2. Creates a number of Resource objects based on each Widget's count property 3. Automatically runs when changes to Widget objects are proposed ## Steps overview[​](#steps-overview "Direct link to Steps overview") 1. Prepare the data model by creating a schema and objects 2. Create a GraphQL query to fetch data from Infrahub 3. Implement a Python Generator that creates the data 4. Tie everything together in an `.infrahub.yml` file 5. Test the Generator locally with infrahubctl 6. Deploy the Generator to Infrahub 7. Validate that the Generator works in Infrahub's CI pipeline ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before you begin, you'll need: * Basic knowledge of Infrahub, Python, GraphQL, YAML, and Git * Familiarity with Generators and how they work in Infrahub (see [Generators](/generators/overview.md)) * An Infrahub instance running locally or remotely * A Git repository connected to Infrahub (see [Connect a repository](/git-integration/connect-repository.md)) * [infrahubctl](/infrahubctl/infrahubctl.md) installed and configured locally * The repository cloned locally where you'll develop the Generator ## Step 1: Setting up the data model[​](#step-1-setting-up-the-data-model "Direct link to Step 1: Setting up the data model") In this step, you'll create the data structures needed for your Generator, including a schema, group, and sample objects that the Generator will process. ### Create a new branch[​](#create-a-new-branch "Direct link to Create a new branch") Version Control & Branching We recommend creating a new branch in your Git repository for your Generator development to avoid affecting the main branch. In this tutorial, we'll use a branch named `create-widget-generator`. ``` git checkout -b create-widget-generator ``` ### Create group[​](#create-group "Direct link to Create group") Object file or UI If you prefer a "as code" approach, use the object file method. Otherwise, you can create the group directly in the Infrahub UI. * Using Object File * Using the UI 1. Create a file named `groups.yml` in the `objects` directory of your repository: objects/groups.yml ``` --- apiVersion: infrahub.app/v1 kind: Object spec: kind: CoreStandardGroup data: - name: "widgets" ``` 2. Update the `.infrahub.yml` file to include the new object file: .infrahub.yml ``` --- objects: - file_path: objects/groups.yml ``` 3. Commit the changes to your repository: ``` git add objects/groups.yml .infrahub.yml git commit -m "Add widgets group" git push ``` 1. Open the **Infrahub UI** in your browser 2. Select the branch `create-widget-generator` 3. Navigate to the **Groups** view (Object Management > Groups) 4. Create a **Standard group** named `widgets` success To verify the group was created successfully, navigate to the Groups view in the Infrahub UI and check that the `widgets` group appears in the list. ### Create and load schema[​](#create-and-load-schema "Direct link to Create and load schema") Schema Creation This step is only necessary if you don't already have the schemas affected by the Generator in your instance. 1. Create a `widgets.yml` file in the `schemas` directory of your repository: schemas/widgets.yml ``` # yaml-language-server: $schema=https://schema.infrahub.app/infrahub/schema/latest.json --- version: '1.0' nodes: - name: Widget namespace: Test label: Widget human_friendly_id: - name__value display_label: "{{ name__value }}" attributes: - name: name kind: Text unique: true - name: count kind: Number - name: Resource namespace: Test label: Resource human_friendly_id: - name__value display_label: "{{ name__value }}" attributes: - name: name kind: Text unique: true ``` Schema Loading Choose the method you already use to manage schema changes in your Infrahub deployment. * Using Git Integration * Using infrahubctl 2. Update the `.infrahub.yml` file to include the new schema file: .infrahub.yml ``` --- schemas: - schemas/widgets.yml ``` 3. Commit and push the changes to your repository 2) Load the following schema using the [infrahubctl schema](/infrahubctl/infrahubctl-schema.md) command. ``` infrahubctl schema load schemas/widgets.yml --branch=create-widget-generator ``` ``` schema 'widgets.yml' loaded successfully 1 schema processed in 8.453 seconds. ``` success You should see new entries for `Widget` and `Resource` in the left-hand side menu of the Infrahub UI. ### Create widget objects[​](#create-widget-objects "Direct link to Create widget objects") Now that you have the schema loaded, you need to create objects that will be used by the Generator: Object Creation This step is only necessary if you don't already have the objects affected by the Generator in your instance. Object Creation Method You can also use an object file to create the widgets, though it's less relevant here than for the group creation. 1. Open the **Infrahub UI** in your browser 2. Select the branch `create-widget-generator` 3. Navigate to the **Widget** view 4. Create a new widget: * Name: `widget1` * Count: `1` * Member of groups: `widgets` 5. Create a second widget: * Name: `widget2` * Count: `2` * Member of groups: `widgets` ## Step 2: Create the GraphQL query[​](#step-2-create-the-graphql-query "Direct link to Step 2: Create the GraphQL query") Next, create a GraphQL query that fetches the data your Generator needs to process. ### Create a query file[​](#create-a-query-file "Direct link to Create a query file") Create a `widget_query.gql` file in the `queries` directory of your repository: queries/widget\_query.gql ``` query Widgets($name: String!) { TestWidget(name__value: $name) { edges { node { __typename id name { value } count { value } } } } } ``` ### Test the query[​](#test-the-query "Direct link to Test the query") To test the query you can use **Infrahub's GraphQL Sandbox**. GraphQL Sandbox Access the sandbox by clicking your user icon in the bottom left corner and selecting **GraphQL Sandbox**. This tool allows you to run GraphQL queries and provides an interactive way to explore the schema. 1. Copy the above GraphQL query in the main section 2. In the **variables** section, add: ``` { "name": "widget1" } ``` 3. Click the **Execute** button, this should return a response like: ``` { "data": { "TestWidget": { "edges": [ { "node": { "__typename": "TestWidget", "id": "185526eb-2114-ce20-390a-c51aac78460a", "name": { "value": "widget1" }, "count": { "value": 1 } } } ] } } } ``` success You now have a working GraphQL query that retrieves the necessary data from Infrahub. You might want to keep the result of the query for later reference, as it will be useful when implementing the Generator. ## Step 3: Implement the Generator[​](#step-3-implement-the-generator "Direct link to Step 3: Implement the Generator") Now create a Python class that implements your Generator logic. The Generator creates `TestResource` objects equal to the widget's count value. The class must: * Inherit from `InfrahubGenerator` * Implement an async `generate()` method that processes data from your GraphQL query info If you aren't using `convert_query_response`, you can access the widget data directly from the `data` parameter passed to the `generate()` method. If you're working with `protocols`, you can create `TestResource` objects using strict typing. 1. Create a file named `widget_generator.py` in your `generators` directory: generators/widget\_generator.py ``` from infrahub_sdk.generator import InfrahubGenerator class WidgetGenerator(InfrahubGenerator): async def generate(self, data: dict) -> None: # Access the widget as an SDK object widget = self.nodes[0] # or self.store.get(data["TestWidget"]["edges"][0]["node"]["id"]) widget_name: str = widget.name.value widget_count: int = widget.count.value # Create resources based on widget count for count in range(1, widget_count + 1): payload = { "name": f"{widget_name.lower()}-{count}", } obj = await self.client.create(kind="TestResource", data=payload) await obj.save(allow_upsert=True) ``` ## Step 4: Tie everything together in the .infrahub.yml file[​](#step-4-tie-everything-together-in-the-infrahubyml-file "Direct link to Step 4: Tie everything together in the .infrahub.yml file") Now that you have your GraphQL query and Python Generator, edit your [.infrahub.yml](/git-integration/infrahub-yml.md) file to tie everything together. 1. Add the following configuration to the file `.infrahub.yml`: .infrahub.yml ``` --- generator_definitions: - name: widget_generator file_path: "generators/widget_generator.py" targets: widgets query: widget_query convert_query_response: true class_name: WidgetGenerator parameters: name: "name__value" queries: - name: widget_query file_path: "queries/widget_query.gql" ``` For a complete explanation of the `.infrahub.yml` file format, see the [infrahub.yml configuration](/git-integration/infrahub-yml.md). 2. Verify the configuration Check that your `.infrahub.yml` file is correctly formatted by listing available Generators: ``` infrahubctl generator --list ``` success If successful, you'll see output like: ``` Generators defined in repository: 1 widget_generator (generators/widget_generator.py::WidgetGenerator) Target: widgets ``` ## Step 5: Test the Generator locally[​](#step-5-test-the-generator-locally "Direct link to Step 5: Test the Generator locally") Before deploying your Generator to Infrahub, test it locally using the `infrahubctl` command-line tool. ### Run the Generator[​](#run-the-generator "Direct link to Run the Generator") Run the Generator on both of your widget objects: ``` infrahubctl generator widget_generator --branch=create-widget-generator name=widget1 infrahubctl generator widget_generator --branch=create-widget-generator name=widget2 ``` ### Verify the results[​](#verify-the-results "Direct link to Verify the results") 1. Select the branch `create-widget-generator` from the branch selector 2. Navigate to **Resource** objects 3. You should see: * One resource object for `widget1` (named `widget1-1`) * Two resource objects for `widget2` (named `widget2-1` and `widget2-2`) ![Generated resource objects](/assets/images/generator_pc_3-cdfaa19101c8ed4238c45fec7213a7a4.png) warning You might want to cleanup this development data before merging your changes into the main branch. success Your Generator worked as expected, it's now time to deploy it to Infrahub. ## Step 6: Deploy to Infrahub[​](#step-6-deploy-to-infrahub "Direct link to Step 6: Deploy to Infrahub") Now that you've tested your Generator, deploy it to Infrahub. ### Verify repository structure[​](#verify-repository-structure "Direct link to Verify repository structure") Ensure your repository has the following structure: Schemas & Objects Depending on your organization, you might also have `schemas` and `objects` directories in your repository. ``` your-repository/ ├── .infrahub.yml ├── generators/ │ └── widget_generator.py └── queries/ └── widget_query.gql ``` ### Commit and push your code[​](#commit-and-push-your-code "Direct link to Commit and push your code") Upload your Generator code to the repository: ``` git add . git commit -m "Add widget generator" git push ``` ### Confirm the Generator is imported[​](#confirm-the-generator-is-imported "Direct link to Confirm the Generator is imported") After pushing your changes, confirm that the Generator is imported correctly by checking the Infrahub UI. 1. Open the **Infrahub UI** in your browser 2. Select the `create-widget-generator` branch 3. Navigate to the **Generators Definition view** (Actions > Generator Definitions) 4. You should see your `widget_generator` listed there If you don't see it verify the status of the repository in the **Repository view** and ensure the sync status is `synced`. ### Proposed changes and merge[​](#proposed-changes-and-merge "Direct link to Proposed changes and merge") Now merge your development branch `create-widget-generator` into `main`. Merging Options We recommend using Infrahub's **Proposed Changes** feature to merge your changes. This allows you to review both data and code changes before merging. 1. Create a **Proposed Change** * Source branch: `create-widget-generator` * Destination branch: `main` * Name: `Add widget generator` 2. Navigate to the **Data** and **Files** tabs to review the changes 3. Back to the **Overview** tab, click the **Merge** button success Your Generator is now deployed to Infrahub in the `main` branch and ready to be used. ## Step 7: Run the Generator in Infrahub's CI pipeline[​](#step-7-run-the-generator-in-infrahubs-ci-pipeline "Direct link to Step 7: Run the Generator in Infrahub's CI pipeline") Now let's verify that your Generator automatically runs when new widgets are created: 1. Create a new branch named `add-widget-3` 2. Navigate to the **Widget** objects in the Infrahub UI 3. Create a new widget: * Name: `widget3` * Count: `3` * Member of groups: `widgets` 4. Create a **Proposed Change** * Source branch: `add-widget-3` * Destination branch: `main` * Name: `Add widget3` ### Check Generator results[​](#check-generator-results "Direct link to Check Generator results") 1. Navigate to the **Checks** tab of your proposed change 2. Wait for the **Generator** CI check to complete ![Generator CI Check](/assets/images/generator_pc_1-806e4bf23f6b989bd1dffb62ee863b2b.png) 3. Navigate to the **Data** tab 4. Click the **Refresh diff** button to see the resources created by your Generator ![Data Refresh showing generated resources](/assets/images/generator_pc_2-029c5257939535f8409cb76669efcc2b.png) success You should see three new resources (`widget3-1`, `widget3-2`, and `widget3-3`) automatically created by your Generator. ## What you learned[​](#what-you-learned "Direct link to What you learned") You now have a working Generator end-to-end: * A `Widget` and `Resource` schema loaded into Infrahub via the Git integration * A GraphQL query that selects a single Widget by name and reads its `count` * A Python `WidgetGenerator` class that creates one `Resource` per count, using `allow_upsert=True` for safe re-runs * A `.infrahub.yml` definition that wires the query, the Python class, and the `widgets` target group together * Confirmed behavior both via `infrahubctl generator` locally and via the Generator CI check on a proposed change ## Next steps[​](#next-steps "Direct link to Next steps") Now that you've created a basic Generator, you can: * Build chained Generators that span multiple layers of a hierarchy — see [Build chained generators](/academy/tutorials/generators/build-chained-generators.md) * Read the [best practices for modular Generators](/generators/modular-best-practices.md) for idempotency, deterministic naming, and debugging cascades * Add unit tests to your Generator to ensure it behaves as expected * Harden your Generator by adding error handling and logging Real-World Example Want to see how Generators can be used in production? Read our blog post on [How to Turn Your Source of Truth into a Service Factory](https://www.opsmill.com/how-to-turn-your-source-of-truth-into-a-service-factory/). --- # Organize objects with groups By the end of this tutorial you will have created a group, added two objects to it, and queried the result end-to-end. You'll leave with a concrete mental model of how groups work and where to go next. This tutorial uses `BuiltinTag` objects so you can follow along without any special schema. The same steps apply to any object type. ## What you will need[​](#what-you-will-need "Direct link to What you will need") * A running Infrahub instance (local or remote). * Permission to create and modify groups. * Two tags to add as members. If you don't have tags yet, create two called `red` and `blue` before starting. ## Step 1 — Create a new group[​](#step-1--create-a-new-group "Direct link to Step 1 — Create a new group") You'll create a Standard group named `TagConfigGroup`. A Standard group is the general-purpose type you create and manage yourself. * Web Interface * GraphQL * Python SDK Navigate to **Object Management** → **Groups** in the left menu. Click **New Group** and provide: * Name: `TagConfigGroup` * An optional description. * Group type: `CoreStandardGroup`. ![New Group](/assets/images/group_tagconfig_grp_new_grp-930aefb484e8655e133dde2fc40a67d5.png) Open the GraphQL interface at `http://localhost:8000/graphql` and run: ``` mutation CreateGroup { CoreStandardGroupCreate(data: {name: {value: "TagConfigGroup"}}) { ok object { hfid } } } ``` Save the `hfid` returned — you'll use it in the next step. ``` from infrahub_sdk import InfrahubClientSync client = InfrahubClientSync(address="http://localhost:8000") group = client.create(kind="CoreStandardGroup", name="TagConfigGroup") group.save() ``` ## Step 2 — Add two tags as members[​](#step-2--add-two-tags-as-members "Direct link to Step 2 — Add two tags as members") Attach your `red` and `blue` tags to the group. * Web Interface * GraphQL * Python SDK 1. Open `TagConfigGroup` from the Groups list. 2. Go to the **Members** tab. 3. Click **Add Members** and select `red` and `blue`. 4. Click **Save**. ![Adding members in group](/assets/images/group_tagconfig_grp_adding_members-8485bf45b9a1b55d2199c2e9e490ff45.png) First, look up the tag IDs: ``` query { BuiltinTag(name__values: ["red", "blue"]) { edges { node { id display_label } } } } ``` Then update the group with those IDs as members: ``` mutation UpdateGroupMembers { CoreStandardGroupUpdate( data: { hfid: ["TagConfigGroup"], members: [ {id: ""}, {id: ""} ] } ) { ok } } ``` ``` group = client.get(kind="CoreStandardGroup", name__value="TagConfigGroup") red_tag = client.get(kind="BuiltinTag", name__value="red") blue_tag = client.get(kind="BuiltinTag", name__value="blue") group.members.add(red_tag) group.members.add(blue_tag) group.save() ``` ## Step 3 — Verify[​](#step-3--verify "Direct link to Step 3 — Verify") Confirm both tags are now in the group. * Web Interface * GraphQL * Python SDK Open `TagConfigGroup` and check the **Members** tab. Both `red` and `blue` should appear. ![Group members](/assets/images/group_tagconfig_grp_new_members-824f11e2fd33298231d83de3662bd7ac.png) ``` query { CoreStandardGroup(name__value: "TagConfigGroup") { edges { node { name { value } members { edges { node { display_label } } } } } } } ``` ``` group = client.get(kind="CoreStandardGroup", name__value="TagConfigGroup") for member in group.members.fetch(): print(f"Member: {member.display_label}") ``` ## What you learned[​](#what-you-learned "Direct link to What you learned") * Groups are first-class objects in the graph with their own attributes and relationships. * A Standard group is manually managed — you decide what goes in it. * Membership is a relationship, so changes flow through the graph and can be queried from either side. ## Where to next[​](#where-to-next "Direct link to Where to next") * [Groups overview](/groups/overview.md) — concepts, architecture, and when to choose each group type. * [Add more members to a group](/groups/add-members.md) — how-to reference. * [Use groups in automation](/groups/use-in-automation.md) — target groups from Artifacts, Transformations, and Checks. --- # Build a Jinja2 Transformation By the end of this tutorial you will have built a working Jinja2 Transformation end-to-end: loaded a small network-device schema, created a few sample devices, written a GraphQL query that filters by device name, written a Jinja template that renders a configuration snippet from the result, registered it in `.infrahub.yml`, tested it locally with `infrahubctl render`, added the repository to Infrahub, and called the render API. You'll leave with a `device_config_transform` you can call against any device by name. For conceptual background, see [Transformations](/transformations/overview.md). For a recipe-form how-to without the running example, see [Write a Jinja2 transformation](/transformations/jinja2.md). Within Infrahub a [Transformation](/transformations/overview.md) is defined in an [external repository](/git-integration/connect-repository.md). However, during development and troubleshooting it is easiest to start from your local computer and run the render using [infrahubctl render](/infrahubctl/infrahubctl-render.md). The tutorial follows these steps: 1. Identify the relevant data you want to extract from the database using a [GraphQL query](/development-resources/graphql/overview.md), that can take an input parameter to filter the data 2. Write a Jinja2 file that uses the GraphQL query to read information from the system and render the data into a new format 3. Create an entry for the Jinja2 Transformation within an `.infrahub.yml` file 4. Create a Git repository 5. Test the Transformation rendering with `infrahubctl` 6. Add the repository to Infrahub as an external repository 7. Validate that the Transformation works using the render API endpoint ## 1. Loading a schema[​](#1-loading-a-schema "Direct link to 1. Loading a schema") This tutorial uses a very simplistic network device model. The rendered template won't be very useful on its own — the goal is to show how Jinja rendering works. Once you've mastered the basics you'll be ready to create more advanced templates. ``` --- version: "1.0" nodes: - name: Device namespace: Network display_label: "{{ name__value }}" attributes: - name: name kind: Text label: Name optional: false unique: true - name: description kind: Text label: Description optional: true ``` Store the schema as a YAML file on your local disk, and load the schema into Infrahub using the following command ``` infrahubctl schema load /path/to/schema.yml ``` More information on loading schema files into Infrahub can be found in the [schema import guide](/schema/create-and-load.md#load-a-schema-file). ## 2. Creating a query to collect the desired data[​](#2-creating-a-query-to-collect-the-desired-data "Direct link to 2. Creating a query to collect the desired data") As the first step we need some data in the database to actually query. Create three devices, called "switch1", "switch2", "switch3", either using the frontend or by submitting three GraphQL mutations as per below (swapping out the name each time). ``` mutation CreateDevice { NetworkDeviceCreate( data: {name: {value: "switch1"}, description: {value: "This is device switch1"}} ) { ok object { id } } } ``` The next step is to create a query that returns the data we created above. The rest of this tutorial assumes that the following query will return a response similar to the response below the query. ``` query DeviceQuery { NetworkDevice { edges { node { name { value } description { value } } } } } ``` Response to the query: ``` { "data": { "NetworkDevice": { "edges": [ { "node": { "name": { "value": "switch1" }, "description": { "value": "This is device switch1" } } }, { "node": { "name": { "value": "switch2" }, "description": { "value": "This is device switch2" } } }, { "node": { "name": { "value": "switch3" }, "description": { "value": "This is device switch3" } } } ] } } } ``` While it's possible to create a Transformation that targets all of these devices — for example to create a report — the goal here is to focus on one device at a time. Modify the query above to take an input parameter so that we can filter the result. Single-target query requirement For proper artifact detection, your query must target a **unique node** using a unique attribute or ID. This ensures Infrahub only regenerates the necessary artifacts instead of regenerating all artifacts unnecessarily. **Requirements for a valid single-target query:** * Must filter on a unique identifier (ID or unique attribute like `name`) * Must use a required variable, for example, `$name: String!` * Must use exact match filters, for example, `name__value: $name`, not list filters, for example, `name__values: $name` **Valid example:** ``` query BuiltinTag($name: String!) { BuiltinTag(name__value: $name) { edges { node { id } } } } ``` **Invalid examples (will cause excessive artifact generation):** No filter at all: ``` query BuiltinTag { BuiltinTag { edges { node { id } } } } ``` Filtering on a non-unique attribute: ``` query BuiltinTag($description: String!) { BuiltinTag(description__value: $description) { edges { node { id } } } } ``` To learn more about single-target queries and why they are important, see the [GraphQL topic](/development-resources/graphql/single-target-queries.md). Create a local directory on your computer. ``` mkdir device_config_render ``` Then save the below query as a text file named `device_config.gql`. ``` query DeviceQuery($name: String!) { NetworkDevice(name__value: $name) { edges { node { name { value } description { value } } } } } ``` The query requires an input parameter called `$name` that will refer to the name of each device. When we want to query for device switch1, the input variables to the query would look like this: ``` { "name": "switch1" } ``` ## 3. Create the Jinja template[​](#3-create-the-jinja-template "Direct link to 3. Create the Jinja template") The next step is to create the actual Jinja Template file. Create a file called `device_config.j2`. ``` {% if data.NetworkDevice.edges and data.NetworkDevice.edges is iterable %} {% for device in data["NetworkDevice"]["edges"] %} {% set device_name = device.node.name.value %} {% set device_description = device.node.description.value %} hostname {{ device_name }} description "{{ device_description }}" end {% endfor %} {% endif %} ``` note In your template, you can utilize most of the **filters** provided by **Jinja2** and **Netutils**! For more information, see the [SDK Templating Reference](/python-sdk/reference/templating.md). ## 4. Create a .infrahub.yml file[​](#4-create-a-infrahubyml-file "Direct link to 4. Create a .infrahub.yml file") In the `.infrahub.yml` file you define what Transformations you have in your repository that you want to make available for Infrahub. Create a `.infrahub.yml` file in the root of the directory. ``` --- jinja2_transforms: - name: device_config_transform # Unique name for your Transformation description: "device config Transformation" # (optional) query: "device_config_query" # Name or ID of the GraphQLQuery template_path: "device_config.j2" # Path to the main Jinja2 template queries: - name: device_config_query # Name of the GraphQLQuery file_path: "device_config.gql" # Path to the main Jinja2 template ``` > The main Jinja2 template can import other templates Three parts here are required: first the `name` of the Transformation which should be unique across Infrahub, `query` the GraphqlQuery linked to our Transformation, and also the `template_path` that should point to the Jinja2 file within the repository. ## 5. Create a Git repository[​](#5-create-a-git-repository "Direct link to 5. Create a Git repository") Within the `device_config_render` folder you should now have three files: * `device_config.gql`: Contains the GraphQL query * `device_config.j2`: Contains the Jinja2 Template * `.infrahub.yml`: Contains the definition for the Transformation Before we can test our Transformation we must add the files to a local Git repository. ``` git init --initial-branch=main git add . git commit -m "First commit" ``` ## 6. Test the render using infrahubctl[​](#6-test-the-render-using-infrahubctl "Direct link to 6. Test the render using infrahubctl") Using `infrahubctl` you can first verify that the `.infrahub.yml` file is formatted correctly by listing available Transformations. ``` Usage: infrahubctl render [OPTIONS] TRANSFORM_NAME [VARIABLES]... Render a local Jinja2 Transformation for debugging purpose. ╭─ Arguments ─────────────────────────────────────────────────────────────────────────────────────╮ │ * transform_name TEXT [default: None] [required] │ │ variables [VARIABLES]... Variables to pass along with the query. Format │ │ key=value key=value. │ │ [default: None] │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─ Options ───────────────────────────────────────────────────────────────────────────────────────╮ │ --branch TEXT Branch on which to render the Transformation. [default: None] │ │ --debug --no-debug [default: no-debug] │ │ --config-file TEXT [env var: INFRAHUBCTL_CONFIG] [default: infrahubctl.toml] │ │ --help Show this message and exit. │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯ ``` Examples ``` infrahubctl render my-param=XXXXX my-other-param=YYYYY ``` note If `--branch` is not provided it will automatically use the name of the local branch. ## 7. Adding the repository to Infrahub[​](#7-adding-the-repository-to-infrahub "Direct link to 7. Adding the repository to Infrahub") To avoid repeating the same instructions, see [Connect a repository](/git-integration/connect-repository.md) for syncing the repository you created and making it available within Infrahub. ## 8. Accessing the Transformation from the API[​](#8-accessing-the-transformation-from-the-api "Direct link to 8. Accessing the Transformation from the API") A Transformation can be rendered on demand via the REST API with the endpoint: `https:///api/transform/jinja2/` This endpoint is branch-aware and it accepts the name of the branch and/or the time as URL parameters. * `https:///api/transform/jinja2/?branch=main` * `https:///api/transform/jinja2/?branch=main&at=