infrahub-server 1.3.6__py3-none-any.whl → 1.4.0b1__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 (163) 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 -41
  7. infrahub/cli/upgrade.py +7 -29
  8. infrahub/computed_attribute/tasks.py +36 -46
  9. infrahub/config.py +53 -2
  10. infrahub/constants/environment.py +1 -0
  11. infrahub/core/attribute.py +9 -7
  12. infrahub/core/branch/tasks.py +43 -41
  13. infrahub/core/constants/__init__.py +20 -6
  14. infrahub/core/constants/infrahubkind.py +2 -0
  15. infrahub/core/diff/coordinator.py +3 -1
  16. infrahub/core/diff/repository/repository.py +0 -8
  17. infrahub/core/diff/tasks.py +11 -8
  18. infrahub/core/graph/__init__.py +1 -1
  19. infrahub/core/graph/index.py +1 -2
  20. infrahub/core/graph/schema.py +50 -29
  21. infrahub/core/initialization.py +62 -33
  22. infrahub/core/ipam/tasks.py +4 -3
  23. infrahub/core/manager.py +2 -2
  24. infrahub/core/merge.py +8 -10
  25. infrahub/core/migrations/graph/__init__.py +4 -0
  26. infrahub/core/migrations/graph/m035_drop_attr_value_index.py +45 -0
  27. infrahub/core/migrations/graph/m036_index_attr_vals.py +577 -0
  28. infrahub/core/migrations/query/attribute_add.py +27 -2
  29. infrahub/core/migrations/query/node_duplicate.py +3 -26
  30. infrahub/core/migrations/schema/tasks.py +6 -5
  31. infrahub/core/node/proposed_change.py +43 -0
  32. infrahub/core/protocols.py +12 -0
  33. infrahub/core/query/attribute.py +32 -14
  34. infrahub/core/query/diff.py +11 -0
  35. infrahub/core/query/ipam.py +13 -7
  36. infrahub/core/query/node.py +51 -23
  37. infrahub/core/query/resource_manager.py +3 -3
  38. infrahub/core/relationship/model.py +13 -13
  39. infrahub/core/schema/basenode_schema.py +8 -0
  40. infrahub/core/schema/definitions/core/__init__.py +10 -1
  41. infrahub/core/schema/definitions/core/ipam.py +28 -2
  42. infrahub/core/schema/definitions/core/propose_change.py +15 -0
  43. infrahub/core/schema/definitions/core/webhook.py +3 -0
  44. infrahub/core/schema/generic_schema.py +10 -0
  45. infrahub/core/schema/manager.py +10 -1
  46. infrahub/core/schema/node_schema.py +22 -17
  47. infrahub/core/schema/profile_schema.py +8 -0
  48. infrahub/core/schema/schema_branch.py +9 -5
  49. infrahub/core/schema/template_schema.py +8 -0
  50. infrahub/core/validators/checks_runner.py +5 -5
  51. infrahub/core/validators/tasks.py +6 -7
  52. infrahub/core/validators/uniqueness/checker.py +4 -2
  53. infrahub/core/validators/uniqueness/model.py +1 -0
  54. infrahub/core/validators/uniqueness/query.py +57 -7
  55. infrahub/database/__init__.py +2 -1
  56. infrahub/events/__init__.py +18 -0
  57. infrahub/events/constants.py +7 -0
  58. infrahub/events/generator.py +29 -2
  59. infrahub/events/proposed_change_action.py +181 -0
  60. infrahub/generators/tasks.py +24 -20
  61. infrahub/git/base.py +4 -7
  62. infrahub/git/integrator.py +21 -12
  63. infrahub/git/repository.py +15 -30
  64. infrahub/git/tasks.py +121 -106
  65. infrahub/graphql/field_extractor.py +69 -0
  66. infrahub/graphql/manager.py +15 -11
  67. infrahub/graphql/mutations/account.py +2 -2
  68. infrahub/graphql/mutations/action.py +8 -2
  69. infrahub/graphql/mutations/artifact_definition.py +4 -1
  70. infrahub/graphql/mutations/branch.py +10 -5
  71. infrahub/graphql/mutations/graphql_query.py +2 -1
  72. infrahub/graphql/mutations/main.py +14 -8
  73. infrahub/graphql/mutations/menu.py +2 -1
  74. infrahub/graphql/mutations/proposed_change.py +225 -8
  75. infrahub/graphql/mutations/relationship.py +6 -1
  76. infrahub/graphql/mutations/repository.py +2 -1
  77. infrahub/graphql/mutations/tasks.py +7 -9
  78. infrahub/graphql/mutations/webhook.py +4 -1
  79. infrahub/graphql/parser.py +15 -6
  80. infrahub/graphql/queries/__init__.py +10 -1
  81. infrahub/graphql/queries/account.py +3 -3
  82. infrahub/graphql/queries/branch.py +2 -2
  83. infrahub/graphql/queries/diff/tree.py +3 -3
  84. infrahub/graphql/queries/event.py +13 -3
  85. infrahub/graphql/queries/ipam.py +23 -1
  86. infrahub/graphql/queries/proposed_change.py +84 -0
  87. infrahub/graphql/queries/relationship.py +2 -2
  88. infrahub/graphql/queries/resource_manager.py +3 -3
  89. infrahub/graphql/queries/search.py +3 -2
  90. infrahub/graphql/queries/status.py +3 -2
  91. infrahub/graphql/queries/task.py +2 -2
  92. infrahub/graphql/resolvers/ipam.py +440 -0
  93. infrahub/graphql/resolvers/many_relationship.py +4 -3
  94. infrahub/graphql/resolvers/resolver.py +5 -5
  95. infrahub/graphql/resolvers/single_relationship.py +3 -2
  96. infrahub/graphql/schema.py +25 -5
  97. infrahub/graphql/types/__init__.py +2 -2
  98. infrahub/graphql/types/attribute.py +3 -3
  99. infrahub/graphql/types/event.py +60 -0
  100. infrahub/groups/tasks.py +6 -6
  101. infrahub/lock.py +3 -2
  102. infrahub/menu/generator.py +8 -0
  103. infrahub/message_bus/operations/__init__.py +9 -12
  104. infrahub/message_bus/operations/git/file.py +6 -5
  105. infrahub/message_bus/operations/git/repository.py +12 -20
  106. infrahub/message_bus/operations/refresh/registry.py +15 -9
  107. infrahub/message_bus/operations/send/echo.py +7 -4
  108. infrahub/message_bus/types.py +1 -0
  109. infrahub/permissions/globals.py +1 -4
  110. infrahub/permissions/manager.py +8 -5
  111. infrahub/pools/prefix.py +7 -5
  112. infrahub/prefect_server/app.py +31 -0
  113. infrahub/prefect_server/bootstrap.py +18 -0
  114. infrahub/proposed_change/action_checker.py +206 -0
  115. infrahub/proposed_change/approval_revoker.py +40 -0
  116. infrahub/proposed_change/branch_diff.py +3 -1
  117. infrahub/proposed_change/checker.py +45 -0
  118. infrahub/proposed_change/constants.py +32 -2
  119. infrahub/proposed_change/tasks.py +182 -150
  120. infrahub/server.py +29 -17
  121. infrahub/services/__init__.py +13 -28
  122. infrahub/services/adapters/cache/__init__.py +4 -0
  123. infrahub/services/adapters/cache/nats.py +2 -0
  124. infrahub/services/adapters/cache/redis.py +3 -0
  125. infrahub/services/adapters/message_bus/__init__.py +0 -2
  126. infrahub/services/adapters/message_bus/local.py +1 -2
  127. infrahub/services/adapters/message_bus/nats.py +6 -8
  128. infrahub/services/adapters/message_bus/rabbitmq.py +7 -9
  129. infrahub/services/adapters/workflow/__init__.py +1 -0
  130. infrahub/services/adapters/workflow/local.py +1 -8
  131. infrahub/services/component.py +2 -1
  132. infrahub/task_manager/event.py +52 -0
  133. infrahub/task_manager/models.py +9 -0
  134. infrahub/tasks/artifact.py +6 -7
  135. infrahub/tasks/check.py +4 -7
  136. infrahub/telemetry/tasks.py +15 -18
  137. infrahub/transformations/tasks.py +10 -6
  138. infrahub/trigger/tasks.py +4 -3
  139. infrahub/types.py +4 -0
  140. infrahub/validators/events.py +7 -7
  141. infrahub/validators/tasks.py +6 -7
  142. infrahub/webhook/models.py +45 -45
  143. infrahub/webhook/tasks.py +25 -24
  144. infrahub/workers/dependencies.py +143 -0
  145. infrahub/workers/infrahub_async.py +19 -43
  146. infrahub/workflows/catalogue.py +16 -2
  147. infrahub/workflows/initialization.py +5 -4
  148. infrahub/workflows/models.py +2 -0
  149. infrahub_sdk/client.py +6 -6
  150. infrahub_sdk/ctl/repository.py +51 -0
  151. infrahub_sdk/ctl/schema.py +9 -9
  152. infrahub_sdk/protocols.py +40 -6
  153. {infrahub_server-1.3.6.dist-info → infrahub_server-1.4.0b1.dist-info}/METADATA +6 -4
  154. {infrahub_server-1.3.6.dist-info → infrahub_server-1.4.0b1.dist-info}/RECORD +162 -149
  155. infrahub_testcontainers/container.py +17 -0
  156. infrahub_testcontainers/docker-compose-cluster.test.yml +56 -1
  157. infrahub_testcontainers/docker-compose.test.yml +56 -1
  158. infrahub_testcontainers/helpers.py +4 -1
  159. infrahub/cli/db_commands/check_inheritance.py +0 -284
  160. /infrahub/{cli/db_commands/__init__.py → py.typed} +0 -0
  161. {infrahub_server-1.3.6.dist-info → infrahub_server-1.4.0b1.dist-info}/LICENSE.txt +0 -0
  162. {infrahub_server-1.3.6.dist-info → infrahub_server-1.4.0b1.dist-info}/WHEEL +0 -0
  163. {infrahub_server-1.3.6.dist-info → infrahub_server-1.4.0b1.dist-info}/entry_points.txt +0 -0
@@ -59,7 +59,7 @@ builtin_ip_prefix = GenericSchema(
59
59
  name="IPPrefix",
60
60
  label="IP Prefix",
61
61
  namespace="Builtin",
62
- description="IPv6 or IPv4 prefix also referred as network",
62
+ description="IPv4 or IPv6 prefix also referred as network",
63
63
  include_in_menu=False,
64
64
  default_filter="prefix__value",
65
65
  order_by=["prefix__version", "prefix__binary_address", "prefix__prefixlen"],
@@ -142,7 +142,7 @@ builtin_ip_address = GenericSchema(
142
142
  name="IPAddress",
143
143
  label="IP Address",
144
144
  namespace="Builtin",
145
- description="IPv6 or IPv4 address",
145
+ description="IPv4 or IPv6 address",
146
146
  include_in_menu=False,
147
147
  default_filter="address__value",
148
148
  order_by=["address__version", "address__binary_address"],
@@ -176,6 +176,32 @@ builtin_ip_address = GenericSchema(
176
176
  ],
177
177
  )
178
178
 
179
+ internal_ipam_ip_range_available = NodeSchema(
180
+ name="IPRangeAvailable",
181
+ label="Available IP Range",
182
+ namespace="Internal",
183
+ description="Range of IPv4 or IPv6 addresses which has not been allocated yet",
184
+ include_in_menu=False,
185
+ display_labels=["address__value", "last_address__value"],
186
+ branch=BranchSupportType.AWARE,
187
+ inherit_from=[InfrahubKind.IPADDRESS],
188
+ generate_profile=False,
189
+ attributes=[Attr(name="last_address", kind="IPHost", branch=BranchSupportType.AWARE, order_weight=2000)],
190
+ )
191
+
192
+ internal_ipam_ip_prefix_available = NodeSchema(
193
+ name="IPPrefixAvailable",
194
+ label="Available IP Prefix",
195
+ namespace="Internal",
196
+ description="IPv4 or IPv6 prefix also referred as network which has not been allocated yet",
197
+ include_in_menu=False,
198
+ display_labels=["prefix__value"],
199
+ branch=BranchSupportType.AWARE,
200
+ inherit_from=[InfrahubKind.IPPREFIX],
201
+ generate_profile=False,
202
+ )
203
+
204
+
179
205
  core_ipam_namespace = NodeSchema(
180
206
  name="Namespace",
181
207
  namespace="Ipam",
@@ -38,6 +38,9 @@ core_proposed_change = NodeSchema(
38
38
  default_value=ProposedChangeState.OPEN.value,
39
39
  optional=True,
40
40
  ),
41
+ Attr(name="is_draft", kind="Boolean", optional=False, default_value=False),
42
+ # Ideally we should support some "runtime-attribute" that could not even be stored in the database.
43
+ Attr(name="total_comments", kind="Number", optional=True, read_only=True),
41
44
  ],
42
45
  relationships=[
43
46
  Rel(
@@ -48,6 +51,17 @@ core_proposed_change = NodeSchema(
48
51
  kind=RelKind.ATTRIBUTE,
49
52
  branch=BranchSupportType.AGNOSTIC,
50
53
  identifier="coreaccount__proposedchange_approved_by",
54
+ read_only=True,
55
+ ),
56
+ Rel(
57
+ name="rejected_by",
58
+ peer=InfrahubKind.GENERICACCOUNT,
59
+ optional=True,
60
+ cardinality=Cardinality.MANY,
61
+ kind=RelKind.ATTRIBUTE,
62
+ branch=BranchSupportType.AGNOSTIC,
63
+ identifier="coreaccount__proposedchange_rejected_by",
64
+ read_only=True,
51
65
  ),
52
66
  Rel(
53
67
  name="reviewers",
@@ -66,6 +80,7 @@ core_proposed_change = NodeSchema(
66
80
  kind=RelKind.ATTRIBUTE,
67
81
  branch=BranchSupportType.AGNOSTIC,
68
82
  identifier="coreaccount__proposedchange_created_by",
83
+ read_only=True,
69
84
  ),
70
85
  Rel(
71
86
  name="comments",
@@ -120,6 +120,9 @@ core_custom_webhook = NodeSchema(
120
120
  branch=BranchSupportType.AGNOSTIC,
121
121
  generate_profile=False,
122
122
  inherit_from=[InfrahubKind.WEBHOOK, InfrahubKind.TASKTARGET],
123
+ attributes=[
124
+ Attr(name="shared_key", kind="Password", unique=False, optional=True, order_weight=4000),
125
+ ],
123
126
  relationships=[
124
127
  Rel(
125
128
  name="transformation",
@@ -2,6 +2,8 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING
4
4
 
5
+ from infrahub.core.constants import InfrahubKind
6
+
5
7
  from .generated.genericnode_schema import GeneratedGenericSchema
6
8
 
7
9
  if TYPE_CHECKING:
@@ -28,6 +30,14 @@ class GenericSchema(GeneratedGenericSchema):
28
30
  def is_template_schema(self) -> bool:
29
31
  return False
30
32
 
33
+ @property
34
+ def is_ip_prefix(self) -> bool:
35
+ return self.kind == InfrahubKind.IPPREFIX
36
+
37
+ @property
38
+ def is_ip_address(self) -> bool:
39
+ return self.kind == InfrahubKind.IPADDRESS
40
+
31
41
  def get_hierarchy_schema(self, db: InfrahubDatabase, branch: Branch | str | None = None) -> GenericSchema: # noqa: ARG002
32
42
  if self.hierarchical:
33
43
  return self
@@ -93,6 +93,15 @@ class SchemaManager(NodeManager):
93
93
 
94
94
  raise ValueError("The selected node is not of type NodeSchema")
95
95
 
96
+ def get_generic_schema(
97
+ self, name: str, branch: Branch | str | None = None, duplicate: bool = True
98
+ ) -> GenericSchema:
99
+ schema = self.get(name=name, branch=branch, duplicate=duplicate)
100
+ if isinstance(schema, GenericSchema):
101
+ return schema
102
+
103
+ raise ValueError("The selected node is not of type GenericSchema")
104
+
96
105
  def get_profile_schema(
97
106
  self, name: str, branch: Branch | str | None = None, duplicate: bool = True
98
107
  ) -> ProfileSchema:
@@ -122,7 +131,7 @@ class SchemaManager(NodeManager):
122
131
 
123
132
  return self._branches[branch_name].get_all(duplicate=duplicate)
124
133
 
125
- async def get_full_safe(self, branch: Branch | str | None = None) -> dict[str, NodeSchema | GenericSchema]:
134
+ async def get_full_safe(self, branch: Branch | str | None = None) -> dict[str, MainSchemaTypes]:
126
135
  await lock.registry.local_schema_wait()
127
136
 
128
137
  return self.get_full(branch=branch)
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING
4
4
 
5
- from infrahub.core.constants import AllowOverrideType, InfrahubKind
5
+ from infrahub.core.constants import AllowOverrideType, InfrahubKind, RelationshipKind
6
6
 
7
7
  from .generated.node_schema import GeneratedNodeSchema
8
8
  from .generic_schema import GenericSchema
@@ -29,6 +29,16 @@ class NodeSchema(GeneratedNodeSchema):
29
29
  def is_template_schema(self) -> bool:
30
30
  return False
31
31
 
32
+ @property
33
+ def is_ip_prefix(self) -> bool:
34
+ """Return whether a node is a derivative of built-in IP prefixes."""
35
+ return InfrahubKind.IPPREFIX in self.inherit_from
36
+
37
+ @property
38
+ def is_ip_address(self) -> bool:
39
+ """Return whether a node is a derivative of built-in IP addreses."""
40
+ return InfrahubKind.IPADDRESS in self.inherit_from
41
+
32
42
  def validate_inheritance(self, interface: GenericSchema) -> None:
33
43
  """Perform checks specific to inheritance from Generics.
34
44
 
@@ -60,14 +70,17 @@ class NodeSchema(GeneratedNodeSchema):
60
70
  )
61
71
 
62
72
  for relationship in self.relationships:
63
- if (
64
- relationship.name in interface.relationship_names
65
- and not relationship.inherited
66
- and interface.get_relationship(relationship.name).allow_override == AllowOverrideType.NONE
67
- ):
68
- raise ValueError(
69
- f"{self.kind}'s relationship {relationship.name} inherited from {interface.kind} cannot be overriden"
70
- )
73
+ if relationship.name in interface.relationship_names and not relationship.inherited:
74
+ interface_relationship = interface.get_relationship(relationship.name)
75
+ if interface_relationship.allow_override == AllowOverrideType.NONE:
76
+ raise ValueError(
77
+ f"{self.kind}'s relationship {relationship.name} inherited from {interface.kind} cannot be overriden"
78
+ )
79
+ if relationship.kind != RelationshipKind.HIERARCHY and relationship.peer != interface_relationship.peer:
80
+ raise ValueError(
81
+ f"{self.kind}'s relationship {relationship.name} inherited from {interface.kind} must have the same peer "
82
+ f"({interface_relationship.peer} != {relationship.peer})"
83
+ )
71
84
 
72
85
  def inherit_from_interface(self, interface: GenericSchema) -> None:
73
86
  existing_inherited_attributes: dict[str, int] = {
@@ -137,11 +150,3 @@ class NodeSchema(GeneratedNodeSchema):
137
150
  if self.namespace not in ["Schema", "Internal"] and InfrahubKind.GENERICGROUP not in self.inherit_from:
138
151
  labels.append(InfrahubKind.NODE)
139
152
  return labels
140
-
141
- def is_ip_prefix(self) -> bool:
142
- """Return whether a node is a derivative of built-in IP prefixes."""
143
- return InfrahubKind.IPPREFIX in self.inherit_from
144
-
145
- def is_ip_address(self) -> bool:
146
- """Return whether a node is a derivative of built-in IP addreses."""
147
- return InfrahubKind.IPADDRESS in self.inherit_from
@@ -28,6 +28,14 @@ class ProfileSchema(BaseNodeSchema):
28
28
  def is_template_schema(self) -> bool:
29
29
  return False
30
30
 
31
+ @property
32
+ def is_ip_prefix(self) -> bool:
33
+ return False
34
+
35
+ @property
36
+ def is_ip_address(self) -> bool:
37
+ return False
38
+
31
39
  def get_labels(self) -> list[str]:
32
40
  """Return the labels for this object, composed of the kind
33
41
  and the list of Generic this object is inheriting from."""
@@ -119,7 +119,7 @@ class SchemaBranch:
119
119
  def template_names(self) -> list[str]:
120
120
  return list(self.templates.keys())
121
121
 
122
- def get_all_kind_id_map(self, nodes_and_generics_only: bool = False) -> dict[str, str]:
122
+ def get_all_kind_id_map(self, nodes_and_generics_only: bool = False) -> dict[str, str | None]:
123
123
  kind_id_map = {}
124
124
  if nodes_and_generics_only:
125
125
  names = self.node_names + self.generic_names_without_templates
@@ -441,7 +441,7 @@ class SchemaBranch:
441
441
  return list(all_schemas.values())
442
442
 
443
443
  def get_schemas_by_rel_identifier(self, identifier: str) -> list[MainSchemaTypes]:
444
- nodes: list[RelationshipSchema] = []
444
+ nodes: list[MainSchemaTypes] = []
445
445
  for node_name in list(self.nodes.keys()) + list(self.generics.keys()):
446
446
  node = self.get(name=node_name, duplicate=False)
447
447
  rel = node.get_relationship_by_identifier(id=identifier, raise_on_error=False)
@@ -660,7 +660,7 @@ class SchemaBranch:
660
660
  and not (
661
661
  schema_attribute_path.relationship_schema.name == "ip_namespace"
662
662
  and isinstance(node_schema, NodeSchema)
663
- and (node_schema.is_ip_address() or node_schema.is_ip_prefix())
663
+ and (node_schema.is_ip_address or node_schema.is_ip_prefix)
664
664
  )
665
665
  ):
666
666
  raise ValueError(
@@ -1447,7 +1447,8 @@ class SchemaBranch:
1447
1447
  node.validate_inheritance(interface=generic_kind_schema)
1448
1448
 
1449
1449
  # Store the list of node referencing a specific generics
1450
- generics_used_by[generic_kind].append(node.kind)
1450
+ if node.namespace != "Internal":
1451
+ generics_used_by[generic_kind].append(node.kind)
1451
1452
  node.inherit_from_interface(interface=generic_kind_schema)
1452
1453
 
1453
1454
  if len(generic_with_hierarchical_support) > 1:
@@ -1925,7 +1926,10 @@ class SchemaBranch:
1925
1926
  for node_name in self.node_names + self.generic_names:
1926
1927
  node = self.get(name=node_name, duplicate=False)
1927
1928
 
1928
- if node.namespace in RESTRICTED_NAMESPACES:
1929
+ if node.namespace in RESTRICTED_NAMESPACES and node.kind not in (
1930
+ InfrahubKind.IPRANGEAVAILABLE,
1931
+ InfrahubKind.IPPREFIXAVAILABLE,
1932
+ ):
1929
1933
  continue
1930
1934
 
1931
1935
  profiles_rel_settings: dict[str, Any] = {
@@ -27,6 +27,14 @@ class TemplateSchema(BaseNodeSchema):
27
27
  def is_template_schema(self) -> bool:
28
28
  return True
29
29
 
30
+ @property
31
+ def is_ip_prefix(self) -> bool:
32
+ return False
33
+
34
+ @property
35
+ def is_ip_address(self) -> bool:
36
+ return False
37
+
30
38
  def get_labels(self) -> list[str]:
31
39
  """Return the labels for this object, composed of the kind and the list of Generic this object is inheriting from."""
32
40
 
@@ -6,7 +6,7 @@ from infrahub_sdk.protocols import CoreValidator
6
6
  from infrahub.context import InfrahubContext
7
7
  from infrahub.core.constants import ValidatorConclusion, ValidatorState
8
8
  from infrahub.core.timestamp import Timestamp
9
- from infrahub.services import InfrahubServices
9
+ from infrahub.services.adapters.event import InfrahubEventService
10
10
  from infrahub.validators.events import send_failed_validator, send_passed_validator
11
11
 
12
12
 
@@ -14,7 +14,7 @@ async def run_checks_and_update_validator(
14
14
  checks: list[Coroutine[Any, None, ValidatorConclusion]],
15
15
  validator: CoreValidator,
16
16
  context: InfrahubContext,
17
- service: InfrahubServices,
17
+ event_service: InfrahubEventService,
18
18
  proposed_change_id: str,
19
19
  ) -> None:
20
20
  """
@@ -38,7 +38,7 @@ async def run_checks_and_update_validator(
38
38
  failed_early = True
39
39
  await validator.save()
40
40
  await send_failed_validator(
41
- service=service, validator=validator, proposed_change_id=proposed_change_id, context=context
41
+ event_service=event_service, validator=validator, proposed_change_id=proposed_change_id, context=context
42
42
  )
43
43
  # Continue to iterate to wait for the end of all checks
44
44
 
@@ -52,9 +52,9 @@ async def run_checks_and_update_validator(
52
52
  if not failed_early:
53
53
  if validator.conclusion.value == ValidatorConclusion.SUCCESS.value:
54
54
  await send_passed_validator(
55
- service=service, validator=validator, proposed_change_id=proposed_change_id, context=context
55
+ event_service=event_service, validator=validator, proposed_change_id=proposed_change_id, context=context
56
56
  )
57
57
  else:
58
58
  await send_failed_validator(
59
- service=service, validator=validator, proposed_change_id=proposed_change_id, context=context
59
+ event_service=event_service, validator=validator, proposed_change_id=proposed_change_id, context=context
60
60
  )
@@ -13,19 +13,18 @@ from infrahub.core.schema import GenericSchema, NodeSchema
13
13
  from infrahub.core.validators.aggregated_checker import AggregatedConstraintChecker
14
14
  from infrahub.core.validators.model import SchemaConstraintValidatorRequest, SchemaViolation
15
15
  from infrahub.dependencies.registry import get_component_registry
16
- from infrahub.services import InfrahubServices # noqa: TC001 needed for prefect flow
16
+ from infrahub.workers.dependencies import get_database
17
17
  from infrahub.workflows.utils import add_tags
18
18
 
19
19
  from .models.validate_migration import SchemaValidateMigrationData, SchemaValidatorPathResponseData
20
20
 
21
21
  if TYPE_CHECKING:
22
22
  from infrahub.core.schema.schema_branch import SchemaBranch
23
+ from infrahub.database import InfrahubDatabase
23
24
 
24
25
 
25
26
  @flow(name="schema_validate_migrations", flow_run_name="Validate schema migrations", persist_result=True)
26
- async def schema_validate_migrations(
27
- message: SchemaValidateMigrationData, service: InfrahubServices
28
- ) -> list[SchemaValidatorPathResponseData]:
27
+ async def schema_validate_migrations(message: SchemaValidateMigrationData) -> list[SchemaValidatorPathResponseData]:
29
28
  batch = InfrahubBatch(return_exceptions=True)
30
29
  log = get_run_logger()
31
30
  await add_tags(branches=[message.branch.name])
@@ -47,7 +46,7 @@ async def schema_validate_migrations(
47
46
  node_schema=schema,
48
47
  schema_path=constraint.path,
49
48
  schema_branch=message.schema_branch,
50
- service=service,
49
+ database=await get_database(),
51
50
  )
52
51
 
53
52
  results = [result async for _, result in batch.execute()]
@@ -67,9 +66,9 @@ async def schema_path_validate(
67
66
  node_schema: NodeSchema | GenericSchema,
68
67
  schema_path: SchemaPath,
69
68
  schema_branch: SchemaBranch,
70
- service: InfrahubServices,
69
+ database: InfrahubDatabase,
71
70
  ) -> SchemaValidatorPathResponseData:
72
- async with service.database.start_session(read_only=True) as db:
71
+ async with database.start_session(read_only=True) as db:
73
72
  constraint_request = SchemaConstraintValidatorRequest(
74
73
  branch=branch,
75
74
  constraint_name=constraint_name,
@@ -75,7 +75,7 @@ class UniquenessChecker(ConstraintCheckerInterface):
75
75
 
76
76
  async def build_query_request(self, schema: MainSchemaTypes) -> NodeUniquenessQueryRequest:
77
77
  unique_attr_paths = {
78
- QueryAttributePath(attribute_name=attr_schema.name, property_name="value")
78
+ QueryAttributePath(attribute_name=attr_schema.name, attribute_kind=attr_schema.kind, property_name="value")
79
79
  for attr_schema in schema.unique_attributes
80
80
  }
81
81
  relationship_attr_paths = set()
@@ -92,7 +92,9 @@ class UniquenessChecker(ConstraintCheckerInterface):
92
92
  sub_schema, property_name = get_attribute_path_from_string(path, schema)
93
93
  if isinstance(sub_schema, AttributeSchema):
94
94
  unique_attr_paths.add(
95
- QueryAttributePath(attribute_name=sub_schema.name, property_name=property_name)
95
+ QueryAttributePath(
96
+ attribute_name=sub_schema.name, attribute_kind=sub_schema.kind, property_name=property_name
97
+ )
96
98
  )
97
99
  elif isinstance(sub_schema, RelationshipSchema):
98
100
  relationship_attr_paths.add(
@@ -22,6 +22,7 @@ class QueryRelationshipAttributePath(BaseModel):
22
22
 
23
23
  class QueryAttributePath(BaseModel):
24
24
  attribute_name: str
25
+ attribute_kind: str
25
26
  property_name: str | None = Field(default=None)
26
27
  value: Any | None = Field(default=None)
27
28
 
@@ -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,16 @@ 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
+ ProposedChangeApprovedEvent,
9
+ ProposedChangeMergedEvent,
10
+ ProposedChangeRejectedEvent,
11
+ ProposedChangeRejectionRevokedEvent,
12
+ ProposedChangeReviewRequestedEvent,
13
+ ProposedChangeThreadCreatedEvent,
14
+ ProposedChangeThreadUpdatedEvent,
15
+ )
6
16
  from .repository_action import CommitUpdatedEvent
7
17
  from .validator_action import ValidatorFailedEvent, ValidatorPassedEvent, ValidatorStartedEvent
8
18
 
@@ -21,6 +31,14 @@ __all__ = [
21
31
  "NodeCreatedEvent",
22
32
  "NodeDeletedEvent",
23
33
  "NodeUpdatedEvent",
34
+ "ProposedChangeApprovalRevokedEvent",
35
+ "ProposedChangeApprovedEvent",
36
+ "ProposedChangeMergedEvent",
37
+ "ProposedChangeRejectedEvent",
38
+ "ProposedChangeRejectionRevokedEvent",
39
+ "ProposedChangeReviewRequestedEvent",
40
+ "ProposedChangeThreadCreatedEvent",
41
+ "ProposedChangeThreadUpdatedEvent",
24
42
  "ValidatorFailedEvent",
25
43
  "ValidatorPassedEvent",
26
44
  "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"