Skip to main content
Artifacts in Infrahub are generated outputs created by applying transformations to data retrieved from GraphQL queries. They represent the final deliverable of the data transformation process, such as device configurations, documentation, or data exports.

What are Artifacts?

An artifact is a stored output generated by:
  1. Executing a GraphQL query to retrieve data
  2. Applying a transformation (Jinja2 or Python) to that data
  3. Storing the result with metadata and versioning
Artifacts are immutable and versioned, providing a complete audit trail of generated configurations.

Artifact Definitions

Artifact definitions specify how artifacts should be generated. They are configured in your repository’s .infrahub.yml file:
artifact_definitions:
  - name: "Startup Config for Edge devices"
    artifact_name: "startup-config"
    parameters:
      device: "name__value"
    content_type: "text/plain"
    targets: "edge_router"
    transformation: "device_startup"

  - name: "Openconfig Interface for Arista devices"
    artifact_name: "openconfig-interfaces"
    parameters:
      device: "name__value"
    content_type: "application/json"
    targets: "arista_devices"
    transformation: "OCInterfaces"

Configuration Options

Required Fields

  • name: Human-readable name describing the artifact
  • artifact_name: Unique identifier for the artifact type
  • transformation: Name of the Jinja2 or Python transformation to use
  • targets: Group or kind of objects to generate artifacts for
  • content_type: MIME type of the generated content

Content Types

Supported content types:
  • text/plain - Plain text files (configurations, scripts)
  • application/json - JSON data
  • application/yaml - YAML data
  • text/markdown - Markdown documentation
  • application/xml - XML data
  • Custom content types for specific use cases

Parameters

Parameters define how to extract variables from target objects for the GraphQL query:
parameters:
  device: "name__value"      # Extract device name
  interface: "id"            # Extract interface ID
  site: "site.name__value"   # Extract nested site name
These parameters are passed as variables to the GraphQL query.

Artifact Generation Process

Artifact Generation Workflow

  1. Target Selection: Identify objects matching the targets group
  2. Parameter Extraction: Extract query variables from each target
  3. Query Execution: Run the GraphQL query with variables
  4. Transformation: Apply the Jinja2 template or Python transform
  5. Content Processing: Format content based on content_type
  6. Checksum Calculation: Generate MD5 checksum of content
  7. Change Detection: Compare with existing artifact checksum
  8. Storage: Store new artifact or reference existing one

Artifact Storage

Artifacts are stored with comprehensive metadata:
class ArtifactGenerateResult(BaseModel):
    changed: bool          # Whether content changed
    checksum: str          # MD5 checksum of content
    storage_id: str        # Storage location identifier
    artifact_id: str       # Unique artifact ID

Storage Properties

  • Immutable: Once created, artifact content never changes
  • Versioned: Each change creates a new artifact version
  • Checksummed: MD5 hash ensures content integrity
  • Linked: Artifacts track their definition, transformation, and query

Example: Network Device Configuration

Complete example generating device startup configurations:

1. Define GraphQL Query

# templates/device_startup_info.gql
query device_startup_info ($device: String!) {
  InfraDevice(name__value: $device) {
    edges {
      node {
        id
        name { value }
        interfaces {
          edges {
            node {
              id
              name { value }
              enabled { value }
              role { value }
            }
          }
        }
      }
    }
  }
}

2. Create Jinja2 Template

{# templates/device_startup_config.tpl.j2 #}
hostname {{ data.InfraDevice.edges[0].node.name.value }}
!
{% for intf in data.InfraDevice.edges[0].node.interfaces.edges %}
interface {{ intf.node.name.value }}
  description role: {{ intf.node.role.value }}
{% if not intf.node.enabled.value %}
  shutdown
{% endif %}
!
{% endfor %}

3. Configure in .infrahub.yml

queries:
  - name: device_startup_info
    file_path: "templates/device_startup_info.gql"

jinja2_transforms:
  - name: device_startup
    description: "Generate startup configuration for network devices"
    query: device_startup_info
    template_path: "templates/device_startup_config.tpl.j2"

artifact_definitions:
  - name: "Startup Config for Edge devices"
    artifact_name: "startup-config"
    parameters:
      device: "name__value"
    content_type: "text/plain"
    targets: "edge_router"
    transformation: "device_startup"

4. Generated Artifact

hostname edge-router-01
!
interface GigabitEthernet0/0
  description role: uplink
!
interface GigabitEthernet0/1
  description role: peer
  shutdown
!

Artifact Validation

Artifacts can be validated before creation using artifact checks:
class CheckArtifactCreate(BaseModel):
    artifact_name: str
    artifact_definition: str
    commit: str
    content_type: str
    transform_type: str
    transform_location: str
    repository_id: str
    branch_name: str
    target_id: str
    query_id: str
    timeout: int
    variables: dict
    validator_id: str
Validation checks:
  • Template syntax errors
  • Transformation execution failures
  • Content type mismatches
  • Timeout violations

Artifact Status

Artifacts track their generation status:
  • Success: Artifact generated successfully
  • Error: Generation failed (with error message)
  • Pending: Generation in progress
# Setting artifact status on error
artifact.status.value = "Error"
await artifact.save()

Working with Artifacts

Triggering Artifact Generation

Artifacts can be generated:
  1. Automatically: When target objects change
  2. On-demand: Via UI or API
  3. In branches: Generate artifacts in proposed changes
  4. After merge: Regenerate after branch merge

Accessing Artifacts

Retrieve artifacts via the SDK:
from infrahub_sdk import InfrahubClient

client = InfrahubClient()

# Get artifact by ID
artifact = await client.get(
    kind="CoreArtifact",
    id=artifact_id
)

# Get artifacts for a target
artifacts = await client.filters(
    kind="CoreArtifact",
    object__ids=[target_id]
)

Comparing Artifacts

Infrahub automatically detects changes:
# Calculate checksum
checksum = hashlib.md5(
    bytes(artifact_content, encoding="utf-8"),
    usedforsecurity=False
).hexdigest()

# Compare with existing
if artifact.checksum.value == checksum:
    # No change, reuse existing artifact
    return ArtifactGenerateResult(
        changed=False,
        checksum=checksum,
        artifact_id=artifact.id,
        storage_id=artifact.storage_id.value
    )

Python Transform Artifacts

Using Python transforms for complex artifact generation:
python_transforms:
  - name: OCInterfaces
    class_name: OCInterfaces
    file_path: "transforms/openconfig.py"

artifact_definitions:
  - name: "Openconfig Interface for Arista devices"
    artifact_name: "openconfig-interfaces"
    parameters:
      device: "name__value"
    content_type: "application/json"
    targets: "arista_devices"
    transformation: "OCInterfaces"
The Python transform returns structured data:
from typing import Any
from infrahub_sdk.transforms import InfrahubTransform

class OCInterfaces(InfrahubTransform):
    query = "oc_interfaces"
    
    async def transform(self, data: dict[str, Any]) -> dict[str, Any]:
        # Return structured data
        return {
            "openconfig-interfaces:interfaces": {
                "interface": [
                    {
                        "name": intf["node"]["name"]["value"],
                        "config": {
                            "name": intf["node"]["name"]["value"],
                            "enabled": intf["node"]["enabled"]["value"]
                        }
                    }
                    for intf in data["InfraDevice"]["edges"][0]["node"]["interfaces"]["edges"]
                ]
            }
        }

Content Type Handling

Infrahub automatically formats output based on content_type:
# JSON output
if content_type == "application/json" and isinstance(content, dict):
    artifact_content = ujson.dumps(content, indent=2)

# YAML output
elif content_type == "application/yaml" and isinstance(content, dict):
    artifact_content = yaml.dump(content, indent=2)

# Text output
else:
    artifact_content = str(content)

Best Practices

  1. Use descriptive names: Make artifact names clear and specific
  2. Target appropriately: Use groups or filters to select correct objects
  3. Set correct content types: Ensures proper formatting and handling
  4. Version control: Keep transformation code in Git repositories
  5. Test thoroughly: Validate artifacts before production use
  6. Monitor generation: Track artifact generation success/failure
  7. Handle errors: Implement error handling in transformations
  8. Optimize queries: Retrieve only needed data to improve performance

Next Steps

Build docs developers (and LLMs) love