Skip to content

Commit 02d9468

Browse files
committed
Add group trackingé
1 parent 9d619b7 commit 02d9468

File tree

11 files changed

+227
-16
lines changed

11 files changed

+227
-16
lines changed

backend/infrahub/core/constants/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,11 @@ class AllowOverrideType(InfrahubStringEnum):
146146
ANY = "any"
147147

148148

149+
class RepositoryObjects(InfrahubStringEnum):
150+
OBJECTS = "objects"
151+
MENUS = "menus"
152+
153+
149154
class ContentType(InfrahubStringEnum):
150155
APPLICATION_JSON = "application/json"
151156
APPLICATION_YAML = "application/yaml"

backend/infrahub/core/protocols.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,11 @@ class CoreRepository(LineageOwner, LineageSource, CoreGenericRepository, CoreTas
493493
commit: StringOptional
494494

495495

496+
class CoreRepositoryGroup(CoreGroup):
497+
content: Dropdown
498+
repository: RelationshipManager
499+
500+
496501
class CoreRepositoryValidator(CoreValidator):
497502
repository: RelationshipManager
498503

backend/infrahub/core/schema/definitions/core/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,13 @@
2828
from .core import core_node, core_task_target
2929
from .generator import core_generator_definition, core_generator_instance
3030
from .graphql_query import core_graphql_query
31-
from .group import core_generator_group, core_graphql_query_group, core_group, core_standard_group
31+
from .group import (
32+
core_generator_group,
33+
core_graphql_query_group,
34+
core_group,
35+
core_repository_group,
36+
core_standard_group,
37+
)
3238
from .ipam import builtin_ip_address, builtin_ip_prefix, builtin_ipam, core_ipam_namespace
3339
from .lineage import lineage_owner, lineage_source
3440
from .menu import generic_menu_item, menu_item
@@ -109,6 +115,7 @@
109115
core_standard_group,
110116
core_generator_group,
111117
core_graphql_query_group,
118+
core_repository_group,
112119
builtin_tag,
113120
core_account,
114121
core_account_token,

backend/infrahub/core/schema/definitions/core/group.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from infrahub.core.constants import (
22
BranchSupportType,
33
InfrahubKind,
4+
RepositoryObjects,
45
)
56
from infrahub.core.constants import RelationshipCardinality as Cardinality
67
from infrahub.core.constants import RelationshipKind as RelKind
8+
from infrahub.core.schema.dropdown import DropdownChoice
79

810
from ...attribute_schema import AttributeSchema as Attr
911
from ...generic_schema import GenericSchema
@@ -80,6 +82,10 @@
8082
generate_profile=False,
8183
)
8284

85+
# TODO add a CoreRepositoryObjectsGroup, attriute: kind="Objects" or "Menu", relationship: repository
86+
# TODO s'assurer que le client.log.... log bien dans infrahub.tasks
87+
88+
8389
core_graphql_query_group = NodeSchema(
8490
name="GraphQLQueryGroup",
8591
namespace="Core",
@@ -106,3 +112,47 @@
106112
),
107113
],
108114
)
115+
116+
117+
core_repository_group = NodeSchema(
118+
name="RepositoryGroup",
119+
namespace="Core",
120+
description="Group of nodes associated with a given repository.",
121+
include_in_menu=False,
122+
icon="mdi:account-group",
123+
label="Repository Group",
124+
default_filter="name__value",
125+
order_by=["name__value"],
126+
display_labels=["name__value"],
127+
branch=BranchSupportType.AGNOSTIC,
128+
inherit_from=[InfrahubKind.GENERICGROUP],
129+
generate_profile=False,
130+
attributes=[
131+
Attr(
132+
name="content",
133+
kind="Dropdown",
134+
description="The value_match defines how the update will be evaluated, if it has to match the new value, the previous value or both",
135+
choices=[
136+
DropdownChoice(
137+
name=RepositoryObjects.OBJECTS.value,
138+
label="Objects",
139+
description="Objects",
140+
),
141+
DropdownChoice(
142+
name=RepositoryObjects.MENUS.value,
143+
label="Menus",
144+
description="Menus",
145+
),
146+
],
147+
optional=False,
148+
),
149+
],
150+
relationships=[
151+
Rel(
152+
name="repository",
153+
peer=InfrahubKind.GENERICREPOSITORY,
154+
optional=False,
155+
cardinality=Cardinality.ONE,
156+
),
157+
],
158+
)

backend/infrahub/git/base.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,11 @@ class InfrahubRepositoryBase(BaseModel, ABC):
162162
infrahub_branch_name: str | None = Field(None, description="Infrahub branch on which to sync the remote repository")
163163
model_config = ConfigDict(arbitrary_types_allowed=True, ignored_types=(Flow, Task))
164164

165+
def get_client(self) -> InfrahubClient:
166+
if self.client is None:
167+
raise ValueError("Client is not set")
168+
return self.client
169+
165170
@property
166171
def sdk(self) -> InfrahubClient:
167172
if self.client:
@@ -445,9 +450,6 @@ def get_worktrees(self) -> list[Worktree]:
445450

446451
return [Worktree.init(response) for response in responses]
447452

448-
def get_client(self) -> InfrahubClient:
449-
return self.sdk
450-
451453
def get_location(self) -> str:
452454
if self.location:
453455
return self.location

backend/infrahub/git/integrator.py

Lines changed: 79 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,20 @@
2929
InfrahubPythonTransformConfig,
3030
InfrahubRepositoryConfig,
3131
)
32+
from infrahub_sdk.spec.menu import MenuFile
3233
from infrahub_sdk.spec.object import ObjectFile
3334
from infrahub_sdk.template import Jinja2Template
3435
from infrahub_sdk.template.exceptions import JinjaTemplateError
3536
from infrahub_sdk.utils import compare_lists
36-
from infrahub_sdk.yaml import SchemaFile
37+
from infrahub_sdk.yaml import InfrahubFile, SchemaFile
3738
from prefect import flow, task
3839
from prefect.cache_policies import NONE
3940
from prefect.logging import get_run_logger
4041
from pydantic import BaseModel, Field
4142
from pydantic import ValidationError as PydanticValidationError
4243
from typing_extensions import Self
4344

44-
from infrahub.core.constants import ArtifactStatus, ContentType, InfrahubKind, RepositorySyncStatus
45+
from infrahub.core.constants import ArtifactStatus, ContentType, InfrahubKind, RepositoryObjects, RepositorySyncStatus
4546
from infrahub.core.registry import registry
4647
from infrahub.events.artifact_action import ArtifactCreatedEvent, ArtifactUpdatedEvent
4748
from infrahub.events.models import EventMeta
@@ -161,7 +162,7 @@ async def init(cls, service: InfrahubServices, commit: str | None = None, **kwar
161162
async def ensure_location_is_defined(self) -> None:
162163
if self.location:
163164
return
164-
client = self.get_client()
165+
client = self.sdk
165166
repo = await client.get(
166167
kind=CoreGenericRepository, name__value=self.name, exclude=["tags", "credential"], raise_when_missing=True
167168
)
@@ -179,8 +180,15 @@ async def import_objects_from_files(
179180
self.create_commit_worktree(commit)
180181
await self._update_sync_status(branch_name=infrahub_branch_name, status=RepositorySyncStatus.SYNCING)
181182

183+
184+
print(f"Before reading config {InfrahubRepositoryConfig.model_fields["objects"]=}")
185+
print(f"Before reading config {InfrahubRepositoryConfig.model_fields["menus"]=}")
186+
182187
config_file = await self.get_repository_config(branch_name=infrahub_branch_name, commit=commit) # type: ignore[misc]
183188
sync_status = RepositorySyncStatus.IN_SYNC if config_file else RepositorySyncStatus.ERROR_IMPORT
189+
if sync_status == RepositorySyncStatus.ERROR_IMPORT:
190+
raise ValueError("Unable to import the repository here")
191+
184192
error: Exception | None = None
185193

186194
try:
@@ -191,6 +199,19 @@ async def import_objects_from_files(
191199
branch_name=infrahub_branch_name, commit=commit, config_file=config_file
192200
) # type: ignore[misc]
193201

202+
await self.import_objects(
203+
branch_name=infrahub_branch_name,
204+
commit=commit,
205+
files_pathes=[obj_config.file_path for obj_config in config_file.objects],
206+
object_type=RepositoryObjects.OBJECTS,
207+
) # type: ignore[misc]
208+
await self.import_objects(
209+
branch_name=infrahub_branch_name,
210+
commit=commit,
211+
files_pathes=[menu_config.file_path for menu_config in config_file.menus],
212+
object_type=RepositoryObjects.MENUS,
213+
) # type: ignore[misc]
214+
194215
await self.import_all_python_files( # type: ignore[call-overload]
195216
branch_name=infrahub_branch_name, commit=commit, config_file=config_file
196217
) # type: ignore[misc]
@@ -200,11 +221,15 @@ async def import_objects_from_files(
200221
await self.import_artifact_definitions(
201222
branch_name=infrahub_branch_name, commit=commit, config_file=config_file
202223
) # type: ignore[misc]
203-
await self.import_objects(branch_name=infrahub_branch_name, commit=commit, config_file=config_file) # type: ignore[misc]
224+
225+
# TODO: import menu, check def load() in menu.^y + add a deidcated group for menu, work as for objects
226+
# "CoreRepositoryMenuGroup"
204227

205228
except Exception as exc:
206229
sync_status = RepositorySyncStatus.ERROR_IMPORT
207230
error = exc
231+
log = get_run_logger()
232+
log.error(f"Error importing the repository {self.name} : {exc}")
208233

209234
await self._update_sync_status(branch_name=infrahub_branch_name, status=sync_status)
210235

@@ -831,23 +856,66 @@ async def _load_objects(
831856
self,
832857
paths: list[Path],
833858
branch: str,
859+
file_type: type[InfrahubFile],
834860
) -> None:
835861
"""Load one or multiple objects files into Infrahub."""
836862

837-
files = await self._load_yamlfile_from_disk(paths=paths, file_type=ObjectFile)
863+
log = get_run_logger()
864+
files = await self._load_yamlfile_from_disk(paths=paths, file_type=file_type)
865+
log.info(f"Loaded files {files=}")
866+
838867
for file in files:
839-
await file.validate_format(client=self.client, branch=branch)
868+
await file.validate_format(client=self.get_client(), branch=branch)
869+
schema = await self.get_client().schema.get(kind=file.spec.kind, branch=branch)
870+
if not schema.human_friendly_id and not schema.default_filter:
871+
raise ValueError(
872+
f"Schemas of objects or menus defined within {file.file_path} "
873+
"should have a `human_friendly_id` defined to avoid creating duplicated objects."
874+
)
875+
840876
for file in files:
841-
await file.process(client=self.client, branch=branch)
877+
# TODO: client.log that is then used should log into infrahub.tasks
878+
# TODO: do not create if no hfid/default_filter
879+
await file.process(client=self.get_client(), branch=branch)
880+
842881

843882
@task(name="import-objects", task_run_name="Import Objects", cache_policy=NONE) # type: ignore[arg-type]
844-
async def import_objects(self, branch_name: str, commit: str, config_file: InfrahubRepositoryConfig) -> None:
883+
async def import_objects(
884+
self, branch_name: str, commit: str, files_pathes: list[Path], object_type: RepositoryObjects
885+
) -> None:
886+
log = get_run_logger()
887+
log.info(f"Importing {object_type.value} from {files_pathes=}")
888+
845889
branch_wt = self.get_worktree(identifier=commit or branch_name)
846890

847-
file_pathes = [branch_wt.directory / obj.file_path for obj in config_file.objects]
848-
await self._load_objects(paths=file_pathes, branch=branch_name)
891+
file_pathes = [branch_wt.directory / file_path for file_path in files_pathes]
849892

850-
# TODO attach to a group?
893+
log.info("Before getting the repository object")
894+
# do not clone for now, assume only one import at a time
895+
if self.is_read_only:
896+
sdk_repo_obj = await self.get_client().get(
897+
kind=InfrahubKind.READONLYREPOSITORY, id=str(self.id), raise_when_missing=True
898+
)
899+
else:
900+
sdk_repo_obj = await self.get_client().get(
901+
kind=InfrahubKind.REPOSITORY, id=str(self.id), raise_when_missing=True
902+
)
903+
904+
log.info(f"After getting the repository object {sdk_repo_obj.id}")
905+
906+
async with self.get_client().start_tracking(
907+
identifier=f"group-repo-{object_type.value}-{self.id}",
908+
delete_unused_nodes=True,
909+
group_type="CoreRepositoryGroup",
910+
group_params={"content": object_type.value, "repository": sdk_repo_obj},
911+
):
912+
log.info("Started to track")
913+
file_type = ObjectFile if object_type == RepositoryObjects.OBJECTS else MenuFile
914+
await self._load_objects(
915+
paths=file_pathes,
916+
branch=branch_name,
917+
file_type=file_type,
918+
)
851919

852920
@task(name="check-definition-get", task_run_name="Get Check Definition", cache_policy=NONE) # type: ignore[arg-type]
853921
async def get_check_definition(

backend/tests/fixtures/repos/car-dealership/initial__main/.infrahub.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,9 @@ objects:
104104
file_path: "objects/persons.yml"
105105
- name: "manufacturers"
106106
file_path: "objects/manufacturers.yml"
107+
108+
menus:
109+
- name: "person_base"
110+
file_path: "menus/person_base.yml"
111+
- name: "manufacturer_base"
112+
file_path: "menus/manufacturer_base.yml"
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
apiVersion: infrahub.app/v1
3+
kind: Menu
4+
spec:
5+
data:
6+
- namespace: Testing
7+
name: Manufacturer
8+
label: Manufacturer
9+
kind: TestingManufacturer
10+
children:
11+
data:
12+
- namespace: Testing
13+
name: Car
14+
label: Car
15+
kind: TestingCar
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
apiVersion: infrahub.app/v1
3+
kind: Menu
4+
spec:
5+
data:
6+
- namespace: Testing
7+
name: Person
8+
label: Person
9+
kind: TestingPerson
10+
children:
11+
data:
12+
- namespace: Testing
13+
name: Manufacturer
14+
label: Manufacturer
15+
kind: TestingManufacturer

backend/tests/integration/git/test_repository.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import pytest
77
from git import GitCommandError
88

9-
from infrahub.core.constants import InfrahubKind, RepositoryOperationalStatus
9+
from infrahub.core.constants import InfrahubKind, RepositoryObjects, RepositoryOperationalStatus
1010
from infrahub.core.manager import NodeManager
1111
from infrahub.core.node import Node
1212
from infrahub.exceptions import RepositoryError
@@ -83,6 +83,43 @@ async def test_create_repository(
8383
assert manufacturer_mercedes.name.value == "Mercedes"
8484
assert list((await manufacturer_mercedes.customers.get_peers(db=db)).values())[0].name.value == "Ethan Carter"
8585

86+
repository_group = await NodeManager.get_one_by_default_filter(
87+
db=db,
88+
id=f"group-repo-{RepositoryObjects.OBJECTS.value}-{repository.id}",
89+
kind="CoreRepositoryGroup",
90+
raise_on_error=True,
91+
prefetch_relationships=True,
92+
)
93+
assert repository_group.content.value == RepositoryObjects.OBJECTS.value
94+
members = (await repository_group.members.get_peers(db=db)).values()
95+
assert len(members) == 4
96+
assert manufacturer_mercedes.id in {m.id for m in members}
97+
assert person_ethan.id in {m.id for m in members}
98+
99+
# TODO Retrieve menus
100+
101+
repository_group_menus = await NodeManager.get_one_by_default_filter(
102+
db=db,
103+
id=f"group-repo-{RepositoryObjects.MENUS.value}-{repository.id}",
104+
kind="CoreRepositoryGroup",
105+
raise_on_error=True,
106+
prefetch_relationships=True,
107+
)
108+
109+
assert repository_group_menus.content.value == RepositoryObjects.MENUS.value
110+
111+
menu_manufacturer = await NodeManager.get_one_by_hfid(
112+
db=db,
113+
hfid=["Testing", "Manufacturer"],
114+
kind="CoreMenu",
115+
raise_on_error=True,
116+
prefetch_relationships=True,
117+
)
118+
119+
menu_person = await NodeManager.get_one_by_hfid(
120+
db=db, hfid=["Testing", "Person"], kind="CoreMenu", raise_on_error=True, prefetch_relationships=True
121+
)
122+
86123
# TODO add a test with invalid yml file OR invalid order of objects in the yml file, and make sure the repository ends
87124
# up in error import state
88125

0 commit comments

Comments
 (0)