Infrahub Check Creator
Overview
Expert guidance for creating Infrahub checks. Checks are user-defined validation logic (Python + GraphQL) that run as part of a proposed change pipeline. If a check logs any errors, the proposed change cannot be merged.
Project Context
Infrahub config:
!cat .infrahub.yml 2>/dev/null || echo "No .infrahub.yml found"
Existing checks:
!find . -name "*.py" -path "*/checks/*" 2>/dev/null | head -20
Existing queries:
!find . -name "*.gql" -path "*/queries/*" 2>/dev/null | head -20
When to Use
- Writing validation logic for proposed changes
- Creating data quality guards (e.g., rack collision detection)
- Building global checks that validate all objects of a type
- Building targeted checks that validate specific grouped objects
- Debugging check failures or understanding the check lifecycle
Rule Categories
| Priority | Category | Prefix | Description |
|---|---|---|---|
| CRITICAL | Architecture | architecture- |
Three components, global vs targeted, execution flow |
| CRITICAL | Python Class | python- |
InfrahubCheck base class, validate(), log_error/log_info |
| HIGH | API Reference | api- |
Class attributes, instance properties, methods, lifecycle |
| HIGH | Registration | registration- |
.infrahub.yml config, query name matching, parameters |
| MEDIUM | Patterns | patterns- |
Error collection, shared utilities, scoped validation, relationship-traversal validation |
| HIGH | Testing | testing- |
Resources Testing Framework (YAML-driven tests), infrahubctl check commands |
Schema Features This Skill Depends On
A check is only useful if it can fetch and validate the right data. Most check failures at deploy time are actually schema-side gaps:
| If the check... | The schema (or .infrahub.yml) must... | See |
|---|---|---|
| Reads an attribute via GraphQL | Expose it on the schema node with the same name (name__value-shaped paths) |
../infrahub-managing-schemas/rules/attribute-defaults-and-types.md |
| Walks a relationship to validate related objects | Have both sides of the relationship defined with matching identifiers; otherwise the traversal returns nothing | ../infrahub-managing-schemas/rules/relationship-identifiers.md |
| Validates a node against a related node's state (child vs parent lifecycle, peer consistency) | Fetch the related node's comparison attribute in the query by traversing the relationship; a check runs one query with no lazy fetch | rules/patterns-relationship-traversal.md |
| Is targeted (per-object) | Register a CoreStandardGroup as targets: in .infrahub.yml and map parameters: to bind GraphQL variables |
rules/registration-config.md |
| Needs the GraphQL response keyed to typed nodes | Select id and __typename in the query — the SDK relies on both |
../infrahub-common/graphql-queries.md |
| Should never block a merge but only annotate | Use self.log_info() instead of log_error(); log_warning() does not exist |
rules/python-validate.md |
Before writing Python
If a cheaper layer can express the constraint, use it.
A schema constraint runs at load time on every write
path; a Python check runs only inside the proposed-
change pipeline, so bad data created via other paths
slips through. Walk this short ladder before reaching
for InfrahubCheck:
| Signal | Cheaper layer | See rule |
|---|---|---|
| Validating uniqueness, presence, allowed values, or regex on a single attribute | Schema constraint (uniqueness_constraints, optional: false, kind: Dropdown choices, regex) |
yagni-python-validator-vs-schema-constraint |
Check whose body is a GraphQL query plus a single if len(...) > 0: raise |
One .gql file plus 5 lines of Python |
yagni-redundant-check-that-graphql-can-answer |
| Enforcing that a relationship is single-peered or non-optional | Schema cardinality: one, kind: Parent / Component, optional: false |
yagni-python-validator-vs-schema-constraint |
Only when none of these apply should you write a Python check. The cross-node business rules, out-of-band reconciliations, and stateful assertions in rules/python-validate.md are the legitimate use cases.
When the check reads objects through the SDK (rather than only its GraphQL
query), type those calls with generated protocol classes rather than string
kinds — client.get(DcimDevice, ...), not kind="DcimDevice". Match the
--sync protocol variant to the check's client. See
protocols-adopt-typed-kinds.
Check Basics
Every check has three components:
- GraphQL query (
.gqlfile) -- fetches the data to validate, and is registered under the top-levelqueries:section of.infrahub.yml - Python class -- inherits from
InfrahubCheck, setsquery = "<query_name>", implementsvalidate() - Configuration -- declared in
.infrahub.ymlundercheck_definitions(which does not take aquery:field — see below)
from infrahub_sdk.checks import InfrahubCheck
class MyCheck(InfrahubCheck):
query = "my_query" # Must match queries[].name in .infrahub.yml
def validate(self, data: dict) -> None:
# Validation logic here
if something_is_wrong:
self.log_error(
message="Problem description"
)
Where the query is bound: the Python class (
query = "..."), notcheck_definitions. The repository config model usesextra="forbid", so puttingquery:undercheck_definitions:makes the whole repo config fail validation. This is the #1 confusion vs.generator_definitions:, which does take a top-levelquery:. See rules/registration-config.md.
Workflow
Follow these steps when creating a check:
- Understand the validation goal — What data condition should block a proposed change? Determine whether this is a global check (all objects of a type) or targeted (specific group). Read rules/architecture-types.md.
- Write the GraphQL query — Create a
.gqlfile that fetches the data to validate. Read ../infrahub-common/graphql-queries.md for query patterns. If the rule compares a node against a related node's state (child vs parent lifecycle, peer consistency), the query must fetch that related attribute by traversing the relationship now — a check runs one query with no later fetch. See rules/patterns-relationship-traversal.md. - Implement the Python class — Inherit from
InfrahubCheck, implementvalidate(). Read rules/python-validate.md for the class pattern and rules/api-reference.md for available methods. - Register in .infrahub.yml — Add the check under
check_definitions. The query name must match the Python classqueryattribute. See rules/registration-config.md. - Add tests — Create YAML-driven test definitions (smoke, unit, integration) alongside the check so it is validated automatically in the proposed change pipeline. Read rules/testing-resource-framework.md.
- Test locally — Run
infrahubctl checkto validate against a feature branch. See rules/testing-commands.md.
Supporting References
- reference.md -- Class API,
log_error/log_info (no log_warning), lifecycle,
.infrahub.ymlregistration (with the no-query:shape that differs from generator_definitions) - examples.md -- Complete check patterns (global, targeted, minimal)
- ../infrahub-common/graphql-queries.md -- GraphQL query writing reference
- ../infrahub-common/infrahub-yml-reference.md -- .infrahub.yml project configuration
- ../infrahub-common/rules/ -- Shared rules (git integration, caching gotchas) that apply across all skills
- ../infrahub-common/rules/workflow-information-priority.md
-- Skill content first; how to consult
docs.infrahub.appon a genuine gap (e.g. deleting nodes) - ../infrahub-managing-schemas/SKILL.md -- Schema definitions checks validate against
- rules/ -- Individual rules organized by category prefix