Skip to content

Commit afc93fc

Browse files
authored
Merge pull request #5852 from opsmill/dga-20250225-stable-to-release-1.2
Merge stable into release-1.2 w/ conflicts resolution
2 parents 593c320 + 8e87aa9 commit afc93fc

File tree

15 files changed

+260
-56
lines changed

15 files changed

+260
-56
lines changed

.github/workflows/update-compose-file-and-chart.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
# yamllint disable rule:truthy
2+
# yamllint disable rule:truthy rule:line-length
33
name: Update Docker Compose & helm chart on Pyproject update in Stable
44

55
# This will bump the infrahub docker image in the docker-compose.yml
@@ -43,11 +43,20 @@ jobs:
4343
- name: "Install Package"
4444
run: "poetry install --all-extras"
4545

46+
- name: "Check prerelease type"
47+
id: release
48+
run: |
49+
echo is_prerelease=$(poetry run python -c "from packaging.version import Version; print(int(Version('$(poetry version -s)').is_prerelease))") >> "$GITHUB_OUTPUT"
50+
echo is_devrelease=$(poetry run python -c "from packaging.version import Version; print(int(Version('$(poetry version -s)').is_devrelease))") >> "$GITHUB_OUTPUT"
51+
4652
- name: "Update Docker Env variable in docker-compose.yml file"
53+
if: steps.release.outputs.is_prerelease == 0 && steps.release.outputs.is_devrelease == 0
4754
run: "poetry run invoke release.gen-config-env -u"
4855
- name: "Update Infrahub Image Version in docker-compose.yml file"
56+
if: steps.release.outputs.is_prerelease == 0 && steps.release.outputs.is_devrelease == 0
4957
run: "poetry run invoke release.update-docker-compose"
5058
- name: "Update AppVersion in helm/chart.yaml file"
59+
if: steps.release.outputs.is_prerelease == 0 && steps.release.outputs.is_devrelease == 0
5160
run: "poetry run invoke release.update-helm-chart"
5261
- name: "Update Versions in python_testcontainers/pyproject.toml"
5362
run: "poetry run invoke release.update-test-containers"

backend/infrahub/core/diff/model/path.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,8 +409,8 @@ class EnrichedDiffRootMetadata(BaseSummary):
409409
from_time: Timestamp
410410
to_time: Timestamp
411411
uuid: str
412-
partner_uuid: str
413412
tracking_id: TrackingId
413+
partner_uuid: str | None = field(default=None)
414414
exists_on_database: bool = field(default=False)
415415

416416
def __hash__(self) -> int:

backend/infrahub/core/diff/query/save.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa
4242
}
4343
WITH DISTINCT diff_root AS diff_root
4444
WITH collect(diff_root) AS diff_roots
45+
WHERE SIZE(diff_roots) = 2
4546
CALL {
4647
WITH diff_roots
4748
WITH diff_roots[0] AS base_diff_node, diff_roots[1] AS branch_diff_node

backend/infrahub/core/diff/repository/deserializer.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ def _deserialize_diff_root(self, root_node: Neo4jNode) -> EnrichedDiffRoot:
164164
def build_diff_root_metadata(cls, root_node: Neo4jNode) -> EnrichedDiffRootMetadata:
165165
from_time = Timestamp(str(root_node.get("from_time")))
166166
to_time = Timestamp(str(root_node.get("to_time")))
167+
partner_uuid = cls._get_str_or_none_property_value(node=root_node, property_name="partner_uuid")
167168
tracking_id_str = str(root_node.get("tracking_id"))
168169
tracking_id = deserialize_tracking_id(tracking_id_str=tracking_id_str)
169170
return EnrichedDiffRootMetadata(
@@ -172,7 +173,7 @@ def build_diff_root_metadata(cls, root_node: Neo4jNode) -> EnrichedDiffRootMetad
172173
from_time=from_time,
173174
to_time=to_time,
174175
uuid=str(root_node.get("uuid")),
175-
partner_uuid=str(root_node.get("partner_uuid")),
176+
partner_uuid=partner_uuid,
176177
tracking_id=tracking_id,
177178
num_added=int(root_node.get("num_added", 0)),
178179
num_updated=int(root_node.get("num_updated", 0)),

backend/infrahub/core/diff/repository/repository.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -154,18 +154,23 @@ async def get_pairs(
154154
diff_branch_names=[base_branch_name],
155155
max_depth=max_depth,
156156
batch_size_limit=batch_size_limit,
157-
diff_ids=[d.partner_uuid for d in diffs_by_uuid.values()],
157+
diff_ids=[d.partner_uuid for d in diffs_by_uuid.values() if d.partner_uuid],
158158
)
159159
diffs_by_uuid.update({bbr.uuid: bbr for bbr in base_branch_roots})
160-
return [
161-
EnrichedDiffs(
162-
base_branch_name=base_branch_name,
163-
diff_branch_name=diff_branch_name,
164-
base_branch_diff=diffs_by_uuid[dbr.partner_uuid],
165-
diff_branch_diff=dbr,
160+
diff_pairs = []
161+
for dbr in diff_branch_roots:
162+
if dbr.partner_uuid is None:
163+
continue
164+
base_branch_diff = diffs_by_uuid[dbr.partner_uuid]
165+
diff_pairs.append(
166+
EnrichedDiffs(
167+
base_branch_name=base_branch_name,
168+
diff_branch_name=diff_branch_name,
169+
base_branch_diff=base_branch_diff,
170+
diff_branch_diff=dbr,
171+
)
166172
)
167-
for dbr in diff_branch_roots
168-
]
173+
return diff_pairs
169174

170175
async def hydrate_diff_pair(
171176
self,
@@ -325,7 +330,7 @@ async def get_diff_pairs_metadata(
325330
roots_by_id = {root.uuid: root for root in empty_roots}
326331
pairs: list[EnrichedDiffsMetadata] = []
327332
for branch_root in empty_roots:
328-
if branch_root.base_branch_name == branch_root.diff_branch_name:
333+
if branch_root.base_branch_name == branch_root.diff_branch_name or branch_root.partner_uuid is None:
329334
continue
330335
base_root = roots_by_id[branch_root.partner_uuid]
331336
pairs.append(

backend/infrahub/core/diff/tasks.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@
1111
from infrahub.log import get_logger
1212
from infrahub.services import InfrahubServices # noqa: TC001 needed for prefect flow
1313
from infrahub.workflows.catalogue import DIFF_REFRESH
14-
from infrahub.workflows.utils import add_branch_tag
14+
from infrahub.workflows.utils import add_tags
1515

1616
log = get_logger()
1717

1818

1919
@flow(name="diff-update", flow_run_name="Update diff for branch {model.branch_name}")
2020
async def update_diff(model: RequestDiffUpdate, service: InfrahubServices) -> None:
21-
await add_branch_tag(branch_name=model.branch_name)
21+
await add_tags(branches=[model.branch_name])
2222

2323
async with service.database.start_session() as db:
2424
component_registry = get_component_registry()
@@ -38,7 +38,7 @@ async def update_diff(model: RequestDiffUpdate, service: InfrahubServices) -> No
3838

3939
@flow(name="diff-refresh", flow_run_name="Recreate diff for branch {branch_name}")
4040
async def refresh_diff(branch_name: str, diff_id: str, service: InfrahubServices) -> None:
41-
await add_branch_tag(branch_name=branch_name)
41+
await add_tags(branches=[branch_name])
4242

4343
async with service.database.start_session() as db:
4444
component_registry = get_component_registry()
@@ -51,7 +51,7 @@ async def refresh_diff(branch_name: str, diff_id: str, service: InfrahubServices
5151

5252
@flow(name="diff-refresh-all", flow_run_name="Recreate all diffs for branch {branch_name}")
5353
async def refresh_diff_all(branch_name: str, context: InfrahubContext, service: InfrahubServices) -> None:
54-
await add_branch_tag(branch_name=branch_name)
54+
await add_tags(branches=[branch_name])
5555

5656
async with service.database.start_session() as db:
5757
component_registry = get_component_registry()

backend/infrahub/core/query/relationship.py

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,7 @@ async def query_init(self, db: InfrahubDatabase, **kwargs) -> None: # noqa: ARG
581581
query = """
582582
MATCH (source_node:Node)%(arrow_left_start)s[:IS_RELATED]%(arrow_left_end)s(rl:Relationship { name: $rel_identifier })
583583
WHERE source_node.uuid IN $source_ids
584+
WITH DISTINCT source_node, rl
584585
CALL {
585586
WITH rl, source_node
586587
MATCH path = (source_node)%(path)s(peer:Node)
@@ -657,10 +658,23 @@ async def query_init(self, db: InfrahubDatabase, **kwargs) -> None: # noqa: ARG
657658
# QUERY Properties
658659
# ----------------------------------------------------------------------------
659660
query = """
660-
MATCH (rl)-[rel_is_visible:IS_VISIBLE]-(is_visible)
661-
MATCH (rl)-[rel_is_protected:IS_PROTECTED]-(is_protected)
662-
WHERE all(r IN [ rel_is_visible, rel_is_protected] WHERE (%s))
663-
""" % (branch_filter,)
661+
CALL {
662+
WITH rl
663+
MATCH (rl)-[r:IS_VISIBLE]-(is_visible)
664+
WHERE %(branch_filter)s
665+
RETURN r AS rel_is_visible, is_visible
666+
ORDER BY r.branch_level DESC, r.from DESC, r.status ASC
667+
LIMIT 1
668+
}
669+
CALL {
670+
WITH rl
671+
MATCH (rl)-[r:IS_PROTECTED]-(is_protected)
672+
WHERE %(branch_filter)s
673+
RETURN r AS rel_is_protected, is_protected
674+
ORDER BY r.branch_level DESC, r.from DESC, r.status ASC
675+
LIMIT 1
676+
}
677+
""" % {"branch_filter": branch_filter}
664678

665679
self.add_to_query(query)
666680

@@ -670,20 +684,24 @@ async def query_init(self, db: InfrahubDatabase, **kwargs) -> None: # noqa: ARG
670684
# We must query them one by one otherwise the second one won't return
671685
for node_prop in ["source", "owner"]:
672686
query = """
673-
WITH %s
674-
OPTIONAL MATCH (rl)-[rel_%s:HAS_%s]-(%s)
675-
WHERE all(r IN [ rel_%s ] WHERE (%s))
676-
""" % (
677-
",".join(self.return_labels),
678-
node_prop,
679-
node_prop.upper(),
680-
node_prop,
681-
node_prop,
682-
branch_filter,
683-
)
687+
CALL {
688+
WITH rl
689+
OPTIONAL MATCH (rl)-[r:HAS_%(node_prop_type)s]-(%(node_prop)s)
690+
WHERE %(branch_filter)s
691+
RETURN r AS rel_%(node_prop)s, %(node_prop)s
692+
ORDER BY r.branch_level DESC, r.from DESC, r.status ASC
693+
LIMIT 1
694+
}
695+
""" % {
696+
"node_prop": node_prop,
697+
"node_prop_type": node_prop.upper(),
698+
"branch_filter": branch_filter,
699+
}
684700
self.add_to_query(query)
685701
self.update_return_labels([f"rel_{node_prop}", node_prop])
686702

703+
self.add_to_query("WITH " + ",".join(self.return_labels))
704+
687705
# ----------------------------------------------------------------------------
688706
# ORDER Results
689707
# ----------------------------------------------------------------------------

backend/infrahub/graphql/mutations/diff.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import TYPE_CHECKING
22

3-
from graphene import Boolean, DateTime, InputObjectType, Mutation, String
3+
from graphene import Boolean, DateTime, Field, InputObjectType, Mutation, String
44
from graphql import GraphQLResolveInfo
55

66
from infrahub.core import registry
@@ -14,6 +14,8 @@
1414
from infrahub.exceptions import ValidationError
1515
from infrahub.workflows.catalogue import DIFF_UPDATE
1616

17+
from ..types.task import TaskInfo
18+
1719
if TYPE_CHECKING:
1820
from ..initialization import GraphqlContext
1921

@@ -23,14 +25,16 @@ class DiffUpdateInput(InputObjectType):
2325
name = String(required=False)
2426
from_time = DateTime(required=False)
2527
to_time = DateTime(required=False)
26-
wait_for_completion = Boolean(required=False)
28+
wait_for_completion = Boolean(required=False, deprecation_reason="Please use `wait_until_completion` instead")
2729

2830

2931
class DiffUpdateMutation(Mutation):
3032
class Arguments:
3133
data = DiffUpdateInput(required=True)
34+
wait_until_completion = Boolean(required=False)
3235

3336
ok = Boolean()
37+
task = Field(TaskInfo, required=False)
3438

3539
@classmethod
3640
@retry_db_transaction(name="diff_update")
@@ -39,9 +43,13 @@ async def mutate(
3943
root: dict, # noqa: ARG003
4044
info: GraphQLResolveInfo,
4145
data: DiffUpdateInput,
42-
) -> dict[str, bool]:
46+
wait_until_completion: bool = False,
47+
) -> dict[str, bool | dict[str, str]]:
4348
graphql_context: GraphqlContext = info.context
4449

50+
if data.wait_for_completion is True:
51+
wait_until_completion = True
52+
4553
from_timestamp_str = DateTime.serialize(data.from_time) if data.from_time else None
4654
to_timestamp_str = DateTime.serialize(data.to_time) if data.to_time else None
4755
if (data.from_time or data.to_time) and not data.name:
@@ -55,11 +63,11 @@ async def mutate(
5563
)
5664

5765
tracking_id = NameTrackingId(name=data.name)
58-
existing_diffs_metatdatas = await diff_repository.get_roots_metadata(
66+
existing_diffs_metadatas = await diff_repository.get_roots_metadata(
5967
diff_branch_names=[diff_branch.name], base_branch_names=[base_branch.name], tracking_id=tracking_id
6068
)
61-
if existing_diffs_metatdatas:
62-
metadata = existing_diffs_metatdatas[0]
69+
if existing_diffs_metadatas:
70+
metadata = existing_diffs_metadatas[0]
6371
from_time = Timestamp(from_timestamp_str) if from_timestamp_str else None
6472
to_time = Timestamp(to_timestamp_str) if to_timestamp_str else None
6573
branched_from_timestamp = Timestamp(diff_branch.get_branched_from())
@@ -70,7 +78,7 @@ async def mutate(
7078
if to_time and to_time < metadata.to_time:
7179
raise ValidationError(f"to_time must be null or greater than or equal to {metadata.to_time}")
7280

73-
if data.wait_for_completion is True:
81+
if wait_until_completion is True:
7482
diff_coordinator = await component_registry.get_component(
7583
DiffCoordinator, db=graphql_context.db, branch=diff_branch
7684
)
@@ -91,8 +99,9 @@ async def mutate(
9199
to_time=to_timestamp_str,
92100
)
93101
if graphql_context.service:
94-
await graphql_context.service.workflow.submit_workflow(
95-
workflow=DIFF_UPDATE, context=graphql_context.get_context(), parameters={"model": model}
102+
workflow = await graphql_context.service.workflow.submit_workflow(
103+
workflow=DIFF_UPDATE, parameters={"model": model}
96104
)
105+
return {"ok": True, "task": {"id": str(workflow.id)}}
97106

98107
return {"ok": True}

backend/tests/integration/diff/test_diff_update.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@
3232
BRANCH_NAME = "branch1"
3333
PROPOSED_CHANGE_NAME = "branch1-pc"
3434
DIFF_UPDATE_QUERY = """
35-
mutation DiffUpdate($branch_name: String!, $wait_for_completion: Boolean) {
36-
DiffUpdate(data: { branch: $branch_name, wait_for_completion: $wait_for_completion }) {
35+
mutation DiffUpdate($branch_name: String!, $wait_for_completion: Boolean = true) {
36+
DiffUpdate(data: { branch: $branch_name }, wait_until_completion: $wait_for_completion) {
3737
ok
3838
}
3939
}

backend/tests/unit/core/diff/test_coordinator.py

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from infrahub.core.diff.calculator import DiffCalculator
99
from infrahub.core.diff.combiner import DiffCombiner
1010
from infrahub.core.diff.coordinator import DiffCoordinator
11-
from infrahub.core.diff.model.path import BranchTrackingId, EnrichedDiffRootMetadata
11+
from infrahub.core.diff.model.path import BranchTrackingId, EnrichedDiffRootMetadata, NameTrackingId
1212
from infrahub.core.diff.repository.repository import DiffRepository
1313
from infrahub.core.initialization import create_branch
1414
from infrahub.core.manager import NodeManager
@@ -240,3 +240,64 @@ async def test_unrelated_changes_skip_some_expensive_operations(
240240
)
241241
assert no_changes_diff.from_time == no_changes_diff_metadata.from_time == diff_with_data.from_time
242242
assert no_changes_diff.to_time == no_changes_diff_metadata.to_time
243+
244+
async def test_diff_on_default_branch_only(
245+
self,
246+
db: InfrahubDatabase,
247+
default_branch: Branch,
248+
person_john_main: Node,
249+
person_jane_main: Node,
250+
person_alfred_main: Node,
251+
car_camry_main: Node,
252+
car_accord_main: Node,
253+
) -> None:
254+
branch = await create_branch(db=db, branch_name="branch1")
255+
256+
updated_person = await NodeManager.get_one(db=db, id=person_john_main.id)
257+
updated_person.height.value = 200
258+
await updated_person.save(db=db)
259+
260+
new_person = await Node.init(db=db, schema="TestPerson", branch=default_branch)
261+
await new_person.new(db=db, name="Jeff", height=170)
262+
await new_person.save(db=db)
263+
264+
deleted_person = await NodeManager.get_one(db=db, id=person_alfred_main.id)
265+
await deleted_person.delete(db=db)
266+
267+
updated_car = await NodeManager.get_one(db=db, id=car_accord_main.id)
268+
await updated_car.owner.update(db=db, data=new_person)
269+
await updated_car.save(db=db)
270+
271+
from_time = Timestamp(branch.get_branched_from())
272+
to_time = Timestamp()
273+
name = str(uuid4())
274+
diff_coordinator = await self.get_wrapped_diff_coordinator(db=db, branch=default_branch)
275+
component_registry = get_component_registry()
276+
diff_repository = await component_registry.get_component(DiffRepository, db=db, branch=default_branch)
277+
main_diff_metadata = await diff_coordinator.create_or_update_arbitrary_timeframe_diff(
278+
base_branch=default_branch, diff_branch=default_branch, from_time=from_time, to_time=to_time, name=name
279+
)
280+
main_diff = await diff_repository.get_one(diff_branch_name=default_branch.name, diff_id=main_diff_metadata.uuid)
281+
282+
assert main_diff.base_branch_name == default_branch.name
283+
assert main_diff.diff_branch_name == default_branch.name
284+
assert main_diff.from_time == from_time
285+
assert main_diff.to_time == to_time
286+
assert main_diff.tracking_id == NameTrackingId(name=name)
287+
assert len(main_diff.nodes) == 4
288+
nodes_by_id = {n.uuid: n for n in main_diff.nodes}
289+
assert set(nodes_by_id.keys()) == {updated_person.id, new_person.id, deleted_person.id, updated_car.id}
290+
new_person_diff = nodes_by_id[new_person.id]
291+
assert new_person_diff.action is DiffAction.ADDED
292+
deleted_person_diff = nodes_by_id[deleted_person.id]
293+
assert deleted_person_diff.action is DiffAction.REMOVED
294+
updated_car_diff = nodes_by_id[updated_car.id]
295+
assert updated_car_diff.action is DiffAction.UPDATED
296+
assert updated_car_diff.attributes == set()
297+
rel_diffs = {(r.name, r.action) for r in updated_car_diff.relationships}
298+
assert rel_diffs == {("owner", DiffAction.UPDATED)}
299+
updated_person_diff = nodes_by_id[updated_person.id]
300+
rel_diffs = {(r.name, r.action) for r in updated_person_diff.relationships}
301+
assert rel_diffs == {("cars", DiffAction.UPDATED)}
302+
attr_diffs = {(a.name, a.action) for a in updated_person_diff.attributes}
303+
assert attr_diffs == {("height", DiffAction.UPDATED)}

0 commit comments

Comments
 (0)