Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
255 changes: 255 additions & 0 deletions docs/docs/guides/object-template.mdx
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there's an example in here for using NumberPools on an attribute in a template, which would probably be helpful. I don't know if it needs to be a full example, but it is unexpected that you have to use a relationship to specify a pool for an attribute

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think from a user perspective, it should be the same and that seems like an implementation detail that we'd be discussing in the guide? We can potentially discuss this in the topic.

Original file line number Diff line number Diff line change
Expand Up @@ -333,3 +333,258 @@ For more information about Profiles and templates, see:

- [Creating and assigning Profiles](../guides/profiles.mdx) - Guide for working with Profiles
- [Understanding Profiles in Infrahub](../topics/profiles.mdx) - Deep dive into Profile concepts

## Allocating resources from pools via templates

Object templates support automatic resource allocation. This section extends the device and interface schema from above by adding IP address support to `InfraInterface`, then shows how to wire a `CoreIPAddressPool` to the interface template.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd add a link to the related topic section here, Like "if you want to know more about xyz"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is documented at the bottom of the guide to learn more about resource managers. Do you want me to move that up to here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bumping this so I can get this merged in ASAP.

### Update the schema

Add the two IPAM nodes and an `ip_address` relationship to `InfraInterface`, if you don't already have them:

```yaml
---
# yaml-language-server: $schema=https://schema.infrahub.app/infrahub/schema/latest.json
version: "1.0"

nodes:
- name: IPAddress
namespace: Ipam
label: "IP Address"
display_label: "{{ address__value }}"
inherit_from:
- BuiltinIPAddress

- name: IPPrefix
namespace: Ipam
label: "IP Prefix"
display_label: "{{ prefix__value }}"
inherit_from:
- BuiltinIPPrefix

- name: Interface
namespace: Infra
label: "Interface"
include_in_menu: true
icon: "mdi:ethernet"
display_label: "{{ name__value }}"
order_by:
- name__value
uniqueness_constraints:
- ["device", "name__value"]
human_friendly_id: ["device__name__value", "name__value"]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you need generate_template: true on this schema

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a component relationship on device so that gets created automatically. We're building on the schema at the start of the guide, but maybe I just add the full schemas into this one as well?

attributes:
- name: name
kind: Text
- name: description
kind: Text
optional: true
- name: enable
kind: Boolean
optional: false
default_value: false
relationships:
- name: device
peer: InfraDevice
optional: false
cardinality: one
kind: Parent
- name: ip_address
peer: IpamIPAddress
optional: true
cardinality: one
kind: Attribute
```

:::info

When a schema node has a relationship or attribute that supports resource pool allocation, Infrahub automatically generates a corresponding `<attribute_or_relationship_name>_from_resource_pool` field on the template node. In this example, because `IpamIPAddress` can be allocated from a `CoreIPAddressPool`, an `ip_address_from_resource_pool` field is generated on `TemplateInfraInterface`. This is what connects the template to the pool.

:::

Load this updated schema into your Infrahub instance before proceeding.

### Create the resource pool

First create a prefix to draw addresses from, then create the pool backed by that prefix.

<Tabs groupId="method" queryString>
<TabItem value="web" label="Via the Web Interface" default>

**Create the IP prefix:**

1. Navigate to `IPAM` > `Prefixes`
2. Click `+ Add IP Prefix`
3. Fill in:
- **Prefix**: `192.168.0.0/16`
- **Member Type**: `address`
- **Is Pool**: `true`
4. Save

**Create the IP address pool:**

1. Navigate to `Resource Manager` > `IP Address Pool`
2. Click `+ Add IP Address Pool`
3. Fill in:
- **Name**: `Interface IP Pool`
- **Default Address Type**: `IpamIPAddress`
- **IP Namespace**: `default`
- **Resources**: select `192.168.0.0/16`
4. Save

</TabItem>
<TabItem value="graphql" label="Via the GraphQL Interface">

```graphql
mutation CreatePrefix {
IpamIPPrefixCreate(
data: {prefix: {value: "192.168.0.0/16"}, member_type: {value: "address"}, is_pool: {value: true}}
) {
ok
object { id }
}
}
```

Then use the returned prefix ID to create the pool:

```graphql
mutation CreatePool($prefixId: String!) {
CoreIPAddressPoolCreate(
data: {
name: {value: "Interface IP Pool"}
default_address_type: {value: "IpamIPAddress"}
ip_namespace: {hfid: ["default"]}
resources: [{id: $prefixId}]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wonder if we can't use prefix HFID here (haven't tried tho):

Suggested change
resources: [{id: $prefixId}]
resources: [["192.168.0.0/16", "default"]]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not work:

mutation CreatePrefix {
  IpamIPPrefixCreate(
    data: {
      prefix: {value: "192.168.0.0/16"}
      member_type: {value: "address"}
      is_pool: {value: true}
    }
  ) {
    ok
    object {
      id
    }
  }
}

{
  "data": null,
  "errors": [
    {
      "message": "Expected value of type 'RelatedIPPrefixNodeInput', found [\"192.168.0.0/16\", \"default\"].",
      "locations": [
        {
          "line": 52,
          "column": 19
        }
      ]
    }
  ]
}

Tried with HFID, but the BuiltinIPPrefix does not have an HFID defined.

mutation CreatePool {
  CoreIPAddressPoolCreate(
    data: {
      name: {value: "Interface IP Pool"}
      default_address_type: {value: "IpamIPAddress"}
      ip_namespace: {hfid: ["default"]}
      resources: {hfid: ["192.168.0.0/16", "default"]}
    }
  ) {
    ok
  }
}

{
  "data": {
    "CoreIPAddressPoolCreate": null
  },
  "errors": [
    {
      "message": "Unable to lookup node by HFID, schema 'BuiltinIPPrefix' does not have a HFID defined.",
      "locations": [
        {
          "line": 47,
          "column": 3
        }
      ],
      "path": [
        "CoreIPAddressPoolCreate"
      ]
    }
  ]
}

}
) {
ok
}
}
```

</TabItem>
<TabItem value="object-file" label="Via an Object File">

```yaml
---
apiVersion: infrahub.app/v1
kind: Object
spec:
kind: CoreIPAddressPool
data:
- name: "Interface IP Pool"
default_address_type: IpamIPAddress
ip_namespace: default
resources:
kind: IpamIPPrefix
data:
- prefix: 192.168.0.0/16
member_type: address
is_pool: true
```

Load with: `infrahubctl object load pools.yml`

</TabItem>
<TabItem value="python-sdk" label="Via the Python SDK">

```python
async def run(client: InfrahubClient, log: logging.Logger, branch: str) -> None:
prefix = await client.create(
kind="IpamIPPrefix",
data={"prefix": "192.168.0.0/16", "member_type": "address", "is_pool": True},
)
await prefix.save(allow_upsert=True)

pool = await client.create(
kind="CoreIPAddressPool",
data={
"name": "Interface IP Pool",
"default_address_type": "IpamIPAddress",
"ip_namespace": {"hfid": ["default"]},
"resources": [{"id": prefix.id}],
},
)
await pool.save(allow_upsert=True)
```

</TabItem>
</Tabs>

### Assign the pool to the interface template

With the pool created, update the interface template from the previous section to allocate an IP address from it whenever a new interface is created.

<Tabs groupId="method" queryString>
<TabItem value="web" label="Via the Web Interface" default>

1. Navigate to `Object Management` > `Templates`
2. Open `Template-SwitchModel123-Ethernet1` (or any interface template)
3. On the `IP Address` field, click the pool selector button and choose `Interface IP Pool`
4. The field will show an `Allocated by pool` badge
5. Save
Comment on lines +517 to +526
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Clarify whether allocation applies to one interface or all configured templates.

These steps tell the reader to update a single interface template, but the closing paragraph says every generated interface gets an address. As written, a reader who only updates Template-SwitchModel123-Ethernet1 will only see allocation on that configured template. Please either instruct them to repeat the step for each interface template or narrow the claim in Lines 577-579.

✍️ Suggested wording
- With the pool created, update the interface template from the previous section to allocate an IP address from it whenever a new interface is created.
+ With the pool created, update each interface template that should allocate an IP address from the pool when a new interface is created.

- 2. Open `Template-SwitchModel123-Ethernet1` (or any interface template)
+ 2. Open each interface template that should receive an allocated address, starting with `Template-SwitchModel123-Ethernet1`

- Creating a device from the template works exactly as described in the [Create object instances](`#create-object-instances-using-the-predefined-template`) section above. No additional steps are required — when the device is saved, Infrahub allocates an available IP address from `Interface IP Pool` and assigns it to each interface's `ip_address` field.
+ Creating a device from the template works exactly as described in the [Create object instances](`#create-object-instances-using-the-predefined-template`) section above. No additional steps are required — when the device is saved, Infrahub allocates an available IP address from `Interface IP Pool` for each interface template configured with `ip_address_from_resource_pool`.

Also applies to: 577-579

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/docs/guides/object-template.mdx` around lines 517 - 526, The
instructions are ambiguous about scope: update the text around the "IP Address"
field steps (which reference Template-SwitchModel123-Ethernet1 and the
"Allocated by pool" badge) to clarify that allocating from "Interface IP Pool"
only applies to the specific template you edit and therefore must be repeated
for each interface template you want to auto-allocate, or alternatively change
the closing claim (Lines 577–579) to say "each configured template you update
will receive an address" instead of implying all generated interfaces are
covered automatically; make the change either by adding a brief sentence after
step 5 instructing the user to repeat for other interface templates or by
narrowing the closing paragraph's wording so the scope is accurate.


</TabItem>
<TabItem value="graphql" label="Via the GraphQL Interface">

```graphql
mutation {
TemplateInfraInterfaceUpdate(
data: {
hfid: ["Template-SwitchModel123", "Template-SwitchModel123-Ethernet1"]
ip_address_from_resource_pool: {hfid: ["Interface IP Pool"]}
}
) {
ok
}
}
```

</TabItem>
<TabItem value="object-file" label="Via an Object File">

```yaml
---
apiVersion: infrahub.app/v1
kind: Object
spec:
kind: TemplateInfraInterface
data:
- template_name: "Template-SwitchModel123-Ethernet1"
device: "Template-SwitchModel123"
ip_address_from_resource_pool: "Interface IP Pool"
```
Comment on lines +545 to +557
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Finish the object-file path with the load step.

This tab stops at the YAML example, so the object-file workflow is not fully executable end to end. Please add the infrahubctl object load ... step here, like you did in the pool-creation tab above.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/docs/guides/object-template.mdx` around lines 545 - 557, The object-file
tab stops at the YAML example and needs the final load step; after the YAML for
the Object (spec.kind: TemplateInfraInterface, template_name:
"Template-SwitchModel123-Ethernet1") add a sentence and the corresponding
infrahubctl CLI invocation (infrahubctl object load <path-to-file> --namespace
<ns> or similar as used in the pool-creation tab) so readers can execute the
object-file workflow end-to-end; mirror the style and flags used in the
pool-creation tab to keep consistency.


</TabItem>
<TabItem value="python-sdk" label="Via the Python SDK">

```python
async def run(client: InfrahubClient, log: logging.Logger, branch: str) -> None:
pool = await client.get(kind="CoreIPAddressPool", hfid=["Interface IP Pool"])

iface_template = await client.get(
kind="TemplateInfraInterface",
hfid=["Template-SwitchModel123", "Template-SwitchModel123-Ethernet1"],
)
iface_template.ip_address_from_resource_pool = {"id": pool.id}
await iface_template.save(allow_upsert=True)
```

</TabItem>
</Tabs>

### Create objects — IP addresses are allocated automatically

Creating a device from the template works exactly as described in the [Create object instances](#create-object-instances-using-the-predefined-template) section above. No additional steps are required — when the device is saved, Infrahub allocates an available IP address from `Interface IP Pool` and assigns it to each interface's `ip_address` field.

:::info

Resource allocation happens at object creation time, not at template creation time. Each new device gets its own unique address drawn from the pool.

:::

For more information about resource pools, see:

- [Managing resource pools](../guides/resource-manager.mdx) - Guide for creating and using pools
- [Resource Manager concepts](../topics/resource-manager.mdx) - Deep dive into allocation behaviour
16 changes: 16 additions & 0 deletions docs/docs/topics/object-template.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,22 @@ This combination allows you to use templates for structural definition (which po

For more details, see the [Creating and assigning Profiles guide](../guides/profiles.mdx).

## Integration with Resource Pools
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have any precise use case in mind for that integration, e.g. management interface IP if all device using this template are sharing the same prefix. On that perhaps it's worth mentioning when not using it, if for instance I need to use different pool based on the site I can't put this in the template.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me think about this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any documented use cases for this specifically.

I could see your use case, being defined as multiple object templates based on a site that they would select from the list.


Templates can be wired to resource pools so that unique values are allocated automatically when objects are created from the template. Rather than manually assigning an IP address or number attribute to each new object, you define once on the template which pool to draw from and Infrahub handles allocation at creation time.

### How it works

When a schema node has a relationship or attribute whose peer type supports resource pool allocation (for example, a relationship to an IP address node, or a number attribute), Infrahub automatically generates a `<attribute_or_relationship_name>_from_resource_pool` field on the corresponding template node. Assigning a pool to that field is all that is needed — no changes to object creation are required.

Allocation happens at object creation time, not at template creation time. Each object created from the template receives its own unique value drawn from the pool.

### Why this matters

Without pool integration, templates can only set static values. Any attribute that must be unique per object (IP addresses, circuit IDs, interface numbers) would have to be filled in manually each time. Pool integration closes that gap: the template defines the structural intent and the pool ensures each instance gets a valid, non-conflicting value automatically.

For step-by-step instructions, see [Allocating resources from pools via templates](../guides/object-template.mdx#allocating-resources-from-pools-via-templates).

## Component relationship

When enabling template generation on a given schema node, Infrahub automatically detects whether the object has any component relationships. If it does, Infrahub will generate corresponding templates for those related objects as well.
Expand Down
Loading