Skip to content

Commit fa1b3dd

Browse files
authored
Merge pull request #7293 from opsmill/dga-20250930-fix-7259
Ignore attribute from template if the value is not defined
2 parents 3cd6a7b + 727e940 commit fa1b3dd

File tree

4 files changed

+71
-11
lines changed

4 files changed

+71
-11
lines changed

backend/infrahub/core/node/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,9 @@ async def handle_object_template(self, fields: dict, db: InfrahubDatabase, error
408408
for attribute_name in template._attributes:
409409
if attribute_name in list(fields) + [OBJECT_TEMPLATE_NAME_ATTR]:
410410
continue
411-
fields[attribute_name] = {"value": getattr(template, attribute_name).value, "source": template.id}
411+
attr_value = getattr(template, attribute_name).value
412+
if attr_value is not None:
413+
fields[attribute_name] = {"value": attr_value, "source": template.id}
412414

413415
for relationship_name in template._relationships:
414416
relationship_schema = template._schema.get_relationship(name=relationship_name)

backend/infrahub/core/node/create.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,19 @@ async def extract_peer_data(
4141
) -> Mapping[str, Any]:
4242
obj_peer_data: dict[str, Any] = {}
4343

44-
for attr in template_peer.get_schema().attribute_names:
45-
if attr not in obj_peer_schema.attribute_names:
44+
for attr_name in template_peer.get_schema().attribute_names:
45+
template_attr = getattr(template_peer, attr_name)
46+
if template_attr.value is None:
4647
continue
47-
obj_peer_data[attr] = {"value": getattr(template_peer, attr).value, "source": template_peer.id}
48+
if template_attr.is_default:
49+
# if template attr is_default and the value matches the object schema, then do not set the source
50+
try:
51+
if obj_peer_schema.get_attribute(name=attr_name).default_value == template_attr.value:
52+
continue
53+
except ValueError:
54+
pass
55+
56+
obj_peer_data[attr_name] = {"value": template_attr.value, "source": template_peer.id}
4857

4958
for rel in template_peer.get_schema().relationship_names:
5059
rel_manager: RelationshipManager = getattr(template_peer, rel)

backend/tests/unit/graphql/test_mutation_create.py

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1309,7 +1309,7 @@ async def test_create_with_object_template(
13091309

13101310
device_template: Node = await Node.init(schema=f"Template{TestKind.DEVICE}", db=db, branch=branch)
13111311
await device_template.new(
1312-
db=db, template_name="MX204 Router", manufacturer="Juniper", height=1, weight=6, airflow="Front to rear"
1312+
db=db, template_name="MX204 Router", manufacturer="Juniper", height=1, weight=0, airflow="Front to rear"
13131313
)
13141314
await device_template.save(db=db)
13151315

@@ -1323,7 +1323,11 @@ async def test_create_with_object_template(
13231323
assert not result.errors
13241324

13251325
device = await NodeManager.get_one(
1326-
db=db, kind=TestKind.DEVICE, branch=branch, id=result.data[f"{TestKind.DEVICE}Create"]["object"]["id"]
1326+
db=db,
1327+
kind=TestKind.DEVICE,
1328+
branch=branch,
1329+
id=result.data[f"{TestKind.DEVICE}Create"]["object"]["id"],
1330+
include_source=True,
13271331
)
13281332
assert device
13291333
assert device.name.value == "th2.par.asbr01"
@@ -1333,10 +1337,26 @@ async def test_create_with_object_template(
13331337
# No interfaces as there are none on the object template
13341338
device_interfaces = await device.interfaces.get_peers(db=db)
13351339
assert not device_interfaces
1340+
# verify attributes and metadata
1341+
assert device.manufacturer.value == "Juniper"
1342+
assert device.manufacturer.is_default is False
1343+
assert device.manufacturer.source_id == device_template.id
1344+
assert device.height.value == 1
1345+
assert device.height.is_default is False
1346+
assert device.height.source_id == device_template.id
1347+
assert device.weight.value == 0
1348+
assert device.weight.is_default is False
1349+
assert device.weight.source_id == device_template.id
1350+
assert device.airflow.value.value == "Front to rear"
1351+
assert device.airflow.is_default is False
1352+
assert device.airflow.source_id == device_template.id
1353+
assert device.part_number.value is None
1354+
assert device.part_number.is_default is True
1355+
assert device.part_number.source_id is None
13361356

13371357
# Create interfaces on object template
13381358
if_names = ["et-0/0/0", "et-0/0/1", "et-0/0/2", "et-0/0/3"]
1339-
interface_templates: list[Node] = []
1359+
interface_templates_by_name: dict[str, Node] = {}
13401360
for if_name in if_names:
13411361
interface_template: Node = await Node.init(
13421362
schema=f"Template{TestKind.PHYSICAL_INTERFACE}", db=db, branch=branch
@@ -1345,9 +1365,9 @@ async def test_create_with_object_template(
13451365
db=db, template_name=f"MX204 {if_name}", device=device_template, name=if_name, phys_type="QSFP28 (100GE)"
13461366
)
13471367
await interface_template.save(db=db)
1348-
interface_templates.append(interface_template)
1368+
interface_templates_by_name[if_name] = interface_template
13491369

1350-
await device_template.interfaces.update(db=db, data=interface_templates)
1370+
await device_template.interfaces.update(db=db, data=list(interface_templates_by_name.values()))
13511371
await device_template.save(db=db)
13521372

13531373
result = await graphql(
@@ -1368,14 +1388,27 @@ async def test_create_with_object_template(
13681388
device_template_node = await device.object_template.get_peer(db=db)
13691389
assert device_template_node.id == device_template.id
13701390
# Validate that interfaces relationship has been populated according to object template
1371-
interfaces = await NodeManager.query(db=db, branch=branch, schema=TestKind.PHYSICAL_INTERFACE)
1391+
interfaces = await NodeManager.query(db=db, branch=branch, schema=TestKind.PHYSICAL_INTERFACE, include_source=True)
13721392
assert len(interfaces) == len(if_names)
13731393
device_interfaces = await device.interfaces.get_peers(db=db)
13741394
assert len(device_interfaces) == len(if_names)
13751395
assert sorted([interface.name.value for interface in device_interfaces.values()]) == if_names
1396+
# verify attributes and metadata on interfaces
1397+
for interface in interfaces:
1398+
assert interface.name.value in ["et-0/0/0", "et-0/0/1", "et-0/0/2", "et-0/0/3"]
1399+
template_obj = interface_templates_by_name[interface.name.value]
1400+
assert interface.name.is_default is False
1401+
assert interface.name.source_id == template_obj.id
1402+
assert interface.phys_type.value.value == "QSFP28 (100GE)"
1403+
assert interface.phys_type.is_default is False
1404+
assert interface.phys_type.source_id == template_obj.id
1405+
assert interface.enabled.value is True
1406+
assert interface.enabled.is_default is True
1407+
assert interface.enabled.source_id is None
13761408

13771409
# Add a SFP to each interface of the object template
13781410
template_interfaces = await device_template.interfaces.get_peers(db=db)
1411+
sfp_templates_by_interface_name: dict[str, Node] = {}
13791412
for interface in template_interfaces.values():
13801413
sfp_template: Node = await Node.init(schema=f"Template{TestKind.SFP}", db=db, branch=branch)
13811414
await sfp_template.new(
@@ -1386,6 +1419,7 @@ async def test_create_with_object_template(
13861419
serial_number=f"QSFP-{interface.name.value}",
13871420
)
13881421
await sfp_template.save(db=db)
1422+
sfp_templates_by_interface_name[interface.name.value] = sfp_template
13891423

13901424
result = await graphql(
13911425
schema=gql_params.schema,
@@ -1411,7 +1445,21 @@ async def test_create_with_object_template(
14111445
# Validate that one SFP is attached to each interface
14121446
device_sfps = [await interface.sfp.get_peer(db=db) for interface in device_interfaces.values()]
14131447
assert len(device_sfps) == len(if_names)
1414-
assert sorted([(await sfp.interface.get_peer(db=db)).name.value for sfp in device_sfps]) == if_names
1448+
sfp_interfaces = [await sfp.interface.get_peer(db=db) for sfp in device_sfps]
1449+
assert sorted([interface.name.value for interface in sfp_interfaces]) == if_names
1450+
# verify attributes and metadata on SFPs
1451+
for sfp in device_sfps:
1452+
interface_name = sfp.serial_number.value.split("-", 1)[1]
1453+
template_obj = sfp_templates_by_interface_name[interface_name]
1454+
assert sfp.phys_type.value.value == "QSFP28 (100GE)"
1455+
assert sfp.phys_type.is_default is False
1456+
assert sfp.phys_type.source_id == template_obj.id
1457+
assert sfp.serial_number.value == f"QSFP-{interface_name}"
1458+
assert sfp.serial_number.is_default is False
1459+
assert sfp.serial_number.source_id == template_obj.id
1460+
assert sfp.part_number.value is None
1461+
assert sfp.part_number.is_default is True
1462+
assert sfp.part_number.source_id is None
14151463

14161464

14171465
async def test_create_without_object_template(

changelog/7259.fixed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix issue with template that would set the value/source of all attributes even for the attribute that are not defined in the template.

0 commit comments

Comments
 (0)