Skip to content

Commit 1e69b45

Browse files
committed
Merge branch 'develop' of github.com:opsmill/infrahub into ple-global-activities
2 parents 106546d + c0acddc commit 1e69b45

File tree

184 files changed

+3610
-1178
lines changed

Some content is hidden

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

184 files changed

+3610
-1178
lines changed

CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,28 @@ This project uses [*towncrier*](https://towncrier.readthedocs.io/) and the chang
1111

1212
<!-- towncrier release notes start -->
1313

14+
## [Infrahub - v1.1.7](https://github.com/opsmill/infrahub/tree/infrahub-v1.1.7) - 2025-02-18
15+
16+
### Added
17+
18+
- Data diffs are loaded in sequential batches for faster performance with large changes.
19+
- The diff tree and diff list can now be scrolled independently.
20+
21+
### Changed
22+
23+
- Modified node mutation events to not send metadata properties as part of the mutation payload. The reason is that the property lookup was time consuming. This information will return again in Infrahub 1.2 with a completely updated format. ([#5664](https://github.com/opsmill/infrahub/issues/5664))
24+
25+
### Fixed
26+
27+
- Fix nodes remaining in the database after a create mutation fails when using pools. ([#4303](https://github.com/opsmill/infrahub/issues/4303))
28+
- Modify the query for the current tasks, ensuring the correct determination of the merge button state. ([#5565](https://github.com/opsmill/infrahub/issues/5565))
29+
- Fix Docker `task-manager-db` PostgreSQL health check test by adding database and user parameters. ([#5739](https://github.com/opsmill/infrahub/issues/5739))
30+
- Fixed issue causing a gap in menu sidebar when text is too long.
31+
- Prevent avatar from being cut off in menu sidebar.
32+
- Enforce permission checks when using relationship add or delete mutation.
33+
- Enhance the data integrity checks UI to enable navigation from the check to the diff view.
34+
- Improved performance when updating an existing diff.
35+
1436
## [Infrahub - v1.1.6](https://github.com/opsmill/infrahub/tree/infrahub-v1.1.6) - 2025-01-30
1537

1638
### Artifact improvements

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)