Skip to main content

Creating a computed attribute in Infrahub

Infrahub supports two different types of computed attributes:

  • Jinja2 uses a concise Jinja2 template directly within the schema definition.
  • Python leverages Infrahub's Python transform feature to offer endless possibilities.

Recommendations

  • We recommend to keep python transforms straightforward to not impact system performance.
  • Use Jinja2 when the calculation logic is straightforward and concise.

Adding a Jinja2 computed attribute

To create a Jinja2 computed attribute everything happens directly in the schema definition. Refer to this guide to find further information about Schema Creation.

In the following example we will create a computed attribute description for our NetworkDevice node. The value will be dynamically generated by combining the device's role with the name of its associated site.

---
version: "1.0"
nodes:
- name: Site
namespace: Location
attributes:
- name: name
kind: Text
unique: true
relationships:
- name: devices
cardinality: many
peer: NetworkDevice
kind: Component

- name: Device
namespace: Network
attributes:
- name: hostname
kind: Text
unique: true
- name: role
kind: Dropdown
choices:
- name: core
- name: edge
- name: spine
- name: leaf
- name: description
kind: Text
computed_attribute:
kind: Jinja2
jinja2_template: "{{ role__value|title }} device located on {{ site__name__value|lower }}"
read_only: true # You must set the attribute to read-only as the value will be handled by the system
optional: false # As it's a Jinja2 kind of attribute you can make it mandatory
relationships:
- name: site
peer: LocationSite
optional: false
cardinality: one
kind: Attribute
note

In your template, you can utilize most of the built-in filters provided by Jinja2!

For more information, please consult the Schema Reference.

You can now load this schema into your Infrahub instance. For more details, refer to the Schema Creation Guide.

Adding Python computed attribute

Creating a Python computed attribute is a two-step process:

  1. Add the attribute to the schema.
  2. Prepare a Python Transform to implement the logic.

In the following example we will create a computed attribute description for our InfraCircuit node. The value will be a combination of all endpoints locations.

Add the attribute to the schema

Here's the schema we'll use as a starting point:

---
version: "1.0"
nodes:
- name: Site
namespace: Location
display_labels:
- name__value
attributes:
- name: name
kind: Text
unique: true
relationships:
- name: circuit_endpoints
cardinality: many
optional: true
peer: InfraCircuitEndpoint
kind: Component

- name: Circuit
namespace: Infra
display_labels:
- circuit_id__value
attributes:
- name: circuit_id
kind: Text
unique: true
- name: computed_description
kind: Text
computed_attribute:
kind: TransformPython
transform: computed_circuit_description # Transform's name
read_only: true # You must set the attribute to read-only as the value will be handled by the system.
optional: true # As it's a Python kind of attribute it must be optional
relationships:
- name: endpoints
peer: InfraCircuitEndpoint
optional: true
cardinality: many
kind: Component

- name: CircuitEndpoint
namespace: Infra
label: Circuit endpoint
display_labels:
- name__value
attributes:
- name: name
kind: Text
unique: true
relationships:
- name: circuit
peer: InfraCircuit
cardinality: one
kind: Attribute
- name: location
peer: LocationSite
cardinality: one
kind: Attribute

Once completed, you can load this schema into Infrahub.

note

You can specify a transform that doesn't yet exist in Infrahub, attribute will just remain empty. Once the transform is loaded, Infrahub will automatically catch up and compute the value of this attribute.

Create the Python transform

Please refer to the Python transform guide for further details.

  1. First we prepare a GraphQL query that returns the data we need
warning

GraphQL query used to compute attribute's value requires ID as parameter.

query MyQuery($id: ID!) {
MyNode(ids: [$id]) {
# ...
}
}
computed_circuit_description.gql
query CircuitDescriptionQuery($id: ID!) {
InfraCircuit(ids: [$id]) {
edges {
node {
circuit_id {
value
}
endpoints {
edges {
node {
name { value }
location {
node {
name {
value
}
}
}
}
}
}
}
}
}
}
  1. Next, the Python script that implements the logic
computed_circuit_description.py
from infrahub_sdk.transforms import InfrahubTransform


class ComputedCircuitDescription(InfrahubTransform):
query = "computed_circuit_description"
url = "computed_circuit_description"

async def transform(self, data):
circuit_dict: dict = data["InfraCircuit"]["edges"][0]["node"]

detailed_endpoints: list[str] = []

for endpoint in circuit_dict["endpoints"]["edges"]:
detailed_endpoints.append(
f'{endpoint["node"]["location"]["node"]["name"]["value"]}::{endpoint["node"]["name"]["value"]}'
)

return f' <- {circuit_dict["circuit_id"]["value"]} -> '.join(detailed_endpoints)
note

Make sure that the script returns a string!

  1. Tie everything together into .infrahub.yml file
.infrahub.yml
---
python_transforms:
- name: computed_circuit_description
class_name: ComputedCircuitDescription
file_path: computed_circuit_description.py

queries:
- name: computed_circuit_description
file_path: computed_circuit_description.gql

At this stage you should be able to test your transform using infrahubctl

note

To create a meaningful test, you may want to create: two sites, one circuit, and two endpoints connecting the circuit to the two sites.

❯ infrahubctl transform computed_circuit_description id=180b4900-7ea4-f4b3-3b5f-c519831a4b93
"site1::endpoint-a <- circuit1 -> site2::endpoint-b"
  1. Load the transform in Infrahub

You need to commit these files to a Git repository and link the repository to Infrahub. Please refer to the adding a repository to Infrahub guide.

success

The circuit's description is now automatically computed (this process may take a few seconds). Any changes to related attributes (i.e., endpoint name) will trigger a reevaluation and update the value accordingly.