infrahub-server 1.4.3__py3-none-any.whl → 1.4.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
infrahub/cli/__init__.py CHANGED
@@ -1,5 +1,3 @@
1
- from asyncio import run as aiorun
2
-
3
1
  import typer
4
2
  from infrahub_sdk.async_typer import AsyncTyper
5
3
 
@@ -43,18 +41,87 @@ async def _init_shell(config_file: str) -> None:
43
41
 
44
42
 
45
43
  @app.command()
46
- def shell(config_file: str = typer.Argument("infrahub.toml", envvar="INFRAHUB_CONFIG")) -> None:
47
- """Start a python shell within Infrahub context."""
48
- aiorun(_init_shell(config_file=config_file))
49
-
50
- # TODO add check to properly exit of ipython is not installed
51
- from IPython import embed
52
- from rich import pretty
53
- from traitlets.config import get_config
54
-
55
- pretty.install()
56
-
57
- c = get_config()
58
- c.InteractiveShellEmbed.colors = "Linux"
59
- c.InteractiveShellApp.extensions.append("rich")
60
- embed(config=c)
44
+ def shell() -> None:
45
+ """Start a python shell within Infrahub context (requires IPython)."""
46
+ from infrahub_sdk import InfrahubClient
47
+ from IPython import start_ipython
48
+ from traitlets.config import Config
49
+
50
+ from infrahub import config
51
+ from infrahub.components import ComponentType
52
+ from infrahub.core.branch import Branch
53
+ from infrahub.core.initialization import initialization
54
+ from infrahub.core.manager import NodeManager
55
+ from infrahub.core.registry import registry
56
+ from infrahub.dependencies.registry import build_component_registry
57
+ from infrahub.lock import initialize_lock
58
+ from infrahub.services import InfrahubServices
59
+ from infrahub.workers.dependencies import (
60
+ get_cache,
61
+ get_component,
62
+ get_database,
63
+ get_workflow,
64
+ set_component_type,
65
+ )
66
+
67
+ async def initialize_service() -> InfrahubServices:
68
+ config.load_and_exit()
69
+ client = InfrahubClient()
70
+
71
+ component_type = ComponentType.GIT_AGENT
72
+ set_component_type(component_type=component_type)
73
+
74
+ database = await get_database()
75
+
76
+ build_component_registry()
77
+
78
+ workflow = get_workflow()
79
+ cache = await get_cache()
80
+ component = await get_component()
81
+ service = await InfrahubServices.new(
82
+ cache=cache,
83
+ client=client,
84
+ database=database,
85
+ workflow=workflow,
86
+ component=component,
87
+ component_type=component_type,
88
+ )
89
+ initialize_lock(service=service)
90
+
91
+ async with service.database as db:
92
+ await initialization(db=db)
93
+ await service.component.refresh_schema_hash()
94
+
95
+ return service
96
+
97
+ def welcome() -> None:
98
+ print("--------------------------------------")
99
+ print("infrahub interactive shell initialized")
100
+ print("--------------------------------------")
101
+ print("Available objects:")
102
+ print("* db: InfrahubDatabase")
103
+ print("* Branch: Branch")
104
+ print("* NodeManager: NodeManager")
105
+ print("* registry: Registry")
106
+ print("* service: InfrahubServices")
107
+ print()
108
+ print("Example use:")
109
+ print("In [1] tags = await NodeManager.query(schema='BuiltinTag', db=db)")
110
+
111
+ c = Config()
112
+ c.InteractiveShellApp.exec_lines = [
113
+ "service = await initialize_service()",
114
+ "db = service.database",
115
+ "welcome()",
116
+ ]
117
+ c.TerminalInteractiveShell.colors = "Neutral"
118
+
119
+ user_ns = {
120
+ "initialize_service": initialize_service,
121
+ "welcome": welcome,
122
+ "NodeManager": NodeManager,
123
+ "Branch": Branch,
124
+ "registry": registry,
125
+ }
126
+
127
+ start_ipython(argv=[], config=c, user_ns=user_ns)
infrahub/core/account.py CHANGED
@@ -5,9 +5,10 @@ from typing import TYPE_CHECKING, Any
5
5
 
6
6
  from typing_extensions import Self
7
7
 
8
- from infrahub.core.constants import InfrahubKind, PermissionDecision
8
+ from infrahub.core.constants import NULL_VALUE, InfrahubKind, PermissionDecision
9
9
  from infrahub.core.query import Query, QueryType
10
10
  from infrahub.core.registry import registry
11
+ from infrahub.core.timestamp import Timestamp
11
12
 
12
13
  if TYPE_CHECKING:
13
14
  from infrahub.core.branch import Branch
@@ -519,47 +520,72 @@ class AccountTokenValidateQuery(Query):
519
520
  super().__init__(**kwargs)
520
521
 
521
522
  async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa: ARG002
522
- token_filter_perms, token_params = self.branch.get_query_filter_relationships(
523
- rel_labels=["r1", "r2"], at=self.at, include_outside_parentheses=True
523
+ branch_filter, branch_params = self.branch.get_query_filter_path(
524
+ at=self.at.to_string(), branch_agnostic=self.branch_agnostic, is_isolated=False
524
525
  )
525
- self.params.update(token_params)
526
-
527
- account_filter_perms, account_params = self.branch.get_query_filter_relationships(
528
- rel_labels=["r31", "r41", "r5", "r6"], at=self.at, include_outside_parentheses=True
526
+ self.params.update(branch_params)
527
+ self.params.update(
528
+ {
529
+ "token_attr_name": "token",
530
+ "token_relationship_name": "account__token",
531
+ "token_value": self.token,
532
+ "null_value": NULL_VALUE,
533
+ }
529
534
  )
530
- self.params.update(account_params)
531
535
 
532
- self.params["token_value"] = self.token
533
-
534
- # ruff: noqa: E501
535
536
  query = """
536
- MATCH (at:InternalAccountToken)-[r1:HAS_ATTRIBUTE]-(a:Attribute {name: "token"})-[r2:HAS_VALUE]-(av:AttributeValue { value: $token_value })
537
- WHERE %s
538
- WITH at
539
- MATCH (at)-[r31]-(:Relationship)-[r41]-(acc:CoreGenericAccount)-[r5:HAS_ATTRIBUTE]-(an:Attribute {name: "name"})-[r6:HAS_VALUE]-(av:AttributeValue)
540
- WHERE %s
541
- """ % (
542
- "\n AND ".join(token_filter_perms),
543
- "\n AND ".join(account_filter_perms),
544
- )
545
-
537
+ // --------------
538
+ // get the active token node for this token value, if it exists
539
+ // --------------
540
+ MATCH (token_node:%(token_node_kind)s)-[r1:HAS_ATTRIBUTE]->(:Attribute {name: $token_attr_name})
541
+ -[r2:HAS_VALUE]->(av:AttributeValueIndexed { value: $token_value })
542
+ WHERE all(r in [r1, r2] WHERE (%(branch_filter)s))
543
+ ORDER BY r1.branch_level DESC, r1.from DESC, r1.status ASC, r2.branch_level DESC, r2.from DESC, r2.status ASC
544
+ LIMIT 1
545
+ WITH token_node
546
+ WHERE r1.status = "active" AND r2.status = "active"
547
+ // --------------
548
+ // get the expiration time
549
+ // --------------
550
+ OPTIONAL MATCH (token_node)-[r1:HAS_ATTRIBUTE]->(:Attribute {name: "expiration"})
551
+ -[r2:HAS_VALUE]->(av)
552
+ WHERE all(r in [r1, r2] WHERE (%(branch_filter)s))
553
+ ORDER BY r1.branch_level DESC, r1.from DESC, r1.status ASC, r2.branch_level DESC, r2.from DESC, r2.status ASC
554
+ LIMIT 1
555
+ WITH token_node, CASE
556
+ WHEN r1.status = "active" AND r2.status = "active" AND av.value <> $null_value THEN av.value
557
+ ELSE NULL
558
+ END AS expiration
559
+ // --------------
560
+ // get the linked account node from the token node
561
+ // --------------
562
+ MATCH (token_node)-[r1:IS_RELATED]-(:Relationship {name: $token_relationship_name})-[r2:IS_RELATED]-(account_node:%(account_node_kind)s)
563
+ WHERE all(r in [r1, r2] WHERE (%(branch_filter)s))
564
+ ORDER BY r1.branch_level DESC, r1.from DESC, r1.status ASC, r2.branch_level DESC, r2.from DESC, r2.status ASC
565
+ LIMIT 1
566
+ WITH expiration, account_node
567
+ WHERE r1.status = "active" AND r2.status = "active"
568
+ """ % {
569
+ "branch_filter": branch_filter,
570
+ "token_node_kind": InfrahubKind.ACCOUNTTOKEN,
571
+ "account_node_kind": InfrahubKind.GENERICACCOUNT,
572
+ }
546
573
  self.add_to_query(query)
547
-
548
- self.return_labels = ["at", "av", "acc"]
549
-
550
- def get_account_name(self) -> str | None:
551
- """Return the account name that matched the query or None."""
552
- if result := self.get_result():
553
- return result.get("av").get("value")
554
-
555
- return None
574
+ self.return_labels = ["account_node.uuid AS account_uuid", "expiration"]
556
575
 
557
576
  def get_account_id(self) -> str | None:
558
577
  """Return the account id that matched the query or a None."""
559
- if result := self.get_result():
560
- return result.get("acc").get("uuid")
561
-
562
- return None
578
+ result = self.get_result()
579
+ if not result:
580
+ return None
581
+ account_uuid = result.get_as_str(label="account_uuid")
582
+ expiration_with_tz = result.get_as_str(label="expiration")
583
+ if expiration_with_tz is None:
584
+ return account_uuid
585
+ expiration = Timestamp(expiration_with_tz)
586
+ if expiration < Timestamp():
587
+ return None
588
+ return account_uuid
563
589
 
564
590
 
565
591
  async def validate_token(token: str, db: InfrahubDatabase, branch: Branch | str | None = None) -> str | None:
@@ -1 +1 @@
1
- GRAPH_VERSION = 37
1
+ GRAPH_VERSION = 38
@@ -39,6 +39,7 @@ from .m034_find_orphaned_schema_fields import Migration034
39
39
  from .m035_orphan_relationships import Migration035
40
40
  from .m036_drop_attr_value_index import Migration036
41
41
  from .m037_index_attr_vals import Migration037
42
+ from .m038_redo_0000_prefix_fix import Migration038
42
43
 
43
44
  if TYPE_CHECKING:
44
45
  from infrahub.core.root import Root
@@ -83,6 +84,7 @@ MIGRATIONS: list[type[GraphMigration | InternalSchemaMigration | ArbitraryMigrat
83
84
  Migration035,
84
85
  Migration036,
85
86
  Migration037,
87
+ Migration038,
86
88
  ]
87
89
 
88
90
 
@@ -0,0 +1,63 @@
1
+ from __future__ import annotations
2
+
3
+ import ipaddress
4
+ from typing import TYPE_CHECKING, Sequence
5
+
6
+ from infrahub.core.branch.models import Branch
7
+ from infrahub.core.initialization import initialization
8
+ from infrahub.core.ipam.reconciler import IpamReconciler
9
+ from infrahub.core.manager import NodeManager
10
+ from infrahub.core.migrations.shared import MigrationResult
11
+ from infrahub.core.timestamp import Timestamp
12
+ from infrahub.lock import initialize_lock
13
+ from infrahub.log import get_logger
14
+
15
+ from ..shared import InternalSchemaMigration, SchemaMigration
16
+
17
+ if TYPE_CHECKING:
18
+ from infrahub.database import InfrahubDatabase
19
+
20
+ log = get_logger()
21
+
22
+
23
+ class Migration038(InternalSchemaMigration):
24
+ """
25
+ Re-run migration 026 after Migration037 updates AttributeValueIndexed vertices correctly so that the call to
26
+ NodeManager.query will work
27
+
28
+ If someone is upgrading from 1.2.4 (release before migration 026) or earlier to 1.4.x or later, then migration 026
29
+ fail to find any 0.0.0.0 prefix nodes even if they exist. So we run it again here after migration 037 makes the
30
+ AttributeValueIndexed changes to be sure it completes correctly.
31
+ """
32
+
33
+ name: str = "038_prefix_0000_fix"
34
+ minimum_version: int = 37
35
+ migrations: Sequence[SchemaMigration] = []
36
+
37
+ async def validate_migration(self, db: InfrahubDatabase) -> MigrationResult: # noqa: ARG002
38
+ return MigrationResult()
39
+
40
+ async def execute(self, db: InfrahubDatabase) -> MigrationResult:
41
+ # load schemas from database into registry
42
+ initialize_lock()
43
+ await initialization(db=db)
44
+
45
+ at = Timestamp()
46
+ for branch in await Branch.get_list(db=db):
47
+ prefix_0000s = await NodeManager.query(
48
+ db=db, schema="BuiltinIPPrefix", branch=branch, filters={"prefix__values": ["0.0.0.0/0", "::/0"]}
49
+ )
50
+ if not prefix_0000s:
51
+ continue
52
+ ipam_reconciler = IpamReconciler(db=db, branch=branch)
53
+ for prefix in prefix_0000s:
54
+ ip_namespace = await prefix.ip_namespace.get_peer(db=db)
55
+ ip_network = ipaddress.ip_network(prefix.prefix.value)
56
+ await ipam_reconciler.reconcile(
57
+ ip_value=ip_network,
58
+ namespace=ip_namespace,
59
+ node_uuid=prefix.get_id(),
60
+ at=at,
61
+ )
62
+
63
+ return MigrationResult()
@@ -168,6 +168,15 @@ async def request_generator_definition_run(
168
168
 
169
169
  client = get_client()
170
170
 
171
+ # Needs to be fetched before fetching group members otherwise `object` relationship would override
172
+ # existing node in client store without the `name` attribute due to #521
173
+ existing_instances = await client.filters(
174
+ kind=InfrahubKind.GENERATORINSTANCE,
175
+ definition__ids=[model.generator_definition.definition_id],
176
+ include=["object"],
177
+ branch=model.branch,
178
+ )
179
+
171
180
  group = await client.get(
172
181
  kind=InfrahubKind.GENERICGROUP,
173
182
  prefetch_relationships=True,
@@ -177,12 +186,6 @@ async def request_generator_definition_run(
177
186
  )
178
187
  await group.members.fetch()
179
188
 
180
- existing_instances = await client.filters(
181
- kind=InfrahubKind.GENERATORINSTANCE,
182
- definition__ids=[model.generator_definition.definition_id],
183
- include=["object"],
184
- branch=model.branch,
185
- )
186
189
  instance_by_member = {}
187
190
  for instance in existing_instances:
188
191
  instance_by_member[instance.object.peer.id] = instance.id
infrahub/git/tasks.py CHANGED
@@ -310,6 +310,15 @@ async def generate_request_artifact_definition(
310
310
 
311
311
  client = get_client()
312
312
 
313
+ # Needs to be fetched before fetching group members otherwise `object` relationship would override
314
+ # existing node in client store without the `name` attribute due to #521
315
+ existing_artifacts = await client.filters(
316
+ kind=CoreArtifact,
317
+ definition__ids=[model.artifact_definition_id],
318
+ include=["object"],
319
+ branch=model.branch,
320
+ )
321
+
313
322
  artifact_definition = await client.get(
314
323
  kind=CoreArtifactDefinition, id=model.artifact_definition_id, branch=model.branch
315
324
  )
@@ -319,12 +328,6 @@ async def generate_request_artifact_definition(
319
328
  await group.members.fetch()
320
329
  current_members = [member.id for member in group.members.peers]
321
330
 
322
- existing_artifacts = await client.filters(
323
- kind=CoreArtifact,
324
- definition__ids=[model.artifact_definition_id],
325
- include=["object"],
326
- branch=model.branch,
327
- )
328
331
  artifacts_by_member = {}
329
332
  for artifact in existing_artifacts:
330
333
  if artifact.object.id in current_members:
@@ -7,7 +7,7 @@ from infrahub.core.constants import GLOBAL_BRANCH_NAME, GlobalPermissions, Infra
7
7
  from infrahub.core.manager import get_schema
8
8
  from infrahub.core.schema.node_schema import NodeSchema
9
9
  from infrahub.database import InfrahubDatabase
10
- from infrahub.graphql.analyzer import InfrahubGraphQLQueryAnalyzer
10
+ from infrahub.graphql.analyzer import GraphQLOperation, InfrahubGraphQLQueryAnalyzer
11
11
  from infrahub.graphql.initialization import GraphqlParams
12
12
  from infrahub.permissions.constants import PermissionDecisionFlag
13
13
  from infrahub.utils import extract_camelcase_words
@@ -119,34 +119,46 @@ class PermissionManagerPermissionChecker(GraphQLQueryPermissionCheckerInterface)
119
119
  permission_required = GlobalPermission(
120
120
  action=GlobalPermissions.MANAGE_PERMISSIONS.value, decision=PermissionDecision.ALLOW_ALL.value
121
121
  )
122
+ # Map kinds and the relationship to protect from being read
123
+ kind_relationship_to_check = {
124
+ InfrahubKind.ACCOUNTROLE: "permissions",
125
+ InfrahubKind.BASEPERMISSION: "roles",
126
+ InfrahubKind.GLOBALPERMISSION: "roles",
127
+ InfrahubKind.OBJECTPERMISSION: "roles",
128
+ }
122
129
 
123
130
  async def supports(self, db: InfrahubDatabase, account_session: AccountSession, branch: Branch) -> bool: # noqa: ARG002
124
131
  return config.SETTINGS.main.allow_anonymous_access or account_session.authenticated
125
132
 
126
133
  async def check(
127
134
  self,
128
- db: InfrahubDatabase,
135
+ db: InfrahubDatabase, # noqa: ARG002
129
136
  account_session: AccountSession, # noqa: ARG002
130
137
  analyzed_query: InfrahubGraphQLQueryAnalyzer,
131
138
  query_parameters: GraphqlParams,
132
- branch: Branch,
139
+ branch: Branch, # noqa: ARG002
133
140
  ) -> CheckerResolution:
134
- is_permission_operation = False
135
- kinds = analyzed_query.query_report.impacted_models
136
-
137
- for kind in kinds:
138
- schema = get_schema(db=db, branch=branch, node_schema=kind)
139
- if is_permission_operation := kind in (
140
- InfrahubKind.BASEPERMISSION,
141
- InfrahubKind.GLOBALPERMISSION,
142
- InfrahubKind.OBJECTPERMISSION,
143
- ) or (isinstance(schema, NodeSchema) and InfrahubKind.BASEPERMISSION in schema.inherit_from):
144
- break
145
-
146
- if not is_permission_operation:
147
- return CheckerResolution.NEXT_CHECKER
148
-
149
- query_parameters.context.active_permissions.raise_for_permission(permission=self.permission_required)
141
+ for kind, relationship in self.kind_relationship_to_check.items():
142
+ if (
143
+ kind in analyzed_query.query_report.requested_read
144
+ and relationship in analyzed_query.query_report.requested_read[kind].relationships
145
+ ):
146
+ query_parameters.context.active_permissions.raise_for_permission(permission=self.permission_required)
147
+
148
+ for query in analyzed_query.query_report.queries:
149
+ if not query.infrahub_model:
150
+ continue
151
+
152
+ # Prevent mutations on permissions and account roles
153
+ if (
154
+ query.operation == GraphQLOperation.MUTATION
155
+ and isinstance(query.infrahub_model, NodeSchema)
156
+ and (
157
+ InfrahubKind.BASEPERMISSION in query.infrahub_model.inherit_from
158
+ or query.infrahub_model.kind == InfrahubKind.ACCOUNTROLE
159
+ )
160
+ ):
161
+ query_parameters.context.active_permissions.raise_for_permission(permission=self.permission_required)
150
162
 
151
163
  return CheckerResolution.NEXT_CHECKER
152
164
 
infrahub/lock.py CHANGED
@@ -13,6 +13,7 @@ from redis.asyncio.lock import Lock as GlobalLock
13
13
 
14
14
  from infrahub import config
15
15
  from infrahub.core.timestamp import current_timestamp
16
+ from infrahub.worker import WORKER_IDENTITY
16
17
 
17
18
  if TYPE_CHECKING:
18
19
  from types import TracebackType
@@ -92,7 +93,7 @@ class NATSLock:
92
93
  await self.release()
93
94
 
94
95
  async def acquire(self) -> None:
95
- token = current_timestamp()
96
+ token = f"{current_timestamp()}::{WORKER_IDENTITY}"
96
97
  while True:
97
98
  if await self.do_acquire(token):
98
99
  self.token = token
@@ -138,9 +139,9 @@ class InfrahubLock:
138
139
  if self.use_local:
139
140
  self.local = LocalLock()
140
141
  elif config.SETTINGS.cache.driver == config.CacheDriver.Redis:
141
- self.remote = GlobalLock(redis=self.connection, name=self.name)
142
+ self.remote = GlobalLock(redis=self.connection, name=f"lock.{self.name}")
142
143
  else:
143
- self.remote = NATSLock(service=self.connection, name=self.name)
144
+ self.remote = NATSLock(service=self.connection, name=f"lock.{self.name}")
144
145
 
145
146
  async def __aenter__(self):
146
147
  await self.acquire()
@@ -156,7 +157,7 @@ class InfrahubLock:
156
157
  async def acquire(self) -> None:
157
158
  with LOCK_ACQUIRE_TIME_METRICS.labels(self.name, self.lock_type).time():
158
159
  if not self.use_local:
159
- await self.remote.acquire(token=current_timestamp())
160
+ await self.remote.acquire(token=f"{current_timestamp()}::{WORKER_IDENTITY}")
160
161
  else:
161
162
  await self.local.acquire()
162
163
  self.acquire_time = time.time_ns()
@@ -646,16 +646,19 @@ async def validate_artifacts_generation(model: RequestArtifactDefinitionCheck, c
646
646
  context=context,
647
647
  )
648
648
 
649
- await artifact_definition.targets.fetch()
650
- group = artifact_definition.targets.peer
651
- await group.members.fetch()
652
-
649
+ # Needs to be fetched before fetching group members otherwise `object` relationship would override
650
+ # existing node in client store without the `name` attribute due to #521
653
651
  existing_artifacts = await client.filters(
654
652
  kind=InfrahubKind.ARTIFACT,
655
653
  definition__ids=[model.artifact_definition.definition_id],
656
654
  include=["object"],
657
655
  branch=model.source_branch,
658
656
  )
657
+
658
+ await artifact_definition.targets.fetch()
659
+ group = artifact_definition.targets.peer
660
+ await group.members.fetch()
661
+
659
662
  artifacts_by_member = {}
660
663
  for artifact in existing_artifacts:
661
664
  artifacts_by_member[artifact.object.peer.id] = artifact.id
@@ -907,6 +910,15 @@ async def request_generator_definition_check(model: RequestGeneratorDefinitionCh
907
910
  context=context,
908
911
  )
909
912
 
913
+ # Needs to be fetched before fetching group members otherwise `object` relationship would override
914
+ # existing node in client store without the `name` attribute due to #521
915
+ existing_instances = await client.filters(
916
+ kind=InfrahubKind.GENERATORINSTANCE,
917
+ definition__ids=[model.generator_definition.definition_id],
918
+ include=["object"],
919
+ branch=model.source_branch,
920
+ )
921
+
910
922
  group = await client.get(
911
923
  kind=InfrahubKind.GENERICGROUP,
912
924
  prefetch_relationships=True,
@@ -916,12 +928,6 @@ async def request_generator_definition_check(model: RequestGeneratorDefinitionCh
916
928
  )
917
929
  await group.members.fetch()
918
930
 
919
- existing_instances = await client.filters(
920
- kind=InfrahubKind.GENERATORINSTANCE,
921
- definition__ids=[model.generator_definition.definition_id],
922
- include=["object"],
923
- branch=model.source_branch,
924
- )
925
931
  instance_by_member = {}
926
932
  for instance in existing_instances:
927
933
  instance_by_member[instance.object.peer.id] = instance.id
@@ -42,7 +42,7 @@ async def create_branch_registry(db: InfrahubDatabase, branch: Branch) -> None:
42
42
 
43
43
 
44
44
  async def update_branch_registry(db: InfrahubDatabase, branch: Branch) -> None:
45
- """Update the registry for a branch if the schema hash has changed."""
45
+ """Update the registry for a branch if the schema hash has changed or the branch was rebased."""
46
46
 
47
47
  existing_branch: Branch = registry.branch[branch.name]
48
48
 
@@ -50,13 +50,23 @@ async def update_branch_registry(db: InfrahubDatabase, branch: Branch) -> None:
50
50
  log.warning("Branch schema hash is not set, cannot update branch registry")
51
51
  return
52
52
 
53
- if existing_branch.schema_hash and existing_branch.schema_hash.main == branch.active_schema_hash.main:
53
+ if existing_branch.schema_hash.main == branch.active_schema_hash.main:
54
54
  log.debug(
55
- "Branch schema hash is the same, no need to update branch registry",
55
+ "Branch schema hash is the same, no need to refresh the GraphQL schema within the registry",
56
56
  branch=branch.name,
57
57
  hash=existing_branch.schema_hash.main,
58
58
  worker=WORKER_IDENTITY,
59
59
  )
60
+ if existing_branch.branched_from != branch.branched_from:
61
+ # If the hash is the same but the branched_from timestamp differs it means
62
+ # that the branch has been rebased and these timestamps need to be refreshed
63
+ # in the registry even though the schema doesn't need to be reloaded.
64
+ log.info(
65
+ "Updating branched_from property in registry for rebased branch",
66
+ branch=branch.name,
67
+ worker=WORKER_IDENTITY,
68
+ )
69
+ registry.branch[branch.name] = branch
60
70
  return
61
71
 
62
72
  log.info(
infrahub_sdk/branch.py CHANGED
@@ -292,13 +292,14 @@ class InfrahubBranchManagerSync(InfraHubBranchManagerBase):
292
292
  },
293
293
  }
294
294
 
295
- query = Mutation(mutation="BranchCreate", input_data=input_data, query=MUTATION_QUERY_DATA)
295
+ mutation_query = MUTATION_QUERY_TASK if background_execution else MUTATION_QUERY_DATA
296
+ query = Mutation(mutation="BranchCreate", input_data=input_data, query=mutation_query)
296
297
  response = self.client.execute_graphql(query=query.render(), tracker="mutation-branch-create")
297
298
 
298
299
  # Make sure server version is recent enough to support background execution, as previously
299
300
  # using background_execution=True had no effect.
300
301
  if background_execution and "task" in response["BranchCreate"]:
301
- return BranchData(**response["BranchCreate"]["task"]["id"])
302
+ return response["BranchCreate"]["task"]["id"]
302
303
  return BranchData(**response["BranchCreate"]["object"])
303
304
 
304
305
  def delete(self, branch_name: str) -> bool:
infrahub_sdk/client.py CHANGED
@@ -209,7 +209,7 @@ class BaseClient:
209
209
  delete_unused_nodes=delete_unused_nodes,
210
210
  group_type=group_type,
211
211
  group_params=group_params,
212
- branch=branch,
212
+ branch=branch or self.default_branch,
213
213
  )
214
214
 
215
215
  def _graphql_url(
@@ -310,8 +310,7 @@ class InfrahubClient(BaseClient):
310
310
 
311
311
  async def get_user(self) -> dict:
312
312
  """Return user information"""
313
- user_info = await self.execute_graphql(query=QUERY_USER)
314
- return user_info
313
+ return await self.execute_graphql(query=QUERY_USER)
315
314
 
316
315
  async def get_user_permissions(self) -> dict:
317
316
  """Return user permissions"""
@@ -540,6 +539,7 @@ class InfrahubClient(BaseClient):
540
539
  schema_kind: str,
541
540
  branch: str,
542
541
  prefetch_relationships: bool,
542
+ include: list[str] | None,
543
543
  timeout: int | None = None,
544
544
  ) -> ProcessRelationsNode:
545
545
  """Processes InfrahubNode and their Relationships from the GraphQL query response.
@@ -564,9 +564,12 @@ class InfrahubClient(BaseClient):
564
564
  node = await InfrahubNode.from_graphql(client=self, branch=branch, data=item, timeout=timeout)
565
565
  nodes.append(node)
566
566
 
567
- if prefetch_relationships:
567
+ if prefetch_relationships or (include and any(rel in include for rel in node._relationships)):
568
568
  await node._process_relationships(
569
- node_data=item, branch=branch, related_nodes=related_nodes, timeout=timeout
569
+ node_data=item,
570
+ branch=branch,
571
+ related_nodes=related_nodes,
572
+ timeout=timeout,
570
573
  )
571
574
 
572
575
  return ProcessRelationsNode(nodes=nodes, related_nodes=related_nodes)
@@ -816,6 +819,7 @@ class InfrahubClient(BaseClient):
816
819
  branch=branch,
817
820
  prefetch_relationships=prefetch_relationships,
818
821
  timeout=timeout,
822
+ include=include,
819
823
  )
820
824
  return response, process_result
821
825
 
@@ -1103,13 +1107,13 @@ class InfrahubClient(BaseClient):
1103
1107
  ) -> dict:
1104
1108
  url = f"{self.address}/api/query/{name}"
1105
1109
  url_params = copy.deepcopy(params or {})
1110
+ url_params["branch"] = branch_name or self.default_branch
1111
+
1106
1112
  headers = copy.copy(self.headers or {})
1107
1113
 
1108
1114
  if self.insert_tracker and tracker:
1109
1115
  headers["X-Infrahub-Tracker"] = tracker
1110
1116
 
1111
- if branch_name:
1112
- url_params["branch"] = branch_name
1113
1117
  if at:
1114
1118
  url_params["at"] = at
1115
1119
 
@@ -1565,8 +1569,7 @@ class InfrahubClientSync(BaseClient):
1565
1569
 
1566
1570
  def get_user(self) -> dict:
1567
1571
  """Return user information"""
1568
- user_info = self.execute_graphql(query=QUERY_USER)
1569
- return user_info
1572
+ return self.execute_graphql(query=QUERY_USER)
1570
1573
 
1571
1574
  def get_user_permissions(self) -> dict:
1572
1575
  """Return user permissions"""
@@ -1831,6 +1834,7 @@ class InfrahubClientSync(BaseClient):
1831
1834
  schema_kind: str,
1832
1835
  branch: str,
1833
1836
  prefetch_relationships: bool,
1837
+ include: list[str] | None,
1834
1838
  timeout: int | None = None,
1835
1839
  ) -> ProcessRelationsNodeSync:
1836
1840
  """Processes InfrahubNodeSync and their Relationships from the GraphQL query response.
@@ -1855,7 +1859,7 @@ class InfrahubClientSync(BaseClient):
1855
1859
  node = InfrahubNodeSync.from_graphql(client=self, branch=branch, data=item, timeout=timeout)
1856
1860
  nodes.append(node)
1857
1861
 
1858
- if prefetch_relationships:
1862
+ if prefetch_relationships or (include and any(rel in include for rel in node._relationships)):
1859
1863
  node._process_relationships(node_data=item, branch=branch, related_nodes=related_nodes, timeout=timeout)
1860
1864
 
1861
1865
  return ProcessRelationsNodeSync(nodes=nodes, related_nodes=related_nodes)
@@ -1980,6 +1984,7 @@ class InfrahubClientSync(BaseClient):
1980
1984
  branch=branch,
1981
1985
  prefetch_relationships=prefetch_relationships,
1982
1986
  timeout=timeout,
1987
+ include=include,
1983
1988
  )
1984
1989
  return response, process_result
1985
1990
 
@@ -2242,13 +2247,13 @@ class InfrahubClientSync(BaseClient):
2242
2247
  ) -> dict:
2243
2248
  url = f"{self.address}/api/query/{name}"
2244
2249
  url_params = copy.deepcopy(params or {})
2250
+ url_params["branch"] = branch_name or self.default_branch
2251
+
2245
2252
  headers = copy.copy(self.headers or {})
2246
2253
 
2247
2254
  if self.insert_tracker and tracker:
2248
2255
  headers["X-Infrahub-Tracker"] = tracker
2249
2256
 
2250
- if branch_name:
2251
- url_params["branch"] = branch_name
2252
2257
  if at:
2253
2258
  url_params["at"] = at
2254
2259
  if subscribers:
@@ -409,7 +409,6 @@ def info( # noqa: PLR0915
409
409
  _: str = CONFIG_PARAM,
410
410
  ) -> None:
411
411
  """Display the status of the Python SDK."""
412
-
413
412
  info: dict[str, Any] = {
414
413
  "error": None,
415
414
  "status": ":x:",
@@ -417,12 +416,17 @@ def info( # noqa: PLR0915
417
416
  "user_info": {},
418
417
  "groups": {},
419
418
  }
419
+ client = initialize_client_sync()
420
+ fetch_user_details = bool(client.config.username) or bool(client.config.api_token)
421
+
420
422
  try:
421
- client = initialize_client_sync()
422
423
  info["infrahub_version"] = client.get_version()
423
- info["user_info"] = client.get_user()
424
+
425
+ if fetch_user_details:
426
+ info["user_info"] = client.get_user()
427
+ info["groups"] = client.get_user_permissions()
428
+
424
429
  info["status"] = ":white_heavy_check_mark:"
425
- info["groups"] = client.get_user_permissions()
426
430
  except Exception as e:
427
431
  info["error"] = f"{e!s} ({e.__class__.__name__})"
428
432
 
@@ -469,7 +473,7 @@ def info( # noqa: PLR0915
469
473
  pretty_model = Pretty(client.config.model_dump(), expand_all=True)
470
474
  layout["client_info"].update(Panel(pretty_model, title="Client Info"))
471
475
 
472
- # Infrahub information planel
476
+ # Infrahub information panel
473
477
  infrahub_info = Table(show_header=False, box=None)
474
478
  if info["user_info"]:
475
479
  infrahub_info.add_row("User:", info["user_info"]["AccountProfile"]["display_label"])
@@ -487,6 +491,8 @@ def info( # noqa: PLR0915
487
491
  infrahub_info.add_row("Groups:", "")
488
492
  for group, roles in groups.items():
489
493
  infrahub_info.add_row("", group, ", ".join(roles))
494
+ else:
495
+ infrahub_info.add_row("User:", "anonymous")
490
496
 
491
497
  layout["infrahub_info"].update(Panel(infrahub_info, title="Infrahub Info"))
492
498
 
infrahub_sdk/graphql.py CHANGED
@@ -8,7 +8,9 @@ from pydantic import BaseModel
8
8
  VARIABLE_TYPE_MAPPING = ((str, "String!"), (int, "Int!"), (float, "Float!"), (bool, "Boolean!"))
9
9
 
10
10
 
11
- def convert_to_graphql_as_string(value: str | bool | list | BaseModel | Enum | Any, convert_enum: bool = False) -> str: # noqa: PLR0911
11
+ def convert_to_graphql_as_string(value: Any, convert_enum: bool = False) -> str: # noqa: PLR0911
12
+ if value is None:
13
+ return "null"
12
14
  if isinstance(value, str) and value.startswith("$"):
13
15
  return value
14
16
  if isinstance(value, Enum):
infrahub_sdk/node/node.py CHANGED
@@ -234,15 +234,10 @@ class InfrahubNodeBase:
234
234
 
235
235
  rel: RelatedNodeBase | RelationshipManagerBase = getattr(self, item_name)
236
236
 
237
- # BLOCKED by https://github.com/opsmill/infrahub/issues/330
238
- # if (
239
- # item is None
240
- # and item_name in self._relationships
241
- # and self._schema.get_relationship(item_name).cardinality == "one"
242
- # ):
243
- # data[item_name] = None
244
- # continue
245
- # el
237
+ if rel_schema.cardinality == RelationshipCardinality.ONE and rel_schema.optional and not rel.initialized:
238
+ data[item_name] = None
239
+ continue
240
+
246
241
  if rel is None or not rel.initialized:
247
242
  continue
248
243
 
@@ -315,7 +310,16 @@ class InfrahubNodeBase:
315
310
  variables.pop(variable_key)
316
311
 
317
312
  # TODO: I do not feel _great_ about this
318
- if not data_item and data_item != [] and item in data:
313
+ # -> I don't even know who you are (but this is not great indeed) -- gmazoyer (quoting Thanos)
314
+ original_data_item = original_data.get(item)
315
+ original_data_item_is_none = original_data_item is None
316
+ if isinstance(original_data_item, dict):
317
+ if "node" in original_data_item:
318
+ original_data_item_is_none = original_data_item["node"] is None
319
+ elif "id" not in original_data_item:
320
+ original_data_item_is_none = True
321
+
322
+ if item in data and (data_item in ({}, []) or (data_item is None and original_data_item_is_none)):
319
323
  data.pop(item)
320
324
 
321
325
  def _strip_unmodified(self, data: dict, variables: dict) -> tuple[dict, dict]:
@@ -324,7 +328,9 @@ class InfrahubNodeBase:
324
328
  relationship_property = getattr(self, relationship)
325
329
  if not relationship_property or relationship not in data:
326
330
  continue
327
- if not relationship_property.initialized:
331
+ if not relationship_property.initialized and (
332
+ not isinstance(relationship_property, RelatedNodeBase) or not relationship_property.schema.optional
333
+ ):
328
334
  data.pop(relationship)
329
335
  elif isinstance(relationship_property, RelationshipManagerBase) and not relationship_property.has_update:
330
336
  data.pop(relationship)
@@ -742,12 +748,11 @@ class InfrahubNode(InfrahubNodeBase):
742
748
  continue
743
749
 
744
750
  peer_data: dict[str, Any] = {}
745
- if rel_schema and prefetch_relationships:
751
+ should_fetch_relationship = prefetch_relationships or (include is not None and rel_name in include)
752
+ if rel_schema and should_fetch_relationship:
746
753
  peer_schema = await self._client.schema.get(kind=rel_schema.peer, branch=self._branch)
747
754
  peer_node = InfrahubNode(client=self._client, schema=peer_schema, branch=self._branch)
748
755
  peer_data = await peer_node.generate_query_data_node(
749
- include=include,
750
- exclude=exclude,
751
756
  property=property,
752
757
  )
753
758
 
@@ -886,7 +891,11 @@ class InfrahubNode(InfrahubNodeBase):
886
891
  await self._process_mutation_result(mutation_name=mutation_name, response=response, timeout=timeout)
887
892
 
888
893
  async def _process_relationships(
889
- self, node_data: dict[str, Any], branch: str, related_nodes: list[InfrahubNode], timeout: int | None = None
894
+ self,
895
+ node_data: dict[str, Any],
896
+ branch: str,
897
+ related_nodes: list[InfrahubNode],
898
+ timeout: int | None = None,
890
899
  ) -> None:
891
900
  """Processes the Relationships of a InfrahubNode and add Related Nodes to a list.
892
901
 
@@ -1363,7 +1372,8 @@ class InfrahubNodeSync(InfrahubNodeBase):
1363
1372
  continue
1364
1373
 
1365
1374
  peer_data: dict[str, Any] = {}
1366
- if rel_schema and prefetch_relationships:
1375
+ should_fetch_relationship = prefetch_relationships or (include is not None and rel_name in include)
1376
+ if rel_schema and should_fetch_relationship:
1367
1377
  peer_schema = self._client.schema.get(kind=rel_schema.peer, branch=self._branch)
1368
1378
  peer_node = InfrahubNodeSync(client=self._client, schema=peer_schema, branch=self._branch)
1369
1379
  peer_data = peer_node.generate_query_data_node(include=include, exclude=exclude, property=property)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: infrahub-server
3
- Version: 1.4.3
3
+ Version: 1.4.5
4
4
  Summary: Infrahub is taking a new approach to Infrastructure Management by providing a new generation of datastore to organize and control all the data that defines how an infrastructure should run.
5
5
  License: Apache-2.0
6
6
  Author: OpsMill
@@ -36,7 +36,7 @@ infrahub/branch/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
36
  infrahub/branch/merge_mutation_checker.py,sha256=bKiQEWEqUqjQEIBDaOKZ2LwA9kgWCEyss80B5yGhPX8,1147
37
37
  infrahub/branch/tasks.py,sha256=09AtjKpA5TC9NxsNt3oE--1pHeh1h1hRYvY41YfMt90,1059
38
38
  infrahub/branch/triggers.py,sha256=4sywoEX79fY2NkaGe6tTHnmytf4k6gXDm2FJHkkRJOw,793
39
- infrahub/cli/__init__.py,sha256=U0Ku6La8qpVpLdIkhCRqxQyvAFJG8WRZAh2yx6yzxCs,1781
39
+ infrahub/cli/__init__.py,sha256=d8x7c4CIq66zul2w9TdT82qjR5cMXV2zSujovJ4kV00,3948
40
40
  infrahub/cli/constants.py,sha256=CoCeTMnfsA3j7ArdLKLZK4VPxOM7ls17qpxGJmND0m8,129
41
41
  infrahub/cli/context.py,sha256=u2EYq9-vjzzfZdIYIbYmTG67nYSsyVFDPBtJ3KgE7KY,494
42
42
  infrahub/cli/db.py,sha256=hqZcklxTgAKuXWOthrltOdENsiiTthq6tqySxu4HPFE,37727
@@ -61,7 +61,7 @@ infrahub/constants/database.py,sha256=WmV1iuOk4xulxZHOVvO3sS_VF1eTf7fKh0TPe_RnfV
61
61
  infrahub/constants/environment.py,sha256=ry-6qsBzSumOjjiq1D3XNoquf1LWqFKiQSJj8t6nET4,32
62
62
  infrahub/context.py,sha256=8SZRKSECkkcsNNzDaKEUJ7Nyr0EzUfToAy969LXjQVk,1554
63
63
  infrahub/core/__init__.py,sha256=z6EJBZyCYCBqinoBtX9li6BTBbbGV8WCkE_4CrEsmDA,104
64
- infrahub/core/account.py,sha256=s8ZC7J8rtEvQZQjbVuiKMlPhl6aQjtAjbZhBejLC0X8,26182
64
+ infrahub/core/account.py,sha256=6f1cIDWvL-HsbzL0UwWoCbDTzx55wzd_SkpQXiKDjcE,27477
65
65
  infrahub/core/attribute.py,sha256=f1-XLuRzbpG7T8y6pqQQqxQkbGDWhS5AZA2_r_mzY-A,44537
66
66
  infrahub/core/branch/__init__.py,sha256=h0oIj0gHp1xI-N1cYW8_N6VZ81CBOmLuiUt5cS5nKuk,49
67
67
  infrahub/core/branch/enums.py,sha256=vGnaTCzikvMcLikKN25TJ8uCmhnD448dp1ve1_tLjwQ,186
@@ -139,7 +139,7 @@ infrahub/core/diff/repository/deserializer.py,sha256=bhN9ao8HxqKyRz273QGLNV9z9_S
139
139
  infrahub/core/diff/repository/repository.py,sha256=u0QTMY1e2dknG_DuRAwzFt-Lp1_mdj5lqF2ymt77k9E,25581
140
140
  infrahub/core/diff/tasks.py,sha256=jSXlenTJ5Fc189Xvm971e3-gBDRnfN19cxNaWvEFwAE,3306
141
141
  infrahub/core/enums.py,sha256=qGbhRVoH43Xi0iDkUfWdQiKapJbLT9UKsCobFk_paIk,491
142
- infrahub/core/graph/__init__.py,sha256=ZYp8RkLFWRjTvuZPPtnGwr38etrRSIUJZiJjqVJdf3o,19
142
+ infrahub/core/graph/__init__.py,sha256=O8Xci_50VdpSOWWUsegCGQvL7gUWJAJNc1ompQbvhf0,19
143
143
  infrahub/core/graph/constraints.py,sha256=lmuzrKDFoeSKRiLtycB9PXi6zhMYghczKrPYvfWyy90,10396
144
144
  infrahub/core/graph/index.py,sha256=A9jzEE_wldBJsEsflODeMt4GM8sPmmbHAJRNdFioR1k,1736
145
145
  infrahub/core/graph/schema.py,sha256=o50Jcy6GBRk55RkDJSMIDDwHhLD7y_RWOirI9rCex4A,10776
@@ -158,7 +158,7 @@ infrahub/core/ipam/utilization.py,sha256=d-zpXCaWsHgJxBLopCDd7y4sJYvHcIzzpYhbTMI
158
158
  infrahub/core/manager.py,sha256=xMXPwlaGNnghkRUW0ILwJAUlBQJZqo9cGp9GVyqkqYk,47564
159
159
  infrahub/core/merge.py,sha256=TNZpxjNYcl3dnvE8eYXaWSXFDYeEa8DDsS9XbR2XKlA,11217
160
160
  infrahub/core/migrations/__init__.py,sha256=dIExw90CrdTByeJqpiWkaZBclpAfzatG2H6fXx54su0,1305
161
- infrahub/core/migrations/graph/__init__.py,sha256=RUaz8bfg8ifZjf6Hfl-J7F5qOP-UbOiFwlalzI9pSok,4091
161
+ infrahub/core/migrations/graph/__init__.py,sha256=8NNpXMWWxKbuliktUJg7JllIePGERZxOnumC-UmkpX0,4161
162
162
  infrahub/core/migrations/graph/m001_add_version_to_graph.py,sha256=YcLN6cFjE6IGheXR4Ujb6CcyY8bJ7WE289hcKJaENOc,1515
163
163
  infrahub/core/migrations/graph/m002_attribute_is_default.py,sha256=wB6f2N_ChTvGajqHD-OWCG5ahRMDhhXZuwo79ieq_II,1036
164
164
  infrahub/core/migrations/graph/m003_relationship_parent_optional.py,sha256=Aya-s98XfE9C7YluOwEjilwgnjaBnZxp27w_Xdv_NmU,2330
@@ -196,6 +196,7 @@ infrahub/core/migrations/graph/m034_find_orphaned_schema_fields.py,sha256=Fekohf
196
196
  infrahub/core/migrations/graph/m035_orphan_relationships.py,sha256=K0J5gzFF5gY-QMom0tRGDckqw19aN0uSV8AZ8KdKSMo,1371
197
197
  infrahub/core/migrations/graph/m036_drop_attr_value_index.py,sha256=z2BplzX0mue3lOxrM7xnWDNrs7N3TGdFKHZR-u0wDDY,1439
198
198
  infrahub/core/migrations/graph/m037_index_attr_vals.py,sha256=bJB4yPWE73XA_ErUcnY90CR09_jbtA0jW6tE5U0GvQ4,22730
199
+ infrahub/core/migrations/graph/m038_redo_0000_prefix_fix.py,sha256=8seWnXQhgEJDFLWxYHVcnMNDPcHq5C24c0RYrtn_WGE,2411
199
200
  infrahub/core/migrations/query/__init__.py,sha256=JoWOUWlV6IzwxWxObsfCnAAKUOHJkE7dZlOsfB64ZEo,876
200
201
  infrahub/core/migrations/query/attribute_add.py,sha256=oitzB-PPAclfyNtcwCWJY3RdI5Zi4oEnR62BDzn1UQk,4835
201
202
  infrahub/core/migrations/query/attribute_rename.py,sha256=onb9Nanht1Tz47JgneAcFsuhqqvPS6dvI2nNjRupLLo,6892
@@ -433,7 +434,7 @@ infrahub/events/validator_action.py,sha256=nQJH-RWcgr3-tzmIldvPmu5O7dUAmv1qQnuxx
433
434
  infrahub/exceptions.py,sha256=cbM-f_2U-5ZFVZ_MaaXgs64k4M7uJ7fqDU2iCRoWlYY,11861
434
435
  infrahub/generators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
435
436
  infrahub/generators/models.py,sha256=9qhSfsoG-uYux35HClAxSq7TRfkosqN3i_eQkeTokLs,1916
436
- infrahub/generators/tasks.py,sha256=br1-1quQvcnTUtSIArVqG8ZjD09K7i65s11B8lOer1A,9382
437
+ infrahub/generators/tasks.py,sha256=Ci6lIbnUS6faGzbUjw1ggROpv17guZ-Z9HH9f6cAZv4,9563
437
438
  infrahub/git/__init__.py,sha256=KeQ9U8UI5jDj6KB6j00Oal7MZmtOD9vKqVgiezG_EQA,281
438
439
  infrahub/git/base.py,sha256=RNXow4RzTBJyjTmT1vZO9_DZSx4Dyv12M-bK_xTBTbg,38657
439
440
  infrahub/git/constants.py,sha256=XpzcAkXbsgXZgrXey74id1sXV8Q6EHb_4FNw7BndxyY,106
@@ -441,7 +442,7 @@ infrahub/git/directory.py,sha256=fozxLXXJPweHG95yQwQkR5yy3sfTdmHiczCAJnsUX54,861
441
442
  infrahub/git/integrator.py,sha256=C9h1qo8EVE8FjwzGWHhTD50I8TuCTH9asgC8-z5eK-g,62818
442
443
  infrahub/git/models.py,sha256=ozk9alxQ8Ops1lw1g8iR3O7INuw1VPsEUr5Wceh9HQY,12152
443
444
  infrahub/git/repository.py,sha256=wPsJAA9aHTHdfw0gqCfkNHvcivs7JabsDf-uazptZt0,10928
444
- infrahub/git/tasks.py,sha256=W9CcF7pw5WXEEIFY4sMaSTWETe7eZcLtIKHlwz6FFYI,37334
445
+ infrahub/git/tasks.py,sha256=aBFN7yV72UshcxFljrmHAKGSDoe0e1eVjrnyPDXQEM8,37515
445
446
  infrahub/git/utils.py,sha256=xhWxlu_FbMqbrwanpPkex4hKRS_d2AFzlxI_6kVQllw,1741
446
447
  infrahub/git/worktree.py,sha256=8IYJWOBytKUWwhMmMVehR4ceeO9e13nV-mvn3iVEgZY,1727
447
448
  infrahub/git_credential/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -460,7 +461,7 @@ infrahub/graphql/auth/query_permission_checker/checker.py,sha256=OSJmoiqETvRtayY
460
461
  infrahub/graphql/auth/query_permission_checker/default_branch_checker.py,sha256=QiuZXFnaTPaILeIl7MoEu4e6EYexRJd-AZLgAHwaCQc,2173
461
462
  infrahub/graphql/auth/query_permission_checker/interface.py,sha256=p4IIicoD1QAM1Q_NiNqOMQqSAIkbNrSHJ-pAlrJfBfo,848
462
463
  infrahub/graphql/auth/query_permission_checker/merge_operation_checker.py,sha256=_cv3jSsIA3MXQcD2etCgKzvTKaKNAhwDNqPRIlIzUo4,1640
463
- infrahub/graphql/auth/query_permission_checker/object_permission_checker.py,sha256=5Af8bwtG5I-jxPQGOG_-qKV9bQFECn27e_gBoYDxXrs,8408
464
+ infrahub/graphql/auth/query_permission_checker/object_permission_checker.py,sha256=1IVUWIV-GOY_pc3zApii2WNdAG7xHhnw05mUUtXbN6A,9093
464
465
  infrahub/graphql/auth/query_permission_checker/super_admin_checker.py,sha256=2RlJ1G-BmJIQW33SletzK1gIQ3nyEB2edTiX0xAjR2E,1550
465
466
  infrahub/graphql/constants.py,sha256=iVvo3HK-ch7YmHw1Eg2E_ja3I45cNAwjpYahsnu85CI,37
466
467
  infrahub/graphql/context.py,sha256=ahp-MvX_0glg9mSPbPVhEwvbYzrIKtaEAGt7CVnAusE,1681
@@ -550,7 +551,7 @@ infrahub/groups/models.py,sha256=eOiNtmJapT4zRQ3XbUf8TVb_bhzG2lUfVPhIBZv3Wz0,759
550
551
  infrahub/groups/parsers.py,sha256=A60n-JmT-8F-7ATugV97R-mPWpRhNVNxqEbpibxiiUs,4260
551
552
  infrahub/groups/tasks.py,sha256=yg-VNj37iWdBHyPE9BhN2QzjVo2D8glnlO37pwGcdsU,1563
552
553
  infrahub/helpers.py,sha256=KchbQqgipU4VjfwnDbzCGjnEv-4espw_g63Zw4KAhbo,251
553
- infrahub/lock.py,sha256=M7HJMOBLGybhss0SVEJ6oDHY0O5a2A4ylWkG9EVHjMk,8542
554
+ infrahub/lock.py,sha256=EagMvKKPqWXKLvOpw2OXNPxNa1XLG0JNZgMzCU45EuA,8654
554
555
  infrahub/log.py,sha256=4FP80DGY5sk9R7CjcDpPiTfxuow-nfbhXWVpDByCVrw,2817
555
556
  infrahub/menu/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
556
557
  infrahub/menu/constants.py,sha256=z9aAxIBlAMXrjl3dXo0vZxBU0pcfh1FQOiqIussvpD0,195
@@ -621,7 +622,7 @@ infrahub/proposed_change/branch_diff.py,sha256=IdMxf5zPmhybQKPPz7AlruNmLCKf5VISP
621
622
  infrahub/proposed_change/checker.py,sha256=ZhNEVJKsQbHH2UE1O35MfOVa8cK1QGEqGyn6MsOuqSQ,1558
622
623
  infrahub/proposed_change/constants.py,sha256=auifG94Oo2cJ4RwZx4P-XDPDpKYPtEVxh013KPfiEdU,2080
623
624
  infrahub/proposed_change/models.py,sha256=ivWJmEAihprKmwgaBGDJ4Koq4ETciE5GfDp86KHDnns,5892
624
- infrahub/proposed_change/tasks.py,sha256=-9-V7Waya7ClZsSQeWQdh-vVksE6Ozn0vEF7eAmPLE8,63629
625
+ infrahub/proposed_change/tasks.py,sha256=M1vdCE1J_IBxGwSnQLWwobxhOSDaaxuvEScgZuYbus8,63991
625
626
  infrahub/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
626
627
  infrahub/pytest_plugin.py,sha256=u3t0WgLMo9XmuQYeb28mccQ3xbnyv2Fv173YWl1zBiM,6678
627
628
  infrahub/schema/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -662,7 +663,7 @@ infrahub/tasks/check.py,sha256=37n1U1Knb3AV6kz2sw_IabL9pnlqceLVICWf9GdSxZE,687
662
663
  infrahub/tasks/dummy.py,sha256=6SxlQqQXZqgTuwLaAsK-p1O1TYNKfdGmUYjNJFNHe9s,1209
663
664
  infrahub/tasks/keepalive.py,sha256=D6yh3Vmlr1WCEpZibk2YLc2n0dCcX6tM62HCSxyGEu8,783
664
665
  infrahub/tasks/recurring.py,sha256=RJO2zdzCU-38Kb81lmCUbFQOBhGui8qn2QizTV4vj9I,447
665
- infrahub/tasks/registry.py,sha256=zanLxRT_RFkYV2Y3OQedHu2-PTBwoExbAzgokT0TZjQ,3957
666
+ infrahub/tasks/registry.py,sha256=ncoiZ-GAkyeuKTy5gRkGzQhtx1rSLsMX4o9uZ1A0Kkk,4543
666
667
  infrahub/telemetry/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
667
668
  infrahub/telemetry/constants.py,sha256=_5mJAZaT_wTCaF7Yzsd---Zn1N6GZkoP_954GK8K4-c,184
668
669
  infrahub/telemetry/database.py,sha256=9UVPOxRionVF65jjo8slRIaNBOv-KMRzq7I-7fe3wZE,3111
@@ -708,9 +709,9 @@ infrahub_sdk/_importer.py,sha256=8oHTMxa_AMO_qbfb3UXNfjSr31S5YJTcqe-YMrixY_E,225
708
709
  infrahub_sdk/analyzer.py,sha256=UDJN372vdAiuAv2TEyPUlsSVoUfZN6obWkIokNNaHbA,4148
709
710
  infrahub_sdk/async_typer.py,sha256=Gj7E8EGdjA-XF404vr9cBt20mmbroQh7N68HXhWYx00,892
710
711
  infrahub_sdk/batch.py,sha256=LRZ_04ic56ll9FBjgXCYrJRDJcwB3wR1yX4grrQutDQ,3795
711
- infrahub_sdk/branch.py,sha256=hmtoIekQ1uusoJ6yEKlw6vrFMTAHJrXu-YsqqCQC_kc,12716
712
+ infrahub_sdk/branch.py,sha256=nj0rAOFSqZiAZ5xjwjwTSXdNSso5s7zm6y_6ts6ieSQ,12791
712
713
  infrahub_sdk/checks.py,sha256=rFHlEY8XEYcjpLCg6gd9a0R8vPnkxNp0OnXk-odsZKY,5707
713
- infrahub_sdk/client.py,sha256=0llKuTQFKRwtxjsWvavw2brOF5B0gJdxn8iIhxAcQzM,101611
714
+ infrahub_sdk/client.py,sha256=qO5Sd8M1Y5YcZeWXWa6ECExshJS78qajAKpwctZwQas,101899
714
715
  infrahub_sdk/config.py,sha256=wnVRkaVO4Nd2IBLRVpLtrC-jjW399mgr1DprohTEzQQ,7936
715
716
  infrahub_sdk/constants.py,sha256=Ca66r09eDzpmMhfFAspKFSehSxOmoflVongP-UuBDc4,138
716
717
  infrahub_sdk/context.py,sha256=QgXZvtUrKolp6ML8TguVK87Wuu-3KyizZVV_N2F4oCw,400
@@ -718,7 +719,7 @@ infrahub_sdk/ctl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,
718
719
  infrahub_sdk/ctl/branch.py,sha256=GeGDNGNpew93MZblqhG0r45wqSz_p8CcQ9R8zuj_jmg,4742
719
720
  infrahub_sdk/ctl/check.py,sha256=HWsK1rTpGF2VvRBiS5KZrRxXrsAHDXoFS3wJkmq8pik,7895
720
721
  infrahub_sdk/ctl/cli.py,sha256=A9jJKYBo5opzIIyWYf6niyAHhy49V59g6biueMDFbpE,328
721
- infrahub_sdk/ctl/cli_commands.py,sha256=ShgWESs4_DjBK4nttXdVAYrJoTaxOvpEtMwSxft0Rrg,18560
722
+ infrahub_sdk/ctl/cli_commands.py,sha256=fExd8OiXwzfDXPPq_JC9jPKH58k3o9JK9-yf18te2nU,18753
722
723
  infrahub_sdk/ctl/client.py,sha256=6bmXmQta9qQCJ8HybQwt2uSF2X1Em91xNFpwiKFujxs,2083
723
724
  infrahub_sdk/ctl/config.py,sha256=y3kTvfxDO2FKzgvaIXKPKOES7BqXT-s9Kuww7ROfs-4,3039
724
725
  infrahub_sdk/ctl/exceptions.py,sha256=RPdBtIj5qVvNqNR9Y-mPNF7kDUxXUUCac5msyitrBXo,272
@@ -738,13 +739,13 @@ infrahub_sdk/data.py,sha256=4d8Fd1s7lTeOu8JWXsK2m2BM8t_5HG0Z73fnCZGc7Pc,841
738
739
  infrahub_sdk/diff.py,sha256=Ms-3YyXo-DoF1feV9qP7GKakBYUNFsULZdy-yMEG71w,4258
739
740
  infrahub_sdk/exceptions.py,sha256=gZLfZhyDd0M-w5WMzzfPkFwha-aZirJ739N_OMN7Ujs,5728
740
741
  infrahub_sdk/generator.py,sha256=I00G7BdQohJFZ7wQru1SWcwO41gPbuQ3ZGEDVkLIn60,3403
741
- infrahub_sdk/graphql.py,sha256=zrxRveg8-t0FbLtOEMDiiW0vqtBHc2qaFRkiHF9Bp6g,7019
742
+ infrahub_sdk/graphql.py,sha256=UQValA_5oEtdm2VC2tuzUnjm4CEj3J0qk79f70cLr84,7024
742
743
  infrahub_sdk/groups.py,sha256=GL14ByW4GHrkqOLJ-_vGhu6bkYDxljqPtkErcQVehv0,711
743
744
  infrahub_sdk/jinja2.py,sha256=lTfV9E_P5gApaX6RW9M8U8oixQi-0H3U8wcs8fdGVaU,1150
744
745
  infrahub_sdk/node/__init__.py,sha256=clAUZ9lNVPFguelR5Sg9PzklAZruTKEm2xk-BaO68l8,1262
745
746
  infrahub_sdk/node/attribute.py,sha256=oEY1qxip8ETEx9Q33NhSQo013zmzrmpVIFzSkEMUY8M,4547
746
747
  infrahub_sdk/node/constants.py,sha256=TJO4uxvv7sc3FjoLdQdV7Ccymqz8AqxDenARst8awb4,775
747
- infrahub_sdk/node/node.py,sha256=LPl9w9CySrZSEXuTn6jXuSshQCzAPyehc8Kyzpsq5dE,73518
748
+ infrahub_sdk/node/node.py,sha256=9ZDySD3NLRgu9hDsw71YO_LLUGKsT0JOMy2kOXzvyFU,74201
748
749
  infrahub_sdk/node/parsers.py,sha256=sLDdT6neoYSZIjOCmq8Bgd0LK8FFoasjvJLuSz0whSU,543
749
750
  infrahub_sdk/node/property.py,sha256=8Mjkc8bp3kLlHyllwxDJlpJTuOA1ciMgY8mtH3dFVLM,728
750
751
  infrahub_sdk/node/related_node.py,sha256=fPMnZ83OZnnbimaPC14MdE3lR-kumAA6hbOhRlo1gms,10093
@@ -826,8 +827,8 @@ infrahub_testcontainers/models.py,sha256=ASYyvl7d_WQz_i7y8-3iab9hwwmCl3OCJavqVbe
826
827
  infrahub_testcontainers/performance_test.py,sha256=hvwiy6tc_lWniYqGkqfOXVGAmA_IV15VOZqbiD9ezno,6149
827
828
  infrahub_testcontainers/plugin.py,sha256=I3RuZQ0dARyKHuqCf0y1Yj731P2Mwf3BJUehRJKeWrs,5645
828
829
  infrahub_testcontainers/prometheus.yml,sha256=610xQEyj3xuVJMzPkC4m1fRnCrjGpiRBrXA2ytCLa54,599
829
- infrahub_server-1.4.3.dist-info/LICENSE.txt,sha256=7GQO7kxVoQYnZtFrjZBKLRXbrGwwwimHPPOJtqXsozQ,11340
830
- infrahub_server-1.4.3.dist-info/METADATA,sha256=3sPnrG5a6lzgW83Theqmb2Ztk6JpquCmwM-AOHOS_4E,8277
831
- infrahub_server-1.4.3.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
832
- infrahub_server-1.4.3.dist-info/entry_points.txt,sha256=UXIeFWDsrV-4IllNvUEd6KieYGzQfn9paga2YyABOQI,393
833
- infrahub_server-1.4.3.dist-info/RECORD,,
830
+ infrahub_server-1.4.5.dist-info/LICENSE.txt,sha256=7GQO7kxVoQYnZtFrjZBKLRXbrGwwwimHPPOJtqXsozQ,11340
831
+ infrahub_server-1.4.5.dist-info/METADATA,sha256=MHlD-kNOoe00t0MsyjiyHHPyfF4WkAneljzGh6VJg90,8277
832
+ infrahub_server-1.4.5.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
833
+ infrahub_server-1.4.5.dist-info/entry_points.txt,sha256=UXIeFWDsrV-4IllNvUEd6KieYGzQfn9paga2YyABOQI,393
834
+ infrahub_server-1.4.5.dist-info/RECORD,,