Skip to content

Commit e65a737

Browse files
authored
Merge pull request #5848 from opsmill/release-1.2
Merge release-1.2 into develop
2 parents 1a9a781 + ca7f952 commit e65a737

File tree

31 files changed

+614
-34
lines changed

31 files changed

+614
-34
lines changed

CHANGELOG.md

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

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

14+
## [Infrahub - v1.2.0rc0](https://github.com/opsmill/infrahub/tree/infrahub-v1.2.0rc0) - 2025-02-23
15+
16+
### Added
17+
18+
- Added Containerlab to the codespace base image. ([#458](https://github.com/opsmill/infrahub/issues/458))
19+
- - We added validation on UI for `min_count` and `max_count` in relationships fields. ([#5661](https://github.com/opsmill/infrahub/issues/5661))
20+
- - Improved Infrahub app layout for a cleaner look.
21+
- Made the top menu more compact.
22+
- Add activities logs in the nodes details view
23+
- Add new feature to create object templates when setting `generate_template: true` in a schema on a node
24+
25+
### Changed
26+
27+
- Replace PrefixPool with netaddr.IPSet ([#3547](https://github.com/opsmill/infrahub/issues/3547))
28+
- Modified query analyzer to not list all potential meta data models when only querying for "source" or "owner" ID. The full models will still show up if a fragment is used under the meta data properties. This change makes it easier to setup fine grained permissions and also speeds up the permission lookup as it doesn't require as many checks. ([#4644](https://github.com/opsmill/infrahub/issues/4644))
29+
- - We made object list retrieval faster with an optimized query.
30+
- Improve typing of GraphQL schema by defining list as non-nullable and ensure that top level item are mandatory.
31+
- Reorganized builtin/default menu to provide a better user experience.
32+
- Updated Infrahub account tokens view:
33+
34+
- Redesigned for a faster, cleaner experience
35+
- Improved clarity and formatting of expiration dates.
36+
- Resolved an issue where expiration data was not being sent to the API.
37+
38+
### Fixed
39+
40+
- Set correct state in events after merging a proposed change, they were incorrectly set as "merging" instead of "merged" ([#5600](https://github.com/opsmill/infrahub/issues/5600))
41+
42+
### Housekeeping
43+
44+
- Activate ruff B rules. ([#2193](https://github.com/opsmill/infrahub/issues/2193))
45+
- Activate ruff C4 rule. ([#2194](https://github.com/opsmill/infrahub/issues/2194))
46+
- Add basic integration test for the HTTP service adapter ([#5553](https://github.com/opsmill/infrahub/issues/5553))
47+
1448
## [Infrahub - v1.1.7](https://github.com/opsmill/infrahub/tree/infrahub-v1.1.7) - 2025-02-18
1549

1650
### Added

backend/infrahub/computed_attribute/tasks.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,7 @@ async def computed_attribute_setup_python(
692692
)
693693
async def computed_attribute_remove_python(
694694
branch_name: str,
695+
context: InfrahubContext, # noqa: ARG001
695696
) -> None:
696697
async with get_client(sync_client=False) as client:
697698
automations = await client.read_automations()

backend/infrahub/core/changelog/diff.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from infrahub.core.constants import DiffAction, RelationshipCardinality
99
from infrahub.core.constants.database import DatabaseEdgeType
10+
from infrahub.core.diff.model.path import ConflictSelection
1011

1112
from .models import (
1213
AttributeChangelog,
@@ -21,6 +22,7 @@
2122
from infrahub.core.diff.model.path import (
2223
EnrichedDiffAttribute,
2324
EnrichedDiffNode,
25+
EnrichedDiffProperty,
2426
EnrichedDiffRelationship,
2527
EnrichedDiffRoot,
2628
)
@@ -86,8 +88,9 @@ def _process_node_attribute(
8688
match attr_property.property_type:
8789
case DatabaseEdgeType.HAS_VALUE:
8890
# TODO deserialize correct value type from string
89-
changelog_attribute.value = attr_property.new_value
90-
changelog_attribute.value_previous = attr_property.previous_value
91+
if _keep_branch_update(diff_property=attr_property):
92+
changelog_attribute.value = attr_property.new_value
93+
changelog_attribute.value_previous = attr_property.previous_value
9194
case DatabaseEdgeType.IS_PROTECTED:
9295
changelog_attribute.add_property(
9396
name="is_protected",
@@ -113,7 +116,7 @@ def _process_node_attribute(
113116
value_previous=attr_property.previous_value,
114117
)
115118

116-
node.attributes[attribute.name] = changelog_attribute
119+
node.add_attribute(attribute=changelog_attribute)
117120

118121
def _process_node_relationship(self, node: NodeChangelog, relationship: EnrichedDiffRelationship) -> None:
119122
match relationship.cardinality:
@@ -229,4 +232,10 @@ def collect_changelogs(self) -> Sequence[tuple[DiffAction, NodeChangelog]]:
229232
for node in self._diff.nodes
230233
if node.action != DiffAction.UNCHANGED
231234
]
232-
return changelogs
235+
return [(action, node_changelog) for action, node_changelog in changelogs if node_changelog.has_changes]
236+
237+
238+
def _keep_branch_update(diff_property: EnrichedDiffProperty) -> bool:
239+
if diff_property.conflict and diff_property.conflict.selected_branch == ConflictSelection.BASE_BRANCH:
240+
return False
241+
return True

backend/infrahub/core/changelog/models.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,8 @@ def delete_relationship(self, relationship: Relationship) -> None:
304304
)
305305

306306
def add_attribute(self, attribute: AttributeChangelog) -> None:
307-
self.attributes[attribute.name] = attribute
307+
if attribute.has_updates:
308+
self.attributes[attribute.name] = attribute
308309

309310
def add_relationship(
310311
self, relationship: RelationshipCardinalityOneChangelog | RelationshipCardinalityManyChangelog

backend/infrahub/core/schema/schema_branch.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1899,7 +1899,7 @@ def generate_object_template_from_node(self, node: NodeSchema) -> TemplateSchema
18991899
return template
19001900

19011901
def identify_required_object_templates(
1902-
self, node_schema: NodeSchema, identified: set[NodeSchema]
1902+
self, node_schema: NodeSchema | GenericSchema, identified: set[NodeSchema | GenericSchema]
19031903
) -> set[NodeSchema]:
19041904
"""Identify all templates required to turn a given node into a template."""
19051905
if node_schema in identified:
@@ -1915,15 +1915,15 @@ def identify_required_object_templates(
19151915
continue
19161916

19171917
peer_schema = self.get(name=relationship.peer, duplicate=False)
1918-
if not isinstance(peer_schema, NodeSchema) or peer_schema in identified:
1918+
if not isinstance(peer_schema, NodeSchema | GenericSchema) or peer_schema in identified:
19191919
continue
19201920

19211921
identified |= self.identify_required_object_templates(node_schema=peer_schema, identified=identified)
19221922

19231923
return identified
19241924

19251925
def manage_object_template_schemas(self) -> None:
1926-
need_templates: set[NodeSchema] = set()
1926+
need_templates: set[NodeSchema | GenericSchema] = set()
19271927
template_schema_kinds: set[str] = set()
19281928

19291929
for node_name in self.node_names + self.generic_names:

backend/infrahub/events/node_action.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ def get_resource(self) -> dict[str, str]:
7777
"infrahub.node.id": self.node_id,
7878
"infrahub.node.action": self.action.value,
7979
"infrahub.node.root_id": self.data.root_node_id,
80+
"infrahub.branch.name": self.meta.context.branch.name,
8081
}
8182

8283
def get_payload(self) -> dict[str, Any]:

backend/infrahub/generators/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class RequestGeneratorDefinitionRun(BaseModel):
3030

3131
generator_definition: ProposedChangeGeneratorDefinition = Field(..., description="The Generator Definition")
3232
branch: str = Field(..., description="The branch to target")
33+
target_members: list[str] = Field(default_factory=list, description="List of targets to run the generator for")
3334

3435

3536
class ProposedChangeGeneratorDefinition(BaseModel):

backend/infrahub/generators/tasks.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
from __future__ import annotations
22

3+
import asyncio
4+
from typing import TYPE_CHECKING, Any
5+
36
from infrahub_sdk.exceptions import ModuleImportError
47
from infrahub_sdk.node import InfrahubNode
58
from infrahub_sdk.protocols import CoreGeneratorInstance
69
from infrahub_sdk.schema.repository import InfrahubGeneratorDefinitionConfig
7-
from prefect import flow, task
10+
from prefect import State, flow, task
811
from prefect.cache_policies import NONE
12+
from prefect.states import Completed, Failed
913

1014
from infrahub import lock
1115
from infrahub.context import InfrahubContext # noqa: TC001 needed for prefect flow
@@ -21,6 +25,9 @@
2125
from infrahub.workflows.catalogue import REQUEST_GENERATOR_DEFINITION_RUN, REQUEST_GENERATOR_RUN
2226
from infrahub.workflows.utils import add_tags
2327

28+
if TYPE_CHECKING:
29+
from collections.abc import Coroutine
30+
2431

2532
@flow(
2633
name="generator-run",
@@ -154,7 +161,7 @@ async def run_generator_definition(branch: str, context: InfrahubContext, servic
154161
)
155162
async def request_generator_definition_run(
156163
model: RequestGeneratorDefinitionRun, context: InfrahubContext, service: InfrahubServices
157-
) -> None:
164+
) -> State[Any]:
158165
await add_tags(branches=[model.branch], nodes=[model.generator_definition.definition_id])
159166

160167
group = await service.client.get(
@@ -190,8 +197,13 @@ async def request_generator_definition_run(
190197
raise_when_missing=True,
191198
)
192199

200+
tasks: list[Coroutine[Any, Any, Any]] = []
193201
for relationship in group.members.peers:
194202
member = relationship.peer
203+
204+
if model.target_members and member.id not in model.target_members:
205+
continue
206+
195207
generator_instance = instance_by_member.get(member.id)
196208
request_generator_run_model = RequestGeneratorRun(
197209
generator_definition=model.generator_definition,
@@ -206,6 +218,14 @@ async def request_generator_definition_run(
206218
target_id=member.id,
207219
target_name=member.display_label,
208220
)
209-
await service.workflow.submit_workflow(
210-
workflow=REQUEST_GENERATOR_RUN, context=context, parameters={"model": request_generator_run_model}
221+
tasks.append(
222+
service.workflow.execute_workflow(
223+
workflow=REQUEST_GENERATOR_RUN, context=context, parameters={"model": request_generator_run_model}
224+
)
211225
)
226+
227+
try:
228+
await asyncio.gather(*tasks)
229+
return Completed(message=f"Successfully run {len(tasks)} generators")
230+
except Exception as exc:
231+
return Failed(message="One or more generators failed", error=exc)
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING
4+
5+
from graphene import Boolean, Field, InputField, InputObjectType, List, Mutation, NonNull, String
6+
7+
from infrahub.core.manager import NodeManager
8+
from infrahub.generators.models import ProposedChangeGeneratorDefinition, RequestGeneratorDefinitionRun
9+
from infrahub.graphql.types.task import TaskInfo
10+
from infrahub.workflows.catalogue import REQUEST_GENERATOR_DEFINITION_RUN
11+
12+
if TYPE_CHECKING:
13+
from graphql import GraphQLResolveInfo
14+
15+
from ..initialization import GraphqlContext
16+
17+
18+
class GeneratorDefinitionRequestRunInput(InputObjectType):
19+
id = InputField(String(required=True), description="ID of the generator definition to run")
20+
nodes = InputField(List(of_type=NonNull(String)), description="ID list of targets to run the generator for")
21+
22+
23+
class GeneratorDefinitionRequestRun(Mutation):
24+
class Arguments:
25+
data = GeneratorDefinitionRequestRunInput(required=True)
26+
wait_until_completion = Boolean(required=False)
27+
28+
ok = Boolean()
29+
task = Field(TaskInfo, required=False)
30+
31+
@classmethod
32+
async def mutate(
33+
cls,
34+
root: dict, # noqa: ARG003
35+
info: GraphQLResolveInfo,
36+
data: GeneratorDefinitionRequestRunInput,
37+
wait_until_completion: bool = True,
38+
) -> GeneratorDefinitionRequestRun:
39+
graphql_context: GraphqlContext = info.context
40+
db = graphql_context.db
41+
42+
generator_definition = await NodeManager.get_one(
43+
id=str(data.id), db=db, branch=graphql_context.branch, prefetch_relationships=True, raise_on_error=True
44+
)
45+
query = await generator_definition.query.get_peer(db=db)
46+
repository = await generator_definition.repository.get_peer(db=db)
47+
group = await generator_definition.targets.get_peer(db=db)
48+
49+
request_model = RequestGeneratorDefinitionRun(
50+
generator_definition=ProposedChangeGeneratorDefinition(
51+
definition_id=generator_definition.id,
52+
definition_name=generator_definition.name.value,
53+
class_name=generator_definition.class_name.value,
54+
file_path=generator_definition.file_path.value,
55+
query_name=query.name.value,
56+
query_models=query.models.value,
57+
repository_id=repository.id,
58+
parameters=generator_definition.parameters.value,
59+
group_id=group.id,
60+
convert_query_response=generator_definition.convert_query_response.value or False,
61+
),
62+
branch=graphql_context.branch.name,
63+
target_members=data.get("nodes", []),
64+
)
65+
66+
if not wait_until_completion:
67+
workflow = await graphql_context.active_service.workflow.submit_workflow(
68+
workflow=REQUEST_GENERATOR_DEFINITION_RUN,
69+
context=graphql_context.get_context(),
70+
parameters={"model": request_model},
71+
)
72+
return cls(ok=True, task={"id": workflow.id})
73+
74+
await graphql_context.active_service.workflow.execute_workflow(
75+
workflow=REQUEST_GENERATOR_DEFINITION_RUN,
76+
context=graphql_context.get_context(),
77+
parameters={"model": request_model},
78+
)
79+
return cls(ok=True)

backend/infrahub/graphql/schema.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from .mutations.computed_attribute import UpdateComputedAttribute
1919
from .mutations.diff import DiffUpdateMutation
2020
from .mutations.diff_conflict import ResolveDiffConflict
21+
from .mutations.generator import GeneratorDefinitionRequestRun
2122
from .mutations.proposed_change import ProposedChangeMerge, ProposedChangeRequestRunCheck
2223
from .mutations.relationship import (
2324
RelationshipAdd,
@@ -83,6 +84,7 @@ class InfrahubBaseMutation(ObjectType):
8384
InfrahubAccountTokenDelete = InfrahubAccountTokenDelete.Field()
8485
CoreProposedChangeRunCheck = ProposedChangeRequestRunCheck.Field()
8586
CoreProposedChangeMerge = ProposedChangeMerge.Field()
87+
CoreGeneratorDefinitionRun = GeneratorDefinitionRequestRun.Field()
8688

8789
IPPrefixPoolGetResource = IPPrefixPoolGetResource.Field()
8890
IPAddressPoolGetResource = IPAddressPoolGetResource.Field()

0 commit comments

Comments
 (0)