Skip to content

simpler create/update for Template relationship pools#8535

Open
ajtmccarty wants to merge 10 commits intorelease-1.8from
ajtm-03062026-template-rel-pools
Open

simpler create/update for Template relationship pools#8535
ajtmccarty wants to merge 10 commits intorelease-1.8from
ajtm-03062026-template-rel-pools

Conversation

@ajtmccarty
Copy link
Contributor

@ajtmccarty ajtmccarty commented Mar 7, 2026

goes with opsmill/infrahub-sdk-python#863 in the SDK

Why

Current approach to supporting relationships that provision from resource pools on templates is to use a xyz_from_resource_pool relationship that links to the CoreIPAddress/PrefixPool to use when provisioning from the template and where xyz is the name of the relationship defined on the node schema's template

for example, I create the InfraInterface node schema that includes a prefix relationship with a peer of BuiltinIPPrefix. then I create a template for InfraInterface and I set the prefix_from_resource_pool relationship to point to the CoreIPPrefixPool that I want to provision from when creating an InfraInterface instance using this template.

there is a good reason for using this approach in the backend (we don't have a way to save metadata about a relationship that does not yet exist), but the end user shouldn't have to deal with our implementation details. this approach is made even more confusing b/c we support NumberPools on attributes in templates in the regular way (i.e. {from_pool: {id: "..."}})

What changed

  • set the generated _from_resource_pool relationships on templates to be read-only so that the user cannot try to edit them
  • update the create and update mutations to support special handling for Templates so that creating a Template with a from_pool on a relationship that includes a _from_resource_pool relationship will link the pool to the template using the _from_resource_pool relationship, which is what the backend template provisioning logic expects
  • tests for this and updates to existing tests to remove direct use of the _from_resource_pool relationships

Summary by CodeRabbit

  • New Features

    • Template mutations now route and apply pool-backed relationships automatically, supporting swaps and clears between pool and direct references
    • Added optional hfid/id inputs for IP address relationships for finer identification
  • Bug Fixes

    • Validation prevents specifying both a direct reference and a pool-origin reference simultaneously
    • Resource-pool relationships are now treated as read-only where applicable
  • Tests

    • Added and expanded tests covering pool vs direct relationship create/update lifecycle
  • Chores

    • Updated SDK submodule

@github-actions github-actions bot added the group/backend Issue related to the backend (API Server, Git Agent) label Mar 7, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 7, 2026

Walkthrough

Introduces TemplatePoolHandler to route pool-backed relationships for template create/update and invokes it in GraphQL mutation logic when the active schema is a TemplateSchema. Marks the resource-pool RelationshipSchema created in schema_branch as read_only. Adds hfid to RelatedNodeInput/RelatedIPAddressNodeInput (and id in the frontend GraphQL type). Adds tests covering template pool create/update/swap/clear flows, updates several functional/integration tests, updates schema.graphql, and advances the python_sdk submodule reference.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'simpler create/update for Template relationship pools' is concise, specific, and directly describes the main change: simplifying how users create/update templates with pool-based relationships by hiding implementation details.
Description check ✅ Passed The description covers Why (with context and motivation), What changed (behavioral changes and implementation notes), and concludes with testing information, aligning well with the template structure despite not filling every optional section.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codspeed-hq
Copy link

codspeed-hq bot commented Mar 7, 2026

Merging this PR will degrade performance by 27.83%

❌ 12 regressed benchmarks

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
test_schemabranch_process 794.9 ms 1,041 ms -23.64%
test_query_one_model 353.4 ms 464.8 ms -23.98%
test_schemabranch_duplicate 5.5 ms 7.3 ms -23.99%
test_load_node_to_db_node_schema 51.7 ms 66.6 ms -22.32%
test_get_menu 188.1 ms 245.9 ms -23.51%
test_get_schema 251.5 ms 328.1 ms -23.33%
test_query_rel_many 523.3 ms 693.7 ms -24.55%
test_relationshipmanager_getpeer 132.4 µs 157.3 µs -15.84%
test_graphql_generate_schema 373.4 ms 517.3 ms -27.83%
test_query_rel_one 500.9 ms 662.6 ms -24.4%
test_base_schema_duplicate_CoreProposedChange 1.7 ms 2.1 ms -19.32%
test_nodemanager_querypeers 1.2 ms 1.5 ms -17.69%

Comparing ajtm-03062026-template-rel-pools (0506f47) with release-1.8 (1066aa5)

Open in CodSpeed

@github-actions github-actions bot added the group/frontend Issue related to the frontend (React) label Mar 7, 2026
@ajtmccarty ajtmccarty marked this pull request as ready for review March 8, 2026 00:04
@ajtmccarty ajtmccarty requested review from a team as code owners March 8, 2026 00:04
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
backend/infrahub/graphql/mutations/template_pool_router.py (1)

83-86: This review comment addresses a speculative future scenario rather than a current issue.

GenericPoolInput.id is currently marked as required=True and the input type does not support hfid lookups. Accessing from_pool["id"] is safe given the current schema constraints—a KeyError cannot occur unless the schema is extended to make id optional and add hfid support (as exists in RelatedNodeInput).

If defensive programming for future schema extensibility is desired, the proposed fix is reasonable; however, it is not addressing a bug in the current codebase.

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

In `@backend/infrahub/graphql/mutations/template_pool_router.py` around lines 83 -
86, The current access of from_pool["id"] in template_pool_router.py is safe
given GenericPoolInput.id is required and hfid lookups are not supported; no
code change is necessary—keep the existing assignment to data[pool_rel_name] =
{"id": from_pool["id"]} and data[rel_name] = None; if you prefer future-proofing
when extending the schema, replace the direct index with a defensive lookup
(e.g., use from_pool.get("id") and fall back to handling an hfid path similar to
RelatedNodeInput) but this is optional and not required to fix a bug now.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/infrahub/graphql/mutations/main.py`:
- Around line 250-252: The payload routing for template pool relationships is
mutating the public input (TemplatePoolHandler.route_relationships on
create_data) before user-input validation, which writes keys like
"<rel>_from_resource_pool" that are defined read_only=True in schema_branch.py
and then rejected by create_node()/obj.from_graphql(); change the flow so
routing doesn't mutate the validated input: either call
TemplatePoolHandler.route_relationships only after validation (move the call to
run after create_node()/obj.from_graphql input validation) or add an internal
handoff flag to route_relationships/create_node()/from_graphql (e.g.,
internal=True or bypass_readonly=True) so routed "<rel>_from_resource_pool"
entries are injected into the node state after validation and skip read-only
validation; update usages in TemplateSchema handling to use the chosen approach
and ensure schema_branch read_only semantics are preserved.

In
`@backend/tests/component/graphql/templates/test_template_pool_relationships.py`:
- Around line 422-428: The test is asserting against a stale `template` object
after reloading; replace the direct use of `template` with the refreshed
`reloaded` instance so the post-update state is validated correctly: change the
call `template.get_relationship("primary_ip").get_peer(db=db)` to use
`reloaded.get_relationship("primary_ip").get_peer(db=db)` (keeping the existing
awaits and assertions) so both relationship checks use the
NodeManager.get_one-refreshed object.

---

Nitpick comments:
In `@backend/infrahub/graphql/mutations/template_pool_router.py`:
- Around line 83-86: The current access of from_pool["id"] in
template_pool_router.py is safe given GenericPoolInput.id is required and hfid
lookups are not supported; no code change is necessary—keep the existing
assignment to data[pool_rel_name] = {"id": from_pool["id"]} and data[rel_name] =
None; if you prefer future-proofing when extending the schema, replace the
direct index with a defensive lookup (e.g., use from_pool.get("id") and fall
back to handling an hfid path similar to RelatedNodeInput) but this is optional
and not required to fix a bug now.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 04282a37-1531-43a8-bcdb-cf639030f328

📥 Commits

Reviewing files that changed from the base of the PR and between 8490fab and c9213da.

📒 Files selected for processing (11)
  • backend/infrahub/core/schema/schema_branch.py
  • backend/infrahub/graphql/mutations/main.py
  • backend/infrahub/graphql/mutations/template_pool_router.py
  • backend/infrahub/graphql/types/attribute.py
  • backend/tests/component/graphql/templates/__init__.py
  • backend/tests/component/graphql/templates/test_template_pool_relationships.py
  • backend/tests/functional/templates/test_template_resource_pool.py
  • backend/tests/integration/templates/test_template_resource_pool_lifecycle.py
  • frontend/app/src/shared/api/graphql/graphql-env.d.ts
  • python_sdk
  • schema/schema.graphql

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
backend/tests/component/graphql/templates/test_template_pool_relationships.py (2)

85-92: Add a GraphQL negative case for the internal relationship.

This suite covers the happy path through primary_ip, but it never proves that primary_ip_from_resource_pool is actually read-only at the GraphQL layer. A small create/update mutation that tries to set the internal field directly and asserts validation failure would lock down the other half of this PR's contract.

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

In
`@backend/tests/component/graphql/templates/test_template_pool_relationships.py`
around lines 85 - 92, Add a negative GraphQL test method inside
TestTemplatePoolRelationships that attempts to set the internal relationship
field primary_ip_from_resource_pool directly via a create (and optionally
update) mutation and asserts the operation is rejected; locate the GraphQL
mutation helpers used elsewhere in this test file (e.g., the
createTemplate/updateTemplate mutation calls) and reuse them to submit a payload
containing primary_ip_from_resource_pool, then assert the response contains a
validation/error entry (HTTP error or GraphQL errors) and that no object is
created/modified. Ensure the test name clearly indicates it verifies
primary_ip_from_resource_pool is read-only at the GraphQL layer.

368-382: The “by name” cases still exercise from_pool.id.

Both tests pass "tpl-test-address-pool" via id, so they do not isolate the new explicit from_pool.hfid/name lookup branch. If id continues to accept friendly identifiers, these cases can stay green while the dedicated HFID path regresses. I'd add separate create/update mutations that populate from_pool.hfid instead of reusing the ...WITH_POOL documents.

Also applies to: 396-417

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

In
`@backend/tests/component/graphql/templates/test_template_pool_relationships.py`
around lines 368 - 382, The test currently exercises the id-based branch because
it passes "tpl-test-address-pool" into the variable used for from_pool.id; add
explicit HFID-path tests instead: create new test functions (e.g.
test_create_template_with_pool_by_hfid and corresponding update test) that call
the same mutation document or a small variant but supply a GraphQL input that
sets from_pool.hfid (not from_pool.id) and pass the actual HFID value (from the
ip_address_pool fixture) via variable_values; ensure the mutation
document/variable names you use target the hfid field (refer to
CREATE_TEMPLATE_WITH_POOL and from_pool.hfid/from_pool.id) so the code path for
HFID lookup is exercised independently of the id-friendly-identifier branch.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@backend/tests/component/graphql/templates/test_template_pool_relationships.py`:
- Around line 85-92: Add a negative GraphQL test method inside
TestTemplatePoolRelationships that attempts to set the internal relationship
field primary_ip_from_resource_pool directly via a create (and optionally
update) mutation and asserts the operation is rejected; locate the GraphQL
mutation helpers used elsewhere in this test file (e.g., the
createTemplate/updateTemplate mutation calls) and reuse them to submit a payload
containing primary_ip_from_resource_pool, then assert the response contains a
validation/error entry (HTTP error or GraphQL errors) and that no object is
created/modified. Ensure the test name clearly indicates it verifies
primary_ip_from_resource_pool is read-only at the GraphQL layer.
- Around line 368-382: The test currently exercises the id-based branch because
it passes "tpl-test-address-pool" into the variable used for from_pool.id; add
explicit HFID-path tests instead: create new test functions (e.g.
test_create_template_with_pool_by_hfid and corresponding update test) that call
the same mutation document or a small variant but supply a GraphQL input that
sets from_pool.hfid (not from_pool.id) and pass the actual HFID value (from the
ip_address_pool fixture) via variable_values; ensure the mutation
document/variable names you use target the hfid field (refer to
CREATE_TEMPLATE_WITH_POOL and from_pool.hfid/from_pool.id) so the code path for
HFID lookup is exercised independently of the id-friendly-identifier branch.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: de8aa5cf-2833-411f-b48a-e6834e4c9ac5

📥 Commits

Reviewing files that changed from the base of the PR and between c9213da and 52541a6.

📒 Files selected for processing (1)
  • backend/tests/component/graphql/templates/test_template_pool_relationships.py

direct_peer = await reloaded.get_relationship("primary_ip").get_peer(db=db)
assert direct_peer is None

async def test_null_clears_both_relationships(
Copy link
Contributor

@polmichel polmichel Mar 8, 2026

Choose a reason for hiding this comment

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

I feel this test could be misleading, as the 'primary_ip' relationship is never populated during the 'save' method.
I will modify this test in a commit so that it only explicitly checks both relationships emptiness while verifying the 'primary_ip_from_resource_pool' relationship is correctly populated during the save method.
I will add another one based on 'primary_ip' template creation

Copy link
Contributor

Choose a reason for hiding this comment

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

Please tell me if I am wrong about the intention behind this test

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
backend/tests/component/graphql/templates/test_template_pool_relationships.py (1)

109-111: Consider using public API instead of private attribute access.

Line 109 accesses registry._default_ipnamespace (private) while line 111 uses registry.default_ipnamespace (public property). This asymmetry is fragile—if the internal implementation changes, the test may silently fail or behave unexpectedly.

If there's a public getter available, prefer using it for consistency; otherwise, this pattern is acceptable for test setup code.

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

In
`@backend/tests/component/graphql/templates/test_template_pool_relationships.py`
around lines 109 - 111, The test should avoid reading the private attribute
registry._default_ipnamespace; use the public API consistently by checking the
public property/ getter registry.default_ipnamespace (or calling the public
method if one exists) before creating an IPAM namespace, and then assign via the
public setter/attribute registry.default_ipnamespace = ip_namespace.id after
create_ipam_namespace(db=db); update the conditional to reference
registry.default_ipnamespace instead of registry._default_ipnamespace to keep
usage symmetrical and resilient to internal changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@backend/tests/component/graphql/templates/test_template_pool_relationships.py`:
- Around line 109-111: The test should avoid reading the private attribute
registry._default_ipnamespace; use the public API consistently by checking the
public property/ getter registry.default_ipnamespace (or calling the public
method if one exists) before creating an IPAM namespace, and then assign via the
public setter/attribute registry.default_ipnamespace = ip_namespace.id after
create_ipam_namespace(db=db); update the conditional to reference
registry.default_ipnamespace instead of registry._default_ipnamespace to keep
usage symmetrical and resilient to internal changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 24b66d1b-f636-4515-8bfd-c647d7c26f0c

📥 Commits

Reviewing files that changed from the base of the PR and between 52541a6 and 0506f47.

📒 Files selected for processing (2)
  • backend/tests/component/graphql/templates/test_template_pool_relationships.py
  • python_sdk
✅ Files skipped from review due to trivial changes (1)
  • python_sdk

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

group/backend Issue related to the backend (API Server, Git Agent) group/frontend Issue related to the frontend (React)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants