infrahub-server 1.6.3__py3-none-any.whl → 1.7.0b0__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 (161) hide show
  1. infrahub/actions/tasks.py +4 -2
  2. infrahub/api/schema.py +3 -1
  3. infrahub/artifacts/tasks.py +1 -0
  4. infrahub/auth.py +2 -2
  5. infrahub/cli/db.py +6 -6
  6. infrahub/computed_attribute/gather.py +3 -4
  7. infrahub/computed_attribute/tasks.py +23 -6
  8. infrahub/config.py +8 -0
  9. infrahub/constants/enums.py +12 -0
  10. infrahub/core/account.py +5 -8
  11. infrahub/core/attribute.py +106 -108
  12. infrahub/core/branch/models.py +44 -71
  13. infrahub/core/branch/tasks.py +5 -3
  14. infrahub/core/changelog/diff.py +1 -20
  15. infrahub/core/changelog/models.py +0 -7
  16. infrahub/core/constants/__init__.py +17 -0
  17. infrahub/core/constants/database.py +0 -1
  18. infrahub/core/constants/schema.py +0 -1
  19. infrahub/core/convert_object_type/repository_conversion.py +3 -4
  20. infrahub/core/diff/data_check_synchronizer.py +3 -2
  21. infrahub/core/diff/enricher/cardinality_one.py +1 -1
  22. infrahub/core/diff/merger/merger.py +27 -1
  23. infrahub/core/diff/merger/serializer.py +3 -10
  24. infrahub/core/diff/model/diff.py +1 -1
  25. infrahub/core/diff/query/merge.py +376 -135
  26. infrahub/core/graph/__init__.py +1 -1
  27. infrahub/core/graph/constraints.py +2 -2
  28. infrahub/core/graph/schema.py +2 -12
  29. infrahub/core/manager.py +132 -126
  30. infrahub/core/metadata/__init__.py +0 -0
  31. infrahub/core/metadata/interface.py +37 -0
  32. infrahub/core/metadata/model.py +31 -0
  33. infrahub/core/metadata/query/__init__.py +0 -0
  34. infrahub/core/metadata/query/node_metadata.py +301 -0
  35. infrahub/core/migrations/graph/__init__.py +4 -0
  36. infrahub/core/migrations/graph/m013_convert_git_password_credential.py +3 -8
  37. infrahub/core/migrations/graph/m017_add_core_profile.py +5 -2
  38. infrahub/core/migrations/graph/m018_uniqueness_nulls.py +2 -1
  39. infrahub/core/migrations/graph/m019_restore_rels_to_time.py +0 -10
  40. infrahub/core/migrations/graph/m020_duplicate_edges.py +0 -8
  41. infrahub/core/migrations/graph/m025_uniqueness_nulls.py +2 -1
  42. infrahub/core/migrations/graph/m026_0000_prefix_fix.py +2 -1
  43. infrahub/core/migrations/graph/m029_duplicates_cleanup.py +0 -1
  44. infrahub/core/migrations/graph/m031_check_number_attributes.py +2 -2
  45. infrahub/core/migrations/graph/m038_redo_0000_prefix_fix.py +2 -1
  46. infrahub/core/migrations/graph/m049_remove_is_visible_relationship.py +38 -0
  47. infrahub/core/migrations/graph/m050_backfill_vertex_metadata.py +168 -0
  48. infrahub/core/migrations/query/attribute_add.py +17 -6
  49. infrahub/core/migrations/query/attribute_remove.py +19 -5
  50. infrahub/core/migrations/query/attribute_rename.py +21 -5
  51. infrahub/core/migrations/query/node_duplicate.py +19 -4
  52. infrahub/core/migrations/schema/attribute_kind_update.py +25 -7
  53. infrahub/core/migrations/schema/attribute_supports_profile.py +3 -1
  54. infrahub/core/migrations/schema/models.py +3 -0
  55. infrahub/core/migrations/schema/node_attribute_add.py +4 -1
  56. infrahub/core/migrations/schema/node_remove.py +24 -2
  57. infrahub/core/migrations/schema/tasks.py +4 -1
  58. infrahub/core/migrations/shared.py +13 -6
  59. infrahub/core/models.py +6 -6
  60. infrahub/core/node/__init__.py +156 -57
  61. infrahub/core/node/create.py +7 -3
  62. infrahub/core/node/standard.py +100 -14
  63. infrahub/core/property.py +0 -1
  64. infrahub/core/protocols_base.py +6 -2
  65. infrahub/core/query/__init__.py +6 -7
  66. infrahub/core/query/attribute.py +161 -46
  67. infrahub/core/query/branch.py +57 -69
  68. infrahub/core/query/diff.py +4 -4
  69. infrahub/core/query/node.py +618 -180
  70. infrahub/core/query/relationship.py +449 -300
  71. infrahub/core/query/standard_node.py +25 -5
  72. infrahub/core/query/utils.py +2 -4
  73. infrahub/core/relationship/constraints/profiles_removal.py +168 -0
  74. infrahub/core/relationship/model.py +293 -139
  75. infrahub/core/schema/attribute_parameters.py +1 -28
  76. infrahub/core/schema/attribute_schema.py +17 -11
  77. infrahub/core/schema/manager.py +63 -43
  78. infrahub/core/schema/relationship_schema.py +6 -2
  79. infrahub/core/schema/schema_branch.py +48 -76
  80. infrahub/core/task/task.py +4 -2
  81. infrahub/core/utils.py +0 -22
  82. infrahub/core/validators/attribute/kind.py +2 -5
  83. infrahub/core/validators/determiner.py +3 -3
  84. infrahub/database/__init__.py +3 -3
  85. infrahub/dependencies/builder/constraint/grouped/node_runner.py +2 -0
  86. infrahub/dependencies/builder/constraint/relationship_manager/profiles_removal.py +8 -0
  87. infrahub/dependencies/registry.py +2 -0
  88. infrahub/display_labels/tasks.py +12 -3
  89. infrahub/git/integrator.py +18 -18
  90. infrahub/git/tasks.py +1 -1
  91. infrahub/graphql/app.py +2 -2
  92. infrahub/graphql/constants.py +3 -0
  93. infrahub/graphql/context.py +1 -1
  94. infrahub/graphql/initialization.py +11 -0
  95. infrahub/graphql/loaders/account.py +134 -0
  96. infrahub/graphql/loaders/node.py +5 -12
  97. infrahub/graphql/loaders/peers.py +5 -7
  98. infrahub/graphql/manager.py +158 -18
  99. infrahub/graphql/metadata.py +91 -0
  100. infrahub/graphql/models.py +33 -3
  101. infrahub/graphql/mutations/account.py +5 -5
  102. infrahub/graphql/mutations/attribute.py +0 -2
  103. infrahub/graphql/mutations/branch.py +9 -5
  104. infrahub/graphql/mutations/computed_attribute.py +1 -1
  105. infrahub/graphql/mutations/display_label.py +1 -1
  106. infrahub/graphql/mutations/hfid.py +1 -1
  107. infrahub/graphql/mutations/ipam.py +4 -6
  108. infrahub/graphql/mutations/main.py +9 -4
  109. infrahub/graphql/mutations/profile.py +16 -22
  110. infrahub/graphql/mutations/proposed_change.py +4 -4
  111. infrahub/graphql/mutations/relationship.py +40 -10
  112. infrahub/graphql/mutations/repository.py +14 -12
  113. infrahub/graphql/mutations/schema.py +2 -2
  114. infrahub/graphql/queries/branch.py +62 -6
  115. infrahub/graphql/queries/diff/tree.py +5 -5
  116. infrahub/graphql/resolvers/account_metadata.py +84 -0
  117. infrahub/graphql/resolvers/ipam.py +6 -8
  118. infrahub/graphql/resolvers/many_relationship.py +77 -35
  119. infrahub/graphql/resolvers/resolver.py +16 -12
  120. infrahub/graphql/resolvers/single_relationship.py +87 -23
  121. infrahub/graphql/subscription/graphql_query.py +2 -0
  122. infrahub/graphql/types/__init__.py +0 -1
  123. infrahub/graphql/types/attribute.py +10 -5
  124. infrahub/graphql/types/branch.py +40 -53
  125. infrahub/graphql/types/enums.py +3 -0
  126. infrahub/graphql/types/metadata.py +28 -0
  127. infrahub/graphql/types/node.py +22 -2
  128. infrahub/graphql/types/relationship.py +10 -2
  129. infrahub/graphql/types/standard_node.py +4 -3
  130. infrahub/hfid/tasks.py +12 -3
  131. infrahub/profiles/gather.py +56 -0
  132. infrahub/profiles/mandatory_fields_checker.py +116 -0
  133. infrahub/profiles/models.py +66 -0
  134. infrahub/profiles/node_applier.py +153 -12
  135. infrahub/profiles/queries/get_profile_data.py +143 -31
  136. infrahub/profiles/tasks.py +79 -27
  137. infrahub/profiles/triggers.py +22 -0
  138. infrahub/proposed_change/tasks.py +4 -1
  139. infrahub/tasks/artifact.py +1 -0
  140. infrahub/transformations/tasks.py +2 -2
  141. infrahub/trigger/catalogue.py +2 -0
  142. infrahub/trigger/models.py +1 -0
  143. infrahub/trigger/setup.py +3 -3
  144. infrahub/trigger/tasks.py +3 -0
  145. infrahub/validators/tasks.py +1 -0
  146. infrahub/webhook/models.py +1 -1
  147. infrahub/webhook/tasks.py +1 -1
  148. infrahub/workers/dependencies.py +9 -3
  149. infrahub/workers/infrahub_async.py +13 -4
  150. infrahub/workflows/catalogue.py +19 -0
  151. infrahub_sdk/node/constants.py +1 -0
  152. infrahub_sdk/node/related_node.py +13 -4
  153. infrahub_sdk/node/relationship.py +8 -0
  154. {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0b0.dist-info}/METADATA +17 -16
  155. {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0b0.dist-info}/RECORD +161 -143
  156. infrahub_testcontainers/container.py +3 -3
  157. infrahub_testcontainers/docker-compose-cluster.test.yml +7 -7
  158. infrahub_testcontainers/docker-compose.test.yml +13 -5
  159. {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0b0.dist-info}/WHEEL +0 -0
  160. {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0b0.dist-info}/entry_points.txt +0 -0
  161. {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0b0.dist-info}/licenses/LICENSE.txt +0 -0
@@ -1,14 +1,15 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING, Any
3
+ from typing import TYPE_CHECKING, Any, assert_never
4
4
 
5
+ from infrahub.constants.enums import OrderByField
5
6
  from infrahub.core.query import Query, QueryType
6
7
  from infrahub.exceptions import InitializationError
7
8
 
8
9
  if TYPE_CHECKING:
9
10
  from uuid import UUID
10
11
 
11
- from infrahub.core.node.standard import StandardNode
12
+ from infrahub.core.node.standard import StandardNode, StandardNodeOrdering
12
13
  from infrahub.database import InfrahubDatabase
13
14
 
14
15
 
@@ -135,11 +136,19 @@ class StandardNodeGetListQuery(Query):
135
136
  raw_filter: str | None = None
136
137
 
137
138
  def __init__(
138
- self, node_class: StandardNode, ids: list[str] | None = None, node_name: str | None = None, **kwargs: Any
139
+ self,
140
+ node_class: StandardNode,
141
+ node_ordering: StandardNodeOrdering,
142
+ ids: list[str] | None = None,
143
+ node_name: str | None = None,
144
+ partial_match: bool = False,
145
+ **kwargs: Any,
139
146
  ) -> None:
140
147
  self.ids = ids
141
148
  self.node_name = node_name
142
149
  self.node_class = node_class
150
+ self.partial_match = partial_match
151
+ self.node_ordering = node_ordering
143
152
 
144
153
  super().__init__(**kwargs)
145
154
 
@@ -148,9 +157,12 @@ class StandardNodeGetListQuery(Query):
148
157
  if self.ids:
149
158
  filters.append("n.uuid in $ids_value")
150
159
  self.params["ids_value"] = self.ids
151
- if self.node_name:
160
+ if self.node_name and not self.partial_match:
152
161
  filters.append("n.name = $name")
153
162
  self.params["name"] = self.node_name
163
+ if self.node_name and self.partial_match:
164
+ filters.append("toLower(toString(n.name)) CONTAINS toLower(toString($name))")
165
+ self.params["name"] = self.node_name
154
166
  if self.raw_filter:
155
167
  filters.append(self.raw_filter)
156
168
 
@@ -169,4 +181,12 @@ class StandardNodeGetListQuery(Query):
169
181
  self.add_to_query(query)
170
182
 
171
183
  self.return_labels = ["n"]
172
- self.order_by = [f"{db.get_id_function_name()}(n)"]
184
+ match self.node_ordering.order_by:
185
+ case OrderByField.ID:
186
+ self.order_by = [f"{db.get_id_function_name()}(n)"]
187
+ case OrderByField.CREATED_AT:
188
+ self.order_by = [f"n.created_at {self.node_ordering.direction.value}"]
189
+ case OrderByField.UPDATED_AT:
190
+ self.order_by = [f"n.updated_at {self.node_ordering.direction.value}"]
191
+ case _:
192
+ assert_never(self.node_ordering.order_by)
@@ -5,16 +5,14 @@ from typing import TYPE_CHECKING
5
5
  from infrahub.core.schema import NodeSchema, ProfileSchema, TemplateSchema
6
6
 
7
7
  if TYPE_CHECKING:
8
- from neo4j.graph import Node as Neo4jNode
9
-
10
8
  from infrahub.core.branch import Branch
11
9
  from infrahub.database import InfrahubDatabase
12
10
 
13
11
 
14
12
  def find_node_schema(
15
- db: InfrahubDatabase, node: Neo4jNode, branch: Branch | str, duplicate: bool = False
13
+ db: InfrahubDatabase, branch: Branch | str, labels: list[str], duplicate: bool = False
16
14
  ) -> NodeSchema | ProfileSchema | TemplateSchema | None:
17
- for label in node.labels:
15
+ for label in labels:
18
16
  if db.schema.has(name=label, branch=branch):
19
17
  schema = db.schema.get(name=label, branch=branch, duplicate=duplicate)
20
18
  if isinstance(schema, NodeSchema | ProfileSchema | TemplateSchema):
@@ -0,0 +1,168 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from infrahub.core import registry
6
+ from infrahub.core.constants import MetadataOptions
7
+ from infrahub.core.manager import NodeManager
8
+ from infrahub.core.schema import NodeSchema, ProfileSchema
9
+ from infrahub.exceptions import ValidationError
10
+
11
+ from .interface import RelationshipManagerConstraintInterface
12
+
13
+ if TYPE_CHECKING:
14
+ from infrahub.core.branch import Branch
15
+ from infrahub.core.node import Node
16
+ from infrahub.core.relationship.model import Relationship, RelationshipManager
17
+ from infrahub.core.schema import MainSchemaTypes
18
+ from infrahub.database import InfrahubDatabase
19
+
20
+
21
+ class RelationshipProfileRemovalConstraint(RelationshipManagerConstraintInterface):
22
+ """Constraint that validates removing profiles from a node doesn't violate required relationships.
23
+
24
+ This runs in two cases:
25
+ 1. When a node's `profiles` relationship is changed.
26
+ 2. When a profile's `related_nodes` relationship is changed.
27
+
28
+ In both cases, it will perform checks only if peers are being removed from the relationship being changed.
29
+ """
30
+
31
+ def __init__(self, db: InfrahubDatabase, branch: Branch | None = None):
32
+ self.db = db
33
+ self.branch = branch
34
+ self.schema_branch = registry.schema.get_schema_branch(branch.name if branch else registry.default_branch)
35
+
36
+ def _get_required_attributes_names(self, schema: NodeSchema) -> set[str]:
37
+ attr_names: set[str] = set()
38
+ for attr_schema in schema.attributes:
39
+ if attr_schema.support_profiles and not attr_schema.optional:
40
+ attr_names.add(attr_schema.name)
41
+ return attr_names
42
+
43
+ def _get_required_relationship_names(self, schema: NodeSchema) -> set[str]:
44
+ rel_names: set[str] = set()
45
+ for rel_schema in schema.relationships:
46
+ if rel_schema.support_profiles and not rel_schema.optional:
47
+ rel_names.add(rel_schema.name)
48
+ return rel_names
49
+
50
+ async def _validate_profile_removal(
51
+ self, node: Node, profile_id: str, required_attr_names: set[str], required_rel_names: set[str]
52
+ ) -> None:
53
+ for attr_name in required_attr_names:
54
+ attr = node.get_attribute(name=attr_name)
55
+ if attr.is_from_profile:
56
+ source = await attr.get_source(db=self.db)
57
+ if source and source.id == profile_id:
58
+ node_display_label = await node.get_display_label(db=self.db)
59
+ node_reference = f"node '{node_display_label}' (ID: {node.get_id()})"
60
+ raise ValidationError(
61
+ f"Cannot remove profile '{profile_id}' because {node_reference} "
62
+ f"inherits required attribute '{attr_name}' from this profile."
63
+ )
64
+
65
+ for rel_name in required_rel_names:
66
+ rel_manager = node.get_relationship(name=rel_name)
67
+
68
+ relationships: list[Relationship] = await rel_manager.get_relationships(db=self.db)
69
+ for rel in relationships:
70
+ if rel.is_from_profile:
71
+ source = await rel.get_source(db=self.db)
72
+ if source and source.id == profile_id:
73
+ node_display_label = await node.get_display_label(db=self.db)
74
+ node_reference = f"node '{node_display_label}' (ID: {node.get_id()})"
75
+ raise ValidationError(
76
+ f"Cannot remove profile '{profile_id}' because {node_reference} "
77
+ f"inherits required relationship '{rel_name}' from this profile."
78
+ )
79
+
80
+ async def _check_node_profiles_removal(
81
+ self, relm: RelationshipManager, node_schema: NodeSchema, node: Node
82
+ ) -> None:
83
+ required_attr_names = self._get_required_attributes_names(schema=node_schema)
84
+ required_rel_names = self._get_required_relationship_names(schema=node_schema)
85
+ if not required_attr_names and not required_rel_names:
86
+ return
87
+
88
+ relm_update_details = await relm.fetch_relationship_ids(db=self.db, force_refresh=False)
89
+ if not relm_update_details.peer_ids_present_database_only:
90
+ return
91
+
92
+ if required_attr_names:
93
+ # Required to get source for attributes
94
+ node = await NodeManager.get_one(
95
+ db=self.db, branch=self.branch, id=node.get_id(), include_metadata=MetadataOptions.SOURCE
96
+ )
97
+
98
+ for profile_id in relm_update_details.peer_ids_present_database_only:
99
+ await self._validate_profile_removal(
100
+ node=node,
101
+ profile_id=profile_id,
102
+ required_attr_names=required_attr_names,
103
+ required_rel_names=required_rel_names,
104
+ )
105
+
106
+ async def _check_profile_related_nodes_removal(
107
+ self, relm: RelationshipManager, profile_schema: ProfileSchema, profile: Node
108
+ ) -> None:
109
+ relm_update_details = await relm.fetch_relationship_ids(db=self.db, force_refresh=False)
110
+ if not relm_update_details.peer_ids_present_database_only:
111
+ return
112
+
113
+ target_kind = profile_schema.get_relationship(name="related_nodes").peer
114
+ target_schema = self.schema_branch.get_node(name=target_kind, duplicate=False)
115
+
116
+ required_attr_names = self._get_required_attributes_names(schema=target_schema)
117
+ required_rel_names = self._get_required_relationship_names(schema=target_schema)
118
+ if not required_attr_names and not required_rel_names:
119
+ return
120
+
121
+ nodes = await NodeManager.get_many(
122
+ db=self.db,
123
+ branch=self.branch,
124
+ ids=relm_update_details.peer_ids_present_database_only,
125
+ include_metadata=MetadataOptions.SOURCE,
126
+ )
127
+ for node in nodes.values():
128
+ await self._validate_profile_removal(
129
+ node=node,
130
+ profile_id=profile.get_id(),
131
+ required_attr_names=required_attr_names,
132
+ required_rel_names=required_rel_names,
133
+ )
134
+
135
+ async def check(self, relm: RelationshipManager, node_schema: MainSchemaTypes, node: Node) -> None:
136
+ if relm.name == "profiles" and isinstance(node_schema, NodeSchema):
137
+ await self._check_node_profiles_removal(relm=relm, node_schema=node_schema, node=node)
138
+ return
139
+
140
+ if relm.name == "related_nodes" and isinstance(node_schema, ProfileSchema):
141
+ await self._check_profile_related_nodes_removal(relm=relm, profile_schema=node_schema, profile=node)
142
+ return
143
+
144
+ async def validate_profile_deletion(self, profile: Node, profile_schema: ProfileSchema) -> None:
145
+ related_nodes_rels = await profile.related_nodes.get_relationships(db=self.db) # type: ignore[attr-defined]
146
+ related_node_ids = [rel.peer_id for rel in related_nodes_rels if rel.peer_id]
147
+
148
+ if not related_node_ids:
149
+ return
150
+
151
+ target_kind = profile_schema.get_relationship(name="related_nodes").peer
152
+ target_schema = self.schema_branch.get_node(name=target_kind, duplicate=False)
153
+
154
+ required_attr_names = self._get_required_attributes_names(schema=target_schema)
155
+ required_rel_names = self._get_required_relationship_names(schema=target_schema)
156
+ if not required_attr_names and not required_rel_names:
157
+ return
158
+
159
+ nodes = await NodeManager.get_many(
160
+ db=self.db, branch=self.branch, ids=related_node_ids, include_metadata=MetadataOptions.SOURCE
161
+ )
162
+ for node in nodes.values():
163
+ await self._validate_profile_removal(
164
+ node=node,
165
+ profile_id=profile.get_id(),
166
+ required_attr_names=required_attr_names,
167
+ required_rel_names=required_rel_names,
168
+ )