Skip to content

Commit e5be26a

Browse files
authored
Merge branch 'develop' into ple-global-activities
2 parents 106546d + 2ce6e23 commit e5be26a

File tree

150 files changed

+2920
-1069
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

150 files changed

+2920
-1069
lines changed

backend/infrahub/api/schema.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
SchemaDiff,
2626
SchemaUpdateValidationResult,
2727
)
28-
from infrahub.core.schema import GenericSchema, MainSchemaTypes, NodeSchema, ProfileSchema, SchemaRoot
28+
from infrahub.core.schema import GenericSchema, MainSchemaTypes, NodeSchema, ProfileSchema, SchemaRoot, TemplateSchema
2929
from infrahub.core.schema.constants import SchemaNamespace # noqa: TC001
3030
from infrahub.core.validators.models.validate_migration import (
3131
SchemaValidateMigrationData,
@@ -87,11 +87,17 @@ class APIProfileSchema(ProfileSchema, APISchemaMixin):
8787
hash: str
8888

8989

90+
class APITemplateSchema(TemplateSchema, APISchemaMixin):
91+
api_kind: str | None = Field(default=None, alias="kind", validate_default=True)
92+
hash: str
93+
94+
9095
class SchemaReadAPI(BaseModel):
9196
main: str = Field(description="Main hash for the entire schema")
9297
nodes: list[APINodeSchema] = Field(default_factory=list)
9398
generics: list[APIGenericSchema] = Field(default_factory=list)
9499
profiles: list[APIProfileSchema] = Field(default_factory=list)
100+
templates: list[APITemplateSchema] = Field(default_factory=list)
95101
namespaces: list[SchemaNamespace] = Field(default_factory=list)
96102

97103

@@ -191,6 +197,11 @@ async def get_schema(
191197
for value in all_schemas
192198
if isinstance(value, ProfileSchema) and value.namespace != "Internal"
193199
],
200+
templates=[
201+
APITemplateSchema.from_schema(value)
202+
for value in all_schemas
203+
if isinstance(value, TemplateSchema) and value.namespace != "Internal"
204+
],
194205
namespaces=schema_branch.get_namespaces(),
195206
)
196207

@@ -207,15 +218,16 @@ async def get_schema_summary(
207218
@router.get("/{schema_kind}")
208219
async def get_schema_by_kind(
209220
schema_kind: str, branch: Branch = Depends(get_branch_dep), _: AccountSession = Depends(get_current_user)
210-
) -> APIProfileSchema | APINodeSchema | APIGenericSchema:
221+
) -> APIProfileSchema | APINodeSchema | APIGenericSchema | APITemplateSchema:
211222
log.debug("schema_kind_request", branch=branch.name)
212223

213224
schema = registry.schema.get(name=schema_kind, branch=branch, duplicate=False)
214225

215-
api_schema: dict[str, type[APIProfileSchema | APINodeSchema | APIGenericSchema]] = {
226+
api_schema: dict[str, type[APIProfileSchema | APINodeSchema | APIGenericSchema | APITemplateSchema]] = {
216227
"profile": APIProfileSchema,
217228
"node": APINodeSchema,
218229
"generic": APIGenericSchema,
230+
"template": APITemplateSchema,
219231
}
220232
key = ""
221233

@@ -225,6 +237,8 @@ async def get_schema_by_kind(
225237
key = "node"
226238
if isinstance(schema, GenericSchema):
227239
key = "generic"
240+
if isinstance(schema, TemplateSchema):
241+
key = "template"
228242

229243
return api_schema[key].from_schema(schema=schema)
230244

backend/infrahub/artifacts/__init__.py

Whitespace-only changes.
Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
from typing import Optional
22

3-
from pydantic import Field
3+
from pydantic import BaseModel, Field
44

5-
from infrahub.message_bus import InfrahubMessage
65

7-
8-
class CheckArtifactCreate(InfrahubMessage):
6+
class CheckArtifactCreate(BaseModel):
97
"""Runs a check to verify the creation of an artifact."""
108

119
artifact_name: str = Field(..., description="Name of the artifact")

backend/infrahub/message_bus/operations/check/artifact.py renamed to backend/infrahub/artifacts/tasks.py

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,44 +2,38 @@
22

33
from prefect import flow
44

5+
from infrahub.artifacts.models import CheckArtifactCreate
56
from infrahub.core.constants import InfrahubKind, ValidatorConclusion
67
from infrahub.core.timestamp import Timestamp
7-
from infrahub.git.repository import InfrahubReadOnlyRepository, InfrahubRepository
8-
from infrahub.log import get_logger
9-
from infrahub.message_bus import messages
8+
from infrahub.git import InfrahubReadOnlyRepository, InfrahubRepository
109
from infrahub.services import InfrahubServices
1110
from infrahub.tasks.artifact import define_artifact
12-
from infrahub.tasks.check import set_check_status
1311
from infrahub.workflows.utils import add_tags
1412

15-
log = get_logger()
16-
1713

1814
@flow(name="git-repository-check-artifact-create", flow_run_name="Check artifact creation")
19-
async def create(message: messages.CheckArtifactCreate, service: InfrahubServices) -> None:
20-
await add_tags(branches=[message.branch_name], nodes=[message.target_id])
21-
validator = await service.client.get(
22-
kind=InfrahubKind.ARTIFACTVALIDATOR, id=message.validator_id, include=["checks"]
23-
)
15+
async def create(model: CheckArtifactCreate, service: InfrahubServices) -> ValidatorConclusion:
16+
await add_tags(branches=[model.branch_name], nodes=[model.target_id])
17+
validator = await service.client.get(kind=InfrahubKind.ARTIFACTVALIDATOR, id=model.validator_id, include=["checks"])
2418

19+
repo: InfrahubReadOnlyRepository | InfrahubRepository
2520
if InfrahubKind.READONLYREPOSITORY:
2621
repo = await InfrahubReadOnlyRepository.init(
27-
id=message.repository_id,
28-
name=message.repository_name,
22+
id=model.repository_id,
23+
name=model.repository_name,
2924
client=service.client,
3025
service=service,
3126
)
3227
else:
3328
repo = await InfrahubRepository.init(
34-
id=message.repository_id,
35-
name=message.repository_name,
29+
id=model.repository_id,
30+
name=model.repository_name,
3631
client=service.client,
3732
service=service,
3833
)
3934

40-
artifact = await define_artifact(message=message, service=service)
35+
artifact = await define_artifact(model=model, service=service)
4136

42-
conclusion = ValidatorConclusion.SUCCESS.value
4337
severity = "info"
4438
artifact_result: dict[str, Union[str, bool, None]] = {
4539
"changed": None,
@@ -50,22 +44,23 @@ async def create(message: messages.CheckArtifactCreate, service: InfrahubService
5044
check_message = "Failed to render artifact"
5145

5246
try:
53-
result = await repo.render_artifact(artifact=artifact, message=message)
47+
result = await repo.render_artifact(artifact=artifact, message=model)
5448
artifact_result["changed"] = result.changed
5549
artifact_result["checksum"] = result.checksum
5650
artifact_result["artifact_id"] = result.artifact_id
5751
artifact_result["storage_id"] = result.storage_id
5852
check_message = "Artifact rendered successfully"
53+
conclusion = ValidatorConclusion.SUCCESS
5954

6055
except Exception as exc:
61-
conclusion = ValidatorConclusion.FAILURE.value
6256
artifact.status.value = "Error"
57+
await artifact.save()
6358
severity = "critical"
59+
conclusion = ValidatorConclusion.FAILURE
6460
check_message += f": {str(exc)}"
65-
await artifact.save()
6661

6762
check = None
68-
check_name = f"{message.artifact_name}: {message.target_name}"
63+
check_name = f"{model.artifact_name}: {model.target_name}"
6964
existing_check = await service.client.filters(
7065
kind=InfrahubKind.ARTIFACTCHECK, validator__ids=validator.id, name__value=check_name
7166
)
@@ -74,7 +69,7 @@ async def create(message: messages.CheckArtifactCreate, service: InfrahubService
7469

7570
if check:
7671
check.created_at.value = Timestamp().to_string()
77-
check.conclusion.value = conclusion
72+
check.conclusion.value = conclusion.value
7873
check.severity.value = severity
7974
check.changed.value = artifact_result["changed"]
8075
check.checksum.value = artifact_result["checksum"]
@@ -86,12 +81,12 @@ async def create(message: messages.CheckArtifactCreate, service: InfrahubService
8681
kind=InfrahubKind.ARTIFACTCHECK,
8782
data={
8883
"name": check_name,
89-
"origin": message.repository_id,
84+
"origin": model.repository_id,
9085
"kind": "ArtifactDefinition",
91-
"validator": message.validator_id,
86+
"validator": model.validator_id,
9287
"created_at": Timestamp().to_string(),
9388
"message": check_message,
94-
"conclusion": conclusion,
89+
"conclusion": conclusion.value,
9590
"severity": severity,
9691
"changed": artifact_result["changed"],
9792
"checksum": artifact_result["checksum"],
@@ -101,4 +96,4 @@ async def create(message: messages.CheckArtifactCreate, service: InfrahubService
10196
)
10297
await check.save()
10398

104-
await set_check_status(message=message, conclusion=conclusion, service=service)
99+
return conclusion

backend/infrahub/core/changelog/models.py

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,10 @@ class RelationshipCardinalityOneChangelog(BaseModel):
102102
properties: dict[str, PropertyChangelog] = Field(
103103
default_factory=dict, description="Changes to properties of this relationship if any were made"
104104
)
105-
_parent: ChangelogNodeParent | None = PrivateAttr(default=None)
105+
_parent: ChangelogRelatedNode | None = PrivateAttr(default=None)
106106

107107
@property
108-
def parent(self) -> ChangelogNodeParent | None:
108+
def parent(self) -> ChangelogRelatedNode | None:
109109
return self._parent
110110

111111
@computed_field
@@ -127,7 +127,7 @@ def add_property(self, name: str, value_current: bool | str | None, value_previo
127127
self.properties[name] = PropertyChangelog(name=name, value=value_current, value_previous=value_previous)
128128

129129
def set_parent(self, parent_id: str, parent_kind: str) -> None:
130-
self._parent = ChangelogNodeParent(node_id=parent_id, node_kind=parent_kind)
130+
self._parent = ChangelogRelatedNode(node_id=parent_id, node_kind=parent_kind)
131131

132132
def set_parent_from_relationship(self, relationship: Relationship) -> None:
133133
if relationship.schema.kind == RelationshipKind.PARENT:
@@ -136,9 +136,9 @@ def set_parent_from_relationship(self, relationship: Relationship) -> None:
136136
and self.peer_id
137137
and self.peer_kind
138138
):
139-
self._parent = ChangelogNodeParent(node_id=self.peer_id, node_kind=self.peer_kind)
139+
self._parent = ChangelogRelatedNode(node_id=self.peer_id, node_kind=self.peer_kind)
140140
elif self.peer_id_previous and self.peer_kind_previous:
141-
self._parent = ChangelogNodeParent(node_id=self.peer_id_previous, node_kind=self.peer_kind_previous)
141+
self._parent = ChangelogRelatedNode(node_id=self.peer_id_previous, node_kind=self.peer_kind_previous)
142142

143143
@property
144144
def is_empty(self) -> bool:
@@ -200,7 +200,7 @@ def is_empty(self) -> bool:
200200
return not self.peers
201201

202202

203-
class ChangelogNodeParent(BaseModel):
203+
class ChangelogRelatedNode(BaseModel):
204204
node_id: str
205205
node_kind: str
206206

@@ -217,36 +217,40 @@ class NodeChangelog(BaseModel):
217217
default_factory=dict
218218
)
219219

220-
_parent: ChangelogNodeParent | None = PrivateAttr(default=None)
220+
_parent: ChangelogRelatedNode | None = PrivateAttr(default=None)
221221

222222
@property
223-
def parent(self) -> ChangelogNodeParent | None:
223+
def parent(self) -> ChangelogRelatedNode | None:
224224
return self._parent
225225

226226
@property
227227
def updated_fields(self) -> list[str]:
228228
"""Return a list of update fields i.e. attributes and relationships"""
229229
return list(self.relationships.keys()) + list(self.attributes.keys())
230230

231+
@property
232+
def has_changes(self) -> bool:
233+
return len(self.updated_fields) > 0
234+
231235
@property
232236
def root_node_id(self) -> str:
233237
"""Return the top level node_id"""
234238
if self.parent:
235239
return self.parent.node_id
236240
return self.node_id
237241

238-
def add_parent(self, parent: ChangelogNodeParent) -> None:
242+
def add_parent(self, parent: ChangelogRelatedNode) -> None:
239243
self._parent = parent
240244

241245
def add_parent_from_relationship(self, parent: Relationship) -> None:
242-
self._parent = ChangelogNodeParent(node_id=parent.get_peer_id(), node_kind=parent.get_peer_kind())
246+
self._parent = ChangelogRelatedNode(node_id=parent.get_peer_id(), node_kind=parent.get_peer_kind())
243247

244248
def create_relationship(self, relationship: Relationship) -> None:
245249
if relationship.schema.cardinality == RelationshipCardinality.ONE:
246250
peer_id = relationship.get_peer_id()
247251
peer_kind = relationship.get_peer_kind()
248252
if relationship.schema.kind == RelationshipKind.PARENT:
249-
self._parent = ChangelogNodeParent(node_id=peer_id, node_kind=peer_kind)
253+
self._parent = ChangelogRelatedNode(node_id=peer_id, node_kind=peer_kind)
250254
changelog_relationship = RelationshipCardinalityOneChangelog(
251255
name=relationship.schema.name,
252256
peer_id=peer_id,
@@ -321,6 +325,27 @@ def create_attribute(self, attribute: BaseAttribute) -> None:
321325
changelog_attribute.add_property(name="is_visible", value_current=attribute.is_visible, value_previous=None)
322326
self.attributes[changelog_attribute.name] = changelog_attribute
323327

328+
def get_related_nodes(self) -> list[ChangelogRelatedNode]:
329+
related_nodes: dict[str, ChangelogRelatedNode] = {}
330+
for relationship in self.relationships.values():
331+
if isinstance(relationship, RelationshipCardinalityOneChangelog):
332+
if relationship.peer_id and relationship.peer_kind:
333+
related_nodes[relationship.peer_id] = ChangelogRelatedNode(
334+
node_id=relationship.peer_id, node_kind=relationship.peer_kind
335+
)
336+
if relationship.peer_id_previous and relationship.peer_kind_previous:
337+
related_nodes[relationship.peer_id_previous] = ChangelogRelatedNode(
338+
node_id=relationship.peer_id_previous, node_kind=relationship.peer_kind_previous
339+
)
340+
elif isinstance(relationship, RelationshipCardinalityManyChangelog):
341+
for peer in relationship.peers:
342+
related_nodes[peer.peer_id] = ChangelogRelatedNode(node_id=peer.peer_id, node_kind=peer.peer_kind)
343+
344+
if self.parent:
345+
related_nodes[self.parent.node_id] = self.parent
346+
347+
return list(related_nodes.values())
348+
324349

325350
class ChangelogRelationshipMapper:
326351
def __init__(self, schema: RelationshipSchema) -> None:

backend/infrahub/core/constants/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ class RelationshipKind(InfrahubStringEnum):
225225
GROUP = "Group"
226226
HIERARCHY = "Hierarchy"
227227
PROFILE = "Profile"
228+
TEMPLATE = "Template"
228229

229230

230231
class RelationshipStatus(InfrahubStringEnum):
@@ -301,6 +302,7 @@ class AttributeDBNodeType(InfrahubStringEnum):
301302
"Lineage",
302303
"Schema",
303304
"Profile",
305+
"Template",
304306
]
305307

306308
NODE_NAME_REGEX = r"^[A-Z][a-zA-Z0-9]+$"
@@ -315,3 +317,6 @@ class AttributeDBNodeType(InfrahubStringEnum):
315317
NAMESPACE_REGEX = r"^[A-Z][a-z0-9]+$"
316318
NODE_KIND_REGEX = r"^[A-Z][a-zA-Z0-9]+$"
317319
DEFAULT_REL_IDENTIFIER_LENGTH = 128
320+
321+
OBJECT_TEMPLATE_RELATIONSHIP_NAME = "object_template"
322+
OBJECT_TEMPLATE_NAME_ATTR = "template_name"

backend/infrahub/core/constants/infrahubkind.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
LINEAGEOWNER = "LineageOwner"
4343
LINEAGESOURCE = "LineageSource"
4444
OBJECTPERMISSION = "CoreObjectPermission"
45+
OBJECTTEMPLATE = "CoreObjectTemplate"
4546
OBJECTTHREAD = "CoreObjectThread"
4647
PASSWORDCREDENTIAL = "CorePasswordCredential"
4748
PROFILE = "CoreProfile"

backend/infrahub/core/diff/combiner.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from copy import deepcopy
22
from dataclasses import dataclass, field, replace
33
from typing import Iterable
4-
from uuid import uuid4
54

65
from infrahub.core.constants import NULL_VALUE, DiffAction, RelationshipCardinality
76
from infrahub.core.constants.database import DatabaseEdgeType
@@ -418,10 +417,16 @@ async def combine(self, earlier_diffs: EnrichedDiffs, later_diffs: EnrichedDiffs
418417
filtered_node_pairs = self._filter_nodes_to_keep(earlier_diff=earlier, later_diff=later)
419418
combined_nodes = self._combine_nodes(node_pairs=filtered_node_pairs)
420419
self._link_child_nodes(nodes=combined_nodes)
420+
if earlier.exists_on_database:
421+
diff_uuid = earlier.uuid
422+
partner_uuid = earlier.partner_uuid
423+
else:
424+
diff_uuid = later.uuid
425+
partner_uuid = later.partner_uuid
421426
combined_diffs.append(
422427
EnrichedDiffRoot(
423-
uuid=str(uuid4()),
424-
partner_uuid=later.partner_uuid,
428+
uuid=diff_uuid,
429+
partner_uuid=partner_uuid,
425430
base_branch_name=later.base_branch_name,
426431
diff_branch_name=later.diff_branch_name,
427432
from_time=earlier.from_time,

0 commit comments

Comments
 (0)