infrahub-server 1.3.7__py3-none-any.whl → 1.4.0__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.
Files changed (174) hide show
  1. infrahub/api/internal.py +5 -0
  2. infrahub/artifacts/tasks.py +17 -22
  3. infrahub/branch/merge_mutation_checker.py +38 -0
  4. infrahub/cli/__init__.py +2 -2
  5. infrahub/cli/context.py +7 -3
  6. infrahub/cli/db.py +5 -16
  7. infrahub/cli/upgrade.py +10 -29
  8. infrahub/computed_attribute/tasks.py +36 -46
  9. infrahub/config.py +57 -6
  10. infrahub/constants/environment.py +1 -0
  11. infrahub/core/attribute.py +15 -7
  12. infrahub/core/branch/tasks.py +43 -41
  13. infrahub/core/constants/__init__.py +21 -6
  14. infrahub/core/constants/infrahubkind.py +2 -0
  15. infrahub/core/diff/coordinator.py +3 -1
  16. infrahub/core/diff/model/path.py +0 -39
  17. infrahub/core/diff/repository/repository.py +0 -8
  18. infrahub/core/diff/tasks.py +11 -8
  19. infrahub/core/graph/__init__.py +1 -1
  20. infrahub/core/graph/index.py +1 -2
  21. infrahub/core/graph/schema.py +50 -29
  22. infrahub/core/initialization.py +81 -47
  23. infrahub/core/ipam/tasks.py +4 -3
  24. infrahub/core/merge.py +8 -10
  25. infrahub/core/migrations/__init__.py +2 -0
  26. infrahub/core/migrations/graph/__init__.py +4 -0
  27. infrahub/core/migrations/graph/m036_drop_attr_value_index.py +45 -0
  28. infrahub/core/migrations/graph/m037_index_attr_vals.py +577 -0
  29. infrahub/core/migrations/query/attribute_add.py +27 -2
  30. infrahub/core/migrations/schema/attribute_kind_update.py +156 -0
  31. infrahub/core/migrations/schema/tasks.py +6 -5
  32. infrahub/core/models.py +5 -1
  33. infrahub/core/node/proposed_change.py +43 -0
  34. infrahub/core/protocols.py +12 -0
  35. infrahub/core/query/attribute.py +32 -14
  36. infrahub/core/query/diff.py +11 -0
  37. infrahub/core/query/ipam.py +13 -7
  38. infrahub/core/query/node.py +51 -10
  39. infrahub/core/query/resource_manager.py +3 -3
  40. infrahub/core/schema/basenode_schema.py +8 -0
  41. infrahub/core/schema/definitions/core/__init__.py +10 -1
  42. infrahub/core/schema/definitions/core/ipam.py +28 -2
  43. infrahub/core/schema/definitions/core/propose_change.py +15 -0
  44. infrahub/core/schema/definitions/core/webhook.py +3 -0
  45. infrahub/core/schema/definitions/internal.py +1 -1
  46. infrahub/core/schema/generated/attribute_schema.py +1 -1
  47. infrahub/core/schema/generic_schema.py +10 -0
  48. infrahub/core/schema/manager.py +10 -1
  49. infrahub/core/schema/node_schema.py +22 -22
  50. infrahub/core/schema/profile_schema.py +8 -0
  51. infrahub/core/schema/schema_branch.py +11 -7
  52. infrahub/core/schema/template_schema.py +8 -0
  53. infrahub/core/validators/attribute/kind.py +5 -1
  54. infrahub/core/validators/checks_runner.py +5 -5
  55. infrahub/core/validators/determiner.py +22 -2
  56. infrahub/core/validators/tasks.py +6 -7
  57. infrahub/core/validators/uniqueness/checker.py +4 -2
  58. infrahub/core/validators/uniqueness/model.py +1 -0
  59. infrahub/core/validators/uniqueness/query.py +57 -7
  60. infrahub/database/__init__.py +2 -1
  61. infrahub/events/__init__.py +20 -0
  62. infrahub/events/constants.py +7 -0
  63. infrahub/events/generator.py +29 -2
  64. infrahub/events/proposed_change_action.py +203 -0
  65. infrahub/generators/tasks.py +24 -20
  66. infrahub/git/base.py +4 -7
  67. infrahub/git/integrator.py +21 -12
  68. infrahub/git/repository.py +15 -30
  69. infrahub/git/tasks.py +121 -106
  70. infrahub/graphql/app.py +2 -1
  71. infrahub/graphql/field_extractor.py +69 -0
  72. infrahub/graphql/manager.py +15 -11
  73. infrahub/graphql/mutations/account.py +2 -2
  74. infrahub/graphql/mutations/action.py +8 -2
  75. infrahub/graphql/mutations/artifact_definition.py +4 -1
  76. infrahub/graphql/mutations/branch.py +10 -5
  77. infrahub/graphql/mutations/graphql_query.py +2 -1
  78. infrahub/graphql/mutations/main.py +14 -8
  79. infrahub/graphql/mutations/menu.py +2 -1
  80. infrahub/graphql/mutations/proposed_change.py +230 -8
  81. infrahub/graphql/mutations/relationship.py +5 -0
  82. infrahub/graphql/mutations/repository.py +2 -1
  83. infrahub/graphql/mutations/tasks.py +7 -9
  84. infrahub/graphql/mutations/webhook.py +4 -1
  85. infrahub/graphql/parser.py +15 -6
  86. infrahub/graphql/queries/__init__.py +10 -1
  87. infrahub/graphql/queries/account.py +3 -3
  88. infrahub/graphql/queries/branch.py +2 -2
  89. infrahub/graphql/queries/diff/tree.py +56 -5
  90. infrahub/graphql/queries/event.py +13 -3
  91. infrahub/graphql/queries/ipam.py +23 -1
  92. infrahub/graphql/queries/proposed_change.py +84 -0
  93. infrahub/graphql/queries/relationship.py +2 -2
  94. infrahub/graphql/queries/resource_manager.py +3 -3
  95. infrahub/graphql/queries/search.py +3 -2
  96. infrahub/graphql/queries/status.py +3 -2
  97. infrahub/graphql/queries/task.py +2 -2
  98. infrahub/graphql/resolvers/ipam.py +440 -0
  99. infrahub/graphql/resolvers/many_relationship.py +4 -3
  100. infrahub/graphql/resolvers/resolver.py +5 -5
  101. infrahub/graphql/resolvers/single_relationship.py +3 -2
  102. infrahub/graphql/schema.py +25 -5
  103. infrahub/graphql/types/__init__.py +2 -2
  104. infrahub/graphql/types/attribute.py +3 -3
  105. infrahub/graphql/types/event.py +68 -0
  106. infrahub/groups/tasks.py +6 -6
  107. infrahub/lock.py +3 -2
  108. infrahub/menu/generator.py +8 -0
  109. infrahub/message_bus/operations/__init__.py +9 -12
  110. infrahub/message_bus/operations/git/file.py +6 -5
  111. infrahub/message_bus/operations/git/repository.py +12 -20
  112. infrahub/message_bus/operations/refresh/registry.py +15 -9
  113. infrahub/message_bus/operations/send/echo.py +7 -4
  114. infrahub/message_bus/types.py +1 -0
  115. infrahub/permissions/__init__.py +2 -1
  116. infrahub/permissions/constants.py +13 -0
  117. infrahub/permissions/globals.py +31 -2
  118. infrahub/permissions/manager.py +8 -5
  119. infrahub/pools/prefix.py +7 -5
  120. infrahub/prefect_server/app.py +31 -0
  121. infrahub/prefect_server/bootstrap.py +18 -0
  122. infrahub/proposed_change/action_checker.py +206 -0
  123. infrahub/proposed_change/approval_revoker.py +40 -0
  124. infrahub/proposed_change/branch_diff.py +3 -1
  125. infrahub/proposed_change/checker.py +45 -0
  126. infrahub/proposed_change/constants.py +32 -2
  127. infrahub/proposed_change/tasks.py +182 -150
  128. infrahub/py.typed +0 -0
  129. infrahub/server.py +29 -17
  130. infrahub/services/__init__.py +13 -28
  131. infrahub/services/adapters/cache/__init__.py +4 -0
  132. infrahub/services/adapters/cache/nats.py +2 -0
  133. infrahub/services/adapters/cache/redis.py +3 -0
  134. infrahub/services/adapters/message_bus/__init__.py +0 -2
  135. infrahub/services/adapters/message_bus/local.py +1 -2
  136. infrahub/services/adapters/message_bus/nats.py +6 -8
  137. infrahub/services/adapters/message_bus/rabbitmq.py +7 -9
  138. infrahub/services/adapters/workflow/__init__.py +1 -0
  139. infrahub/services/adapters/workflow/local.py +1 -8
  140. infrahub/services/component.py +2 -1
  141. infrahub/task_manager/event.py +56 -0
  142. infrahub/task_manager/models.py +9 -0
  143. infrahub/tasks/artifact.py +6 -7
  144. infrahub/tasks/check.py +4 -7
  145. infrahub/telemetry/tasks.py +15 -18
  146. infrahub/transformations/tasks.py +10 -6
  147. infrahub/trigger/tasks.py +4 -3
  148. infrahub/types.py +4 -0
  149. infrahub/validators/events.py +7 -7
  150. infrahub/validators/tasks.py +6 -7
  151. infrahub/webhook/models.py +45 -45
  152. infrahub/webhook/tasks.py +25 -24
  153. infrahub/workers/dependencies.py +143 -0
  154. infrahub/workers/infrahub_async.py +19 -43
  155. infrahub/workflows/catalogue.py +16 -2
  156. infrahub/workflows/initialization.py +5 -4
  157. infrahub/workflows/models.py +2 -0
  158. infrahub_sdk/client.py +2 -2
  159. infrahub_sdk/ctl/repository.py +51 -0
  160. infrahub_sdk/ctl/schema.py +9 -9
  161. infrahub_sdk/node/node.py +2 -2
  162. infrahub_sdk/pytest_plugin/items/graphql_query.py +1 -1
  163. infrahub_sdk/schema/repository.py +1 -1
  164. infrahub_sdk/testing/docker.py +1 -1
  165. infrahub_sdk/utils.py +2 -2
  166. {infrahub_server-1.3.7.dist-info → infrahub_server-1.4.0.dist-info}/METADATA +7 -5
  167. {infrahub_server-1.3.7.dist-info → infrahub_server-1.4.0.dist-info}/RECORD +174 -158
  168. infrahub_testcontainers/container.py +17 -0
  169. infrahub_testcontainers/docker-compose-cluster.test.yml +56 -1
  170. infrahub_testcontainers/docker-compose.test.yml +56 -1
  171. infrahub_testcontainers/helpers.py +4 -1
  172. {infrahub_server-1.3.7.dist-info → infrahub_server-1.4.0.dist-info}/LICENSE.txt +0 -0
  173. {infrahub_server-1.3.7.dist-info → infrahub_server-1.4.0.dist-info}/WHEEL +0 -0
  174. {infrahub_server-1.3.7.dist-info → infrahub_server-1.4.0.dist-info}/entry_points.txt +0 -0
@@ -3,7 +3,9 @@ from __future__ import annotations
3
3
  from typing import TYPE_CHECKING, Any
4
4
 
5
5
  from infrahub.core.constants.relationship_label import RELATIONSHIP_TO_VALUE_LABEL
6
+ from infrahub.core.graph.schema import GraphAttributeValueIndexedNode, GraphAttributeValueNode
6
7
  from infrahub.core.query import Query, QueryType
8
+ from infrahub.types import is_large_attribute_type
7
9
 
8
10
  from .model import QueryAttributePathValued, QueryRelationshipPathValued
9
11
 
@@ -40,6 +42,7 @@ class NodeUniqueAttributeConstraintQuery(Query):
40
42
  items="relationships(active_path)", item_names=["branch", "branch_level"]
41
43
  )
42
44
 
45
+ attrs_include_large_type = False
43
46
  attribute_names = set()
44
47
  attr_paths, attr_paths_with_value, attr_values = [], [], []
45
48
  for attr_path in self.query_request.unique_attribute_paths:
@@ -49,12 +52,19 @@ class NodeUniqueAttributeConstraintQuery(Query):
49
52
  raise ValueError(
50
53
  f"{attr_path.property_name} is not a valid property for a uniqueness constraint"
51
54
  ) from exc
55
+ if is_large_attribute_type(attr_path.attribute_kind):
56
+ attrs_include_large_type = True
52
57
  attribute_names.add(attr_path.attribute_name)
53
58
  if attr_path.value:
54
59
  attr_paths_with_value.append((attr_path.attribute_name, property_rel_name, attr_path.value))
55
60
  attr_values.append(attr_path.value)
56
61
  else:
57
62
  attr_paths.append((attr_path.attribute_name, property_rel_name))
63
+ attr_value_label = (
64
+ GraphAttributeValueNode.get_default_label()
65
+ if attrs_include_large_type
66
+ else GraphAttributeValueIndexedNode.get_default_label()
67
+ )
58
68
 
59
69
  relationship_names = set()
60
70
  relationship_attr_paths = []
@@ -112,11 +122,11 @@ class NodeUniqueAttributeConstraintQuery(Query):
112
122
  """ % {"node_kind": self.query_request.kind}
113
123
 
114
124
  attr_paths_with_value_subquery = """
115
- MATCH attr_path = (start_node:%(node_kind)s)-[:HAS_ATTRIBUTE]->(attr:Attribute)-[r:HAS_VALUE]->(attr_value:AttributeValue)
125
+ MATCH attr_path = (start_node:%(node_kind)s)-[:HAS_ATTRIBUTE]->(attr:Attribute)-[r:HAS_VALUE]->(attr_value:%(attr_value_label)s)
116
126
  WHERE attr.name in $attribute_names AND attr_value.value in $attr_values
117
127
  AND [attr.name, type(r), attr_value.value] in $attr_paths_with_value
118
128
  RETURN start_node, attr_path as potential_path, NULL as rel_identifier, attr.name as potential_attr, attr_value.value as potential_attr_value
119
- """ % {"node_kind": self.query_request.kind}
129
+ """ % {"node_kind": self.query_request.kind, "attr_value_label": attr_value_label}
120
130
 
121
131
  relationship_attr_paths_subquery = """
122
132
  MATCH rel_path = (start_node:%(node_kind)s)-[:IS_RELATED]-(relationship_node:Relationship)-[:IS_RELATED]-(related_n:Node)-[:HAS_ATTRIBUTE]->(rel_attr:Attribute)-[:HAS_VALUE]->(rel_attr_value:AttributeValue)
@@ -262,8 +272,20 @@ class UniquenessValidationQuery(Query):
262
272
  self.node_ids_to_exclude = node_ids_to_exclude
263
273
  super().__init__(**kwargs)
264
274
 
275
+ def _is_attribute_large_type(self, db: InfrahubDatabase, node_kind: str, attribute_name: str) -> bool:
276
+ """Determine if an attribute is a large type that should use AttributeValue instead of AttributeValueIndexed."""
277
+ node_schema = db.schema.get(node_kind, branch=self.branch, duplicate=False)
278
+ attr_schema = node_schema.get_attribute(attribute_name)
279
+ return is_large_attribute_type(attr_schema.kind)
280
+
265
281
  def _build_attr_subquery(
266
- self, node_kind: str, attr_path: QueryAttributePathValued, index: int, branch_filter: str, is_first_query: bool
282
+ self,
283
+ node_kind: str,
284
+ attr_path: QueryAttributePathValued,
285
+ index: int,
286
+ branch_filter: str,
287
+ is_first_query: bool,
288
+ is_large_type: bool,
267
289
  ) -> tuple[str, dict[str, str | int | float | bool]]:
268
290
  attr_name_var = f"attr_name_{index}"
269
291
  attr_value_var = f"attr_value_{index}"
@@ -271,8 +293,16 @@ class UniquenessValidationQuery(Query):
271
293
  first_query_filter = "WHERE $node_ids_to_exclude IS NULL OR NOT node.uuid IN $node_ids_to_exclude"
272
294
  else:
273
295
  first_query_filter = ""
296
+
297
+ # Determine the appropriate label based on attribute type
298
+ attr_value_label = (
299
+ GraphAttributeValueNode.get_default_label()
300
+ if is_large_type
301
+ else GraphAttributeValueIndexedNode.get_default_label()
302
+ )
303
+
274
304
  attribute_query = """
275
- MATCH (node:%(node_kind)s)-[:HAS_ATTRIBUTE]->(attr:Attribute {name: $%(attr_name_var)s})-[:HAS_VALUE]->(:AttributeValue {value: $%(attr_value_var)s})
305
+ MATCH (node:%(node_kind)s)-[:HAS_ATTRIBUTE]->(attr:Attribute {name: $%(attr_name_var)s})-[:HAS_VALUE]->(:%(attr_value_label)s {value: $%(attr_value_var)s})
276
306
  %(first_query_filter)s
277
307
  WITH DISTINCT node
278
308
  CALL (node) {
@@ -300,6 +330,7 @@ CALL (node) {
300
330
  "attr_value_var": attr_value_var,
301
331
  "branch_filter": branch_filter,
302
332
  "index": index,
333
+ "attr_value_label": attr_value_label,
303
334
  }
304
335
  params: dict[str, str | int | float | bool] = {
305
336
  attr_name_var: attr_path.attribute_name,
@@ -314,6 +345,7 @@ CALL (node) {
314
345
  index: int,
315
346
  branch_filter: str,
316
347
  is_first_query: bool,
348
+ is_large_type: bool = False,
317
349
  ) -> tuple[str, dict[str, str | int | float | bool]]:
318
350
  params: dict[str, str | int | float | bool] = {}
319
351
  rel_attr_query = ""
@@ -321,6 +353,14 @@ CALL (node) {
321
353
  if rel_path.attribute_name and rel_path.attribute_value:
322
354
  attr_name_var = f"attr_name_{index}"
323
355
  attr_value_var = f"attr_value_{index}"
356
+
357
+ # Determine the appropriate label based on relationship attribute type
358
+ rel_attr_value_label = (
359
+ GraphAttributeValueNode.get_default_label()
360
+ if is_large_type
361
+ else GraphAttributeValueIndexedNode.get_default_label()
362
+ )
363
+
324
364
  rel_attr_query = """
325
365
  MATCH (peer)-[r:HAS_ATTRIBUTE]->(attr:Attribute {name: $%(attr_name_var)s})
326
366
  WHERE %(branch_filter)s
@@ -330,19 +370,25 @@ CALL (node) {
330
370
  LIMIT 1
331
371
  WITH attr, is_active
332
372
  WHERE is_active = TRUE
333
- MATCH (attr)-[r:HAS_VALUE]->(:AttributeValue {value: $%(attr_value_var)s})
373
+ MATCH (attr)-[r:HAS_VALUE]->(:%(rel_attr_value_label)s {value: $%(attr_value_var)s})
334
374
  WHERE %(branch_filter)s
335
375
  WITH r
336
376
  ORDER BY r.branch_level DESC, r.from DESC, r.status ASC
337
377
  LIMIT 1
338
378
  WITH r
339
379
  WHERE r.status = "active"
340
- """ % {"attr_name_var": attr_name_var, "attr_value_var": attr_value_var, "branch_filter": branch_filter}
380
+ """ % {
381
+ "attr_name_var": attr_name_var,
382
+ "attr_value_var": attr_value_var,
383
+ "branch_filter": branch_filter,
384
+ "rel_attr_value_label": rel_attr_value_label,
385
+ }
341
386
  rel_attr_match = (
342
- "-[r:HAS_ATTRIBUTE]->(attr:Attribute {name: $%(attr_name_var)s})-[:HAS_VALUE]->(:AttributeValue {value: $%(attr_value_var)s})"
387
+ "-[r:HAS_ATTRIBUTE]->(attr:Attribute {name: $%(attr_name_var)s})-[:HAS_VALUE]->(:%(rel_attr_value_label)s {value: $%(attr_value_var)s})"
343
388
  % {
344
389
  "attr_name_var": attr_name_var,
345
390
  "attr_value_var": attr_value_var,
391
+ "rel_attr_value_label": rel_attr_value_label,
346
392
  }
347
393
  )
348
394
  params[attr_name_var] = rel_path.attribute_name
@@ -426,12 +472,16 @@ CALL (node) {
426
472
  for index, schema_path in enumerate(self.query_request.unique_valued_paths):
427
473
  is_first_query = index == 0
428
474
  if isinstance(schema_path, QueryAttributePathValued):
475
+ is_large_type = self._is_attribute_large_type(
476
+ db=db, node_kind=self.query_request.kind, attribute_name=schema_path.attribute_name
477
+ )
429
478
  subquery, params = self._build_attr_subquery(
430
479
  node_kind=self.query_request.kind,
431
480
  attr_path=schema_path,
432
481
  index=index,
433
482
  branch_filter=branch_filter,
434
483
  is_first_query=is_first_query,
484
+ is_large_type=is_large_type,
435
485
  )
436
486
  else:
437
487
  subquery, params = self._build_rel_subquery(
@@ -295,7 +295,8 @@ class InfrahubDatabase:
295
295
  traceback: TracebackType | None,
296
296
  ) -> None:
297
297
  if self._mode == InfrahubDatabaseMode.SESSION:
298
- return await self._session.close()
298
+ await self._session.close()
299
+ return
299
300
 
300
301
  if self._mode == InfrahubDatabaseMode.TRANSACTION:
301
302
  if exc_type is not None:
@@ -3,6 +3,17 @@ from .branch_action import BranchCreatedEvent, BranchDeletedEvent, BranchMergedE
3
3
  from .group_action import GroupMemberAddedEvent, GroupMemberRemovedEvent
4
4
  from .models import EventMeta, InfrahubEvent
5
5
  from .node_action import NodeCreatedEvent, NodeDeletedEvent, NodeUpdatedEvent
6
+ from .proposed_change_action import (
7
+ ProposedChangeApprovalRevokedEvent,
8
+ ProposedChangeApprovalsRevokedEvent,
9
+ ProposedChangeApprovedEvent,
10
+ ProposedChangeMergedEvent,
11
+ ProposedChangeRejectedEvent,
12
+ ProposedChangeRejectionRevokedEvent,
13
+ ProposedChangeReviewRequestedEvent,
14
+ ProposedChangeThreadCreatedEvent,
15
+ ProposedChangeThreadUpdatedEvent,
16
+ )
6
17
  from .repository_action import CommitUpdatedEvent
7
18
  from .validator_action import ValidatorFailedEvent, ValidatorPassedEvent, ValidatorStartedEvent
8
19
 
@@ -21,6 +32,15 @@ __all__ = [
21
32
  "NodeCreatedEvent",
22
33
  "NodeDeletedEvent",
23
34
  "NodeUpdatedEvent",
35
+ "ProposedChangeApprovalRevokedEvent",
36
+ "ProposedChangeApprovalsRevokedEvent",
37
+ "ProposedChangeApprovedEvent",
38
+ "ProposedChangeMergedEvent",
39
+ "ProposedChangeRejectedEvent",
40
+ "ProposedChangeRejectionRevokedEvent",
41
+ "ProposedChangeReviewRequestedEvent",
42
+ "ProposedChangeThreadCreatedEvent",
43
+ "ProposedChangeThreadUpdatedEvent",
24
44
  "ValidatorFailedEvent",
25
45
  "ValidatorPassedEvent",
26
46
  "ValidatorStartedEvent",
@@ -1 +1,8 @@
1
+ from enum import Enum
2
+
1
3
  EVENT_NAMESPACE = "infrahub"
4
+
5
+
6
+ class EventSortOrder(str, Enum):
7
+ ASC = "asc"
8
+ DESC = "desc"
@@ -1,14 +1,16 @@
1
1
  from infrahub.context import InfrahubContext
2
2
  from infrahub.core.branch import Branch
3
3
  from infrahub.core.changelog.models import RelationshipChangelogGetter
4
- from infrahub.core.constants import MutationAction
4
+ from infrahub.core.constants import InfrahubKind, MutationAction
5
5
  from infrahub.core.node import Node
6
+ from infrahub.core.protocols import CoreProposedChange
6
7
  from infrahub.database import InfrahubDatabase
7
8
  from infrahub.events.node_action import NodeDeletedEvent, NodeMutatedEvent, NodeUpdatedEvent, get_node_event
8
9
  from infrahub.groups.parsers import GroupNodeMutationParser
9
10
  from infrahub.worker import WORKER_IDENTITY
10
11
 
11
12
  from .models import EventMeta, InfrahubEvent
13
+ from .proposed_change_action import ProposedChangeThreadCreatedEvent, ProposedChangeThreadUpdatedEvent
12
14
 
13
15
 
14
16
  async def generate_node_mutation_events(
@@ -68,4 +70,29 @@ async def generate_node_mutation_events(
68
70
 
69
71
  group_parser = GroupNodeMutationParser(db=db, branch=branch)
70
72
  group_events = await group_parser.group_events_from_node_actions(events=events)
71
- return events + group_events
73
+
74
+ specific_events: list[InfrahubEvent] = []
75
+ if (kind := node.get_kind()) in [
76
+ InfrahubKind.CHANGETHREAD,
77
+ InfrahubKind.OBJECTTHREAD,
78
+ InfrahubKind.ARTIFACTTHREAD,
79
+ InfrahubKind.FILETHREAD,
80
+ ]:
81
+ proposed_change: CoreProposedChange = await node.change.get_peer(db=db, peer_type=CoreProposedChange) # type: ignore[attr-defined]
82
+ action_to_event_map = {
83
+ MutationAction.CREATED: ProposedChangeThreadCreatedEvent,
84
+ MutationAction.UPDATED: ProposedChangeThreadUpdatedEvent,
85
+ }
86
+ if action in action_to_event_map:
87
+ specific_events.append(
88
+ action_to_event_map[action](
89
+ proposed_change_id=proposed_change.id,
90
+ proposed_change_name=proposed_change.name.value,
91
+ proposed_change_state=proposed_change.state.value,
92
+ thread_id=node.id,
93
+ thread_kind=kind,
94
+ meta=EventMeta.from_context(context=context),
95
+ )
96
+ )
97
+
98
+ return events + group_events + specific_events
@@ -0,0 +1,203 @@
1
+ from typing import ClassVar
2
+
3
+ from pydantic import Field
4
+
5
+ from infrahub.core.constants import InfrahubKind, MutationAction
6
+
7
+ from .constants import EVENT_NAMESPACE
8
+ from .models import InfrahubEvent
9
+
10
+
11
+ class ProposedChangeEvent(InfrahubEvent):
12
+ proposed_change_id: str = Field(..., description="The ID of the proposed change")
13
+ proposed_change_name: str = Field(..., description="The name of the proposed change")
14
+ proposed_change_state: str = Field(..., description="The state of the proposed change")
15
+
16
+ def get_resource(self) -> dict[str, str]:
17
+ return {
18
+ "prefect.resource.id": f"infrahub.proposed_change.{self.proposed_change_id}",
19
+ "infrahub.node.kind": InfrahubKind.PROPOSEDCHANGE,
20
+ "infrahub.node.id": self.proposed_change_id,
21
+ "infrahub.proposed_change.name": self.proposed_change_name,
22
+ "infrahub.proposed_change.state": self.proposed_change_state,
23
+ "infrahub.branch.name": self.meta.context.branch.name,
24
+ }
25
+
26
+
27
+ class ProposedChangeReviewEvent(ProposedChangeEvent):
28
+ reviewer_account_id: str = Field(..., description="The ID of the user who reviewed the proposed change")
29
+ reviewer_account_name: str = Field(..., description="The name of the user who reviewed the proposed change")
30
+ reviewer_decision: str = Field(..., description="The decision made by the reviewer")
31
+
32
+ def get_related(self) -> list[dict[str, str]]:
33
+ related = super().get_related()
34
+ related.append(
35
+ {
36
+ "prefect.resource.id": self.reviewer_account_id,
37
+ "prefect.resource.role": "infrahub.related.node",
38
+ "infrahub.node.kind": InfrahubKind.GENERICACCOUNT,
39
+ "infrahub.node.id": self.reviewer_account_id,
40
+ "infrahub.reviewer.account.name": self.reviewer_account_name,
41
+ }
42
+ )
43
+ return related
44
+
45
+ def get_resource(self) -> dict[str, str]:
46
+ return {**super().get_resource(), "infrahub.proposed_change.reviewer_decision": self.reviewer_decision}
47
+
48
+
49
+ class ProposedChangeReviewRevokedEvent(ProposedChangeEvent):
50
+ reviewer_account_id: str = Field(..., description="The ID of the user who reviewed the proposed change")
51
+ reviewer_account_name: str = Field(..., description="The name of the user who reviewed the proposed change")
52
+ reviewer_former_decision: str = Field(..., description="The former decision made by the reviewer")
53
+
54
+ def get_related(self) -> list[dict[str, str]]:
55
+ related = super().get_related()
56
+ related.append(
57
+ {
58
+ "prefect.resource.id": self.reviewer_account_id,
59
+ "prefect.resource.role": "infrahub.related.node",
60
+ "infrahub.node.kind": InfrahubKind.GENERICACCOUNT,
61
+ "infrahub.node.id": self.reviewer_account_id,
62
+ "infrahub.reviewer.account.name": self.reviewer_account_name,
63
+ }
64
+ )
65
+ return related
66
+
67
+ def get_resource(self) -> dict[str, str]:
68
+ return {
69
+ **super().get_resource(),
70
+ "infrahub.proposed_change.reviewer_former_decision": self.reviewer_former_decision,
71
+ }
72
+
73
+
74
+ class ProposedChangeMergedEvent(ProposedChangeEvent):
75
+ """Event generated when a proposed change has been merged"""
76
+
77
+ event_name: ClassVar[str] = f"{EVENT_NAMESPACE}.proposed_change.merged"
78
+
79
+ merged_by_account_id: str = Field(..., description="The ID of the user who merged the proposed change")
80
+ merged_by_account_name: str = Field(..., description="The name of the user who merged the proposed change")
81
+
82
+ def get_related(self) -> list[dict[str, str]]:
83
+ related = super().get_related()
84
+ related.append(
85
+ {
86
+ "prefect.resource.id": self.merged_by_account_id,
87
+ "prefect.resource.role": "infrahub.related.node",
88
+ "infrahub.node.kind": InfrahubKind.GENERICACCOUNT,
89
+ "infrahub.node.id": self.merged_by_account_id,
90
+ "infrahub.merged_by.account.name": self.merged_by_account_name,
91
+ }
92
+ )
93
+ return related
94
+
95
+ def get_resource(self) -> dict[str, str]:
96
+ return {
97
+ **super().get_resource(),
98
+ "infrahub.proposed_change.merged_by_account_id": self.merged_by_account_id,
99
+ "infrahub.proposed_change.merged_by_account_name": self.merged_by_account_name,
100
+ }
101
+
102
+
103
+ class ProposedChangeReviewRequestedEvent(ProposedChangeEvent):
104
+ """Event generated when a proposed change has been flagged for review"""
105
+
106
+ event_name: ClassVar[str] = f"{EVENT_NAMESPACE}.proposed_change.review_requested"
107
+
108
+ requested_by_account_id: str = Field(
109
+ ..., description="The ID of the user who requested the proposed change to be reviewed"
110
+ )
111
+ requested_by_account_name: str = Field(
112
+ ..., description="The name of the user who requested the proposed change to be reviewed"
113
+ )
114
+
115
+ def get_related(self) -> list[dict[str, str]]:
116
+ related = super().get_related()
117
+ related.append(
118
+ {
119
+ "prefect.resource.id": self.requested_by_account_id,
120
+ "prefect.resource.role": "infrahub.related.node",
121
+ "infrahub.node.kind": InfrahubKind.GENERICACCOUNT,
122
+ "infrahub.node.id": self.requested_by_account_id,
123
+ "infrahub.requested_by.account.name": self.requested_by_account_name,
124
+ }
125
+ )
126
+ return related
127
+
128
+
129
+ class ProposedChangeApprovedEvent(ProposedChangeReviewEvent):
130
+ """Event generated when a proposed change has been approved"""
131
+
132
+ event_name: ClassVar[str] = f"{EVENT_NAMESPACE}.proposed_change.approved"
133
+
134
+
135
+ class ProposedChangeRejectedEvent(ProposedChangeReviewEvent):
136
+ """Event generated when a proposed change has been rejected"""
137
+
138
+ event_name: ClassVar[str] = f"{EVENT_NAMESPACE}.proposed_change.rejected"
139
+
140
+
141
+ class ProposedChangeApprovalRevokedEvent(ProposedChangeReviewRevokedEvent):
142
+ """Event generated when a proposed change approval has been revoked"""
143
+
144
+ event_name: ClassVar[str] = f"{EVENT_NAMESPACE}.proposed_change.approval_revoked"
145
+
146
+
147
+ class ProposedChangeRejectionRevokedEvent(ProposedChangeReviewRevokedEvent):
148
+ """Event generated when a proposed change rejection has been revoked"""
149
+
150
+ event_name: ClassVar[str] = f"{EVENT_NAMESPACE}.proposed_change.rejection_revoked"
151
+
152
+
153
+ class ProposedChangeApprovalsRevokedEvent(ProposedChangeEvent):
154
+ reviewer_accounts: dict[str, str] = Field(
155
+ default_factory=dict, description="ID to name map of accounts whose approval was revoked"
156
+ )
157
+
158
+ event_name: ClassVar[str] = f"{EVENT_NAMESPACE}.proposed_change.approvals_revoked"
159
+
160
+ def get_related(self) -> list[dict[str, str]]:
161
+ related = super().get_related()
162
+ for account_id, account_name in self.reviewer_accounts.items():
163
+ related.append(
164
+ {
165
+ "prefect.resource.id": account_id,
166
+ "prefect.resource.role": "infrahub.related.node",
167
+ "infrahub.node.kind": InfrahubKind.GENERICACCOUNT,
168
+ "infrahub.node.id": account_id,
169
+ "infrahub.reviewer.account.name": account_name,
170
+ }
171
+ )
172
+ return related
173
+
174
+
175
+ class ProposedChangeThreadEvent(ProposedChangeEvent):
176
+ thread_id: str = Field(..., description="The ID of the thread that was created or updated")
177
+ thread_kind: str = Field(..., description="The name of the thread that was created or updated")
178
+
179
+ def get_related(self) -> list[dict[str, str]]:
180
+ related = super().get_related()
181
+ related.append(
182
+ {
183
+ "prefect.resource.id": self.thread_id,
184
+ "prefect.resource.role": "infrahub.related.node",
185
+ "infrahub.node.kind": self.thread_kind,
186
+ "infrahub.node.id": self.thread_id,
187
+ }
188
+ )
189
+ return related
190
+
191
+
192
+ class ProposedChangeThreadCreatedEvent(ProposedChangeThreadEvent):
193
+ """Event generated when a thread has been created in a proposed change"""
194
+
195
+ event_name: ClassVar[str] = f"{EVENT_NAMESPACE}.proposed_change_thread.created"
196
+ action: MutationAction = MutationAction.CREATED
197
+
198
+
199
+ class ProposedChangeThreadUpdatedEvent(ProposedChangeThreadEvent):
200
+ """Event generated when a thread has been updated in a proposed change"""
201
+
202
+ event_name: ClassVar[str] = f"{EVENT_NAMESPACE}.proposed_change_thread.updated"
203
+ action: MutationAction = MutationAction.UPDATED
@@ -21,25 +21,29 @@ from infrahub.generators.models import (
21
21
  )
22
22
  from infrahub.git.base import extract_repo_file_information
23
23
  from infrahub.git.repository import get_initialized_repo
24
- from infrahub.services import InfrahubServices # noqa: TC001 needed for prefect flow
24
+ from infrahub.workers.dependencies import get_client, get_workflow
25
25
  from infrahub.workflows.catalogue import REQUEST_GENERATOR_DEFINITION_RUN, REQUEST_GENERATOR_RUN
26
26
  from infrahub.workflows.utils import add_tags
27
27
 
28
28
  if TYPE_CHECKING:
29
29
  from collections.abc import Coroutine
30
30
 
31
+ from infrahub_sdk.client import InfrahubClient
32
+
31
33
 
32
34
  @flow(
33
35
  name="generator-run",
34
36
  flow_run_name="Run generator {model.generator_definition.definition_name}",
35
37
  )
36
- async def run_generator(model: RequestGeneratorRun, service: InfrahubServices) -> None:
38
+ async def run_generator(model: RequestGeneratorRun) -> None:
37
39
  await add_tags(branches=[model.branch_name], nodes=[model.target_id])
38
40
 
41
+ client = get_client()
42
+
39
43
  repository = await get_initialized_repo(
44
+ client=client,
40
45
  repository_id=model.repository_id,
41
46
  name=model.repository_name,
42
- service=service,
43
47
  repository_kind=model.repository_kind,
44
48
  commit=model.commit,
45
49
  )
@@ -60,7 +64,7 @@ async def run_generator(model: RequestGeneratorRun, service: InfrahubServices) -
60
64
  repo_directory=repository.directory_root,
61
65
  worktree_directory=commit_worktree.directory,
62
66
  )
63
- generator_instance = await _define_instance(model=model, service=service)
67
+ generator_instance = await _define_instance(model=model, client=client)
64
68
 
65
69
  try:
66
70
  generator_class = generator_definition.load_class(
@@ -69,7 +73,7 @@ async def run_generator(model: RequestGeneratorRun, service: InfrahubServices) -
69
73
 
70
74
  generator = generator_class(
71
75
  query=generator_definition.query,
72
- client=service.client,
76
+ client=client,
73
77
  branch=model.branch_name,
74
78
  params=model.variables,
75
79
  generator_instance=generator_instance.id,
@@ -87,11 +91,9 @@ async def run_generator(model: RequestGeneratorRun, service: InfrahubServices) -
87
91
 
88
92
 
89
93
  @task(name="generator-define-instance", task_run_name="Define Instance", cache_policy=NONE) # type: ignore[arg-type]
90
- async def _define_instance(model: RequestGeneratorRun, service: InfrahubServices) -> CoreGeneratorInstance:
94
+ async def _define_instance(model: RequestGeneratorRun, client: InfrahubClient) -> CoreGeneratorInstance:
91
95
  if model.generator_instance:
92
- instance = await service.client.get(
93
- kind=CoreGeneratorInstance, id=model.generator_instance, branch=model.branch_name
94
- )
96
+ instance = await client.get(kind=CoreGeneratorInstance, id=model.generator_instance, branch=model.branch_name)
95
97
  instance.status.value = GeneratorInstanceStatus.PENDING.value
96
98
  await instance.update(do_full_update=True)
97
99
 
@@ -99,7 +101,7 @@ async def _define_instance(model: RequestGeneratorRun, service: InfrahubServices
99
101
  async with lock.registry.get(
100
102
  f"{model.target_id}-{model.generator_definition.definition_id}", namespace="generator"
101
103
  ):
102
- instances = await service.client.filters(
104
+ instances = await client.filters(
103
105
  kind=CoreGeneratorInstance,
104
106
  definition__ids=[model.generator_definition.definition_id],
105
107
  object__ids=[model.target_id],
@@ -110,7 +112,7 @@ async def _define_instance(model: RequestGeneratorRun, service: InfrahubServices
110
112
  instance.status.value = GeneratorInstanceStatus.PENDING.value
111
113
  await instance.update(do_full_update=True)
112
114
  else:
113
- instance = await service.client.create(
115
+ instance = await client.create(
114
116
  kind=CoreGeneratorInstance,
115
117
  branch=model.branch_name,
116
118
  data={
@@ -125,10 +127,10 @@ async def _define_instance(model: RequestGeneratorRun, service: InfrahubServices
125
127
 
126
128
 
127
129
  @flow(name="generator-definition-run", flow_run_name="Run all generators")
128
- async def run_generator_definition(branch: str, context: InfrahubContext, service: InfrahubServices) -> None:
130
+ async def run_generator_definition(branch: str, context: InfrahubContext) -> None:
129
131
  await add_tags(branches=[branch])
130
132
 
131
- generators = await service.client.filters(
133
+ generators = await get_client().filters(
132
134
  kind=InfrahubKind.GENERATORDEFINITION, prefetch_relationships=True, populate_store=True, branch=branch
133
135
  )
134
136
 
@@ -150,7 +152,7 @@ async def run_generator_definition(branch: str, context: InfrahubContext, servic
150
152
 
151
153
  for generator_definition in generator_definitions:
152
154
  model = RequestGeneratorDefinitionRun(branch=branch, generator_definition=generator_definition)
153
- await service.workflow.submit_workflow(
155
+ await get_workflow().submit_workflow(
154
156
  workflow=REQUEST_GENERATOR_DEFINITION_RUN, context=context, parameters={"model": model}
155
157
  )
156
158
 
@@ -160,11 +162,13 @@ async def run_generator_definition(branch: str, context: InfrahubContext, servic
160
162
  flow_run_name="Execute generator {model.generator_definition.definition_name}",
161
163
  )
162
164
  async def request_generator_definition_run(
163
- model: RequestGeneratorDefinitionRun, context: InfrahubContext, service: InfrahubServices
165
+ model: RequestGeneratorDefinitionRun, context: InfrahubContext
164
166
  ) -> State[Any]:
165
167
  await add_tags(branches=[model.branch], nodes=[model.generator_definition.definition_id])
166
168
 
167
- group = await service.client.get(
169
+ client = get_client()
170
+
171
+ group = await client.get(
168
172
  kind=InfrahubKind.GENERICGROUP,
169
173
  prefetch_relationships=True,
170
174
  populate_store=True,
@@ -173,7 +177,7 @@ async def request_generator_definition_run(
173
177
  )
174
178
  await group.members.fetch()
175
179
 
176
- existing_instances = await service.client.filters(
180
+ existing_instances = await client.filters(
177
181
  kind=InfrahubKind.GENERATORINSTANCE,
178
182
  definition__ids=[model.generator_definition.definition_id],
179
183
  include=["object"],
@@ -183,14 +187,14 @@ async def request_generator_definition_run(
183
187
  for instance in existing_instances:
184
188
  instance_by_member[instance.object.peer.id] = instance.id
185
189
 
186
- repository = await service.client.get(
190
+ repository = await client.get(
187
191
  kind=InfrahubKind.REPOSITORY,
188
192
  branch=model.branch,
189
193
  id=model.generator_definition.repository_id,
190
194
  raise_when_missing=False,
191
195
  )
192
196
  if not repository:
193
- repository = await service.client.get(
197
+ repository = await client.get(
194
198
  kind=InfrahubKind.READONLYREPOSITORY,
195
199
  branch=model.branch,
196
200
  id=model.generator_definition.repository_id,
@@ -219,7 +223,7 @@ async def request_generator_definition_run(
219
223
  target_name=member.display_label,
220
224
  )
221
225
  tasks.append(
222
- service.workflow.execute_workflow(
226
+ get_workflow().execute_workflow(
223
227
  workflow=REQUEST_GENERATOR_RUN, context=context, parameters={"model": request_generator_run_model}
224
228
  )
225
229
  )
infrahub/git/base.py CHANGED
@@ -33,7 +33,7 @@ from infrahub.git.constants import BRANCHES_DIRECTORY_NAME, COMMITS_DIRECTORY_NA
33
33
  from infrahub.git.directory import get_repositories_directory, initialize_repositories_directory
34
34
  from infrahub.git.worktree import Worktree
35
35
  from infrahub.log import get_logger
36
- from infrahub.services import InfrahubServices # noqa: TC001
36
+ from infrahub.workers.dependencies import get_client
37
37
 
38
38
  if TYPE_CHECKING:
39
39
  from infrahub_sdk.branch import BranchData
@@ -153,9 +153,6 @@ class InfrahubRepositoryBase(BaseModel, ABC):
153
153
  )
154
154
 
155
155
  cache_repo: Repo | None = Field(None, description="Internal cache of the GitPython Repo object")
156
- service: InfrahubServices = Field(
157
- ..., description="Service object with access to the message queue, the database etc.."
158
- )
159
156
  is_read_only: bool = Field(False, description="If true, changes will not be synced to remote")
160
157
 
161
158
  internal_status: str = Field("active", description="Internal status: Active, Inactive, Staging")
@@ -169,10 +166,10 @@ class InfrahubRepositoryBase(BaseModel, ABC):
169
166
 
170
167
  @property
171
168
  def sdk(self) -> InfrahubClient:
172
- if self.client:
173
- return self.client
169
+ if not self.client:
170
+ self.client = get_client()
174
171
 
175
- return self.service.client
172
+ return self.client
176
173
 
177
174
  @property
178
175
  def default_branch(self) -> str: