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
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:
- Add the attribute to the schema.
- 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.
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.
- First we prepare a GraphQL query that returns the data we need
GraphQL query used to compute attribute's value requires ID
as parameter.
query MyQuery($id: ID!) {
MyNode(ids: [$id]) {
# ...
}
}
query CircuitDescriptionQuery($id: ID!) {
InfraCircuit(ids: [$id]) {
edges {
node {
circuit_id {
value
}
endpoints {
edges {
node {
name { value }
location {
node {
name {
value
}
}
}
}
}
}
}
}
}
}
- Next, the Python script that implements the logic
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)
Make sure that the script returns a string!
- Tie everything together into
.infrahub.yml
file
---
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
To create a meaningful test, you may want to create: two sites, one circuit, and two endpoints connecting the circuit to the two sites.
"site1::endpoint-a <- circuit1 -> site2::endpoint-b"
- 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.
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.