infrahub-server 1.5.0b0__py3-none-any.whl → 1.5.0b2__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 (183) hide show
  1. infrahub/actions/tasks.py +8 -0
  2. infrahub/api/diff/diff.py +1 -1
  3. infrahub/api/internal.py +2 -0
  4. infrahub/api/oauth2.py +13 -19
  5. infrahub/api/oidc.py +15 -21
  6. infrahub/api/schema.py +24 -3
  7. infrahub/artifacts/models.py +2 -1
  8. infrahub/auth.py +137 -3
  9. infrahub/cli/__init__.py +2 -0
  10. infrahub/cli/db.py +103 -98
  11. infrahub/cli/db_commands/clean_duplicate_schema_fields.py +212 -0
  12. infrahub/cli/dev.py +118 -0
  13. infrahub/cli/tasks.py +46 -0
  14. infrahub/cli/upgrade.py +30 -3
  15. infrahub/computed_attribute/tasks.py +20 -8
  16. infrahub/core/attribute.py +13 -5
  17. infrahub/core/branch/enums.py +1 -1
  18. infrahub/core/branch/models.py +7 -3
  19. infrahub/core/branch/tasks.py +70 -8
  20. infrahub/core/changelog/models.py +4 -12
  21. infrahub/core/constants/__init__.py +3 -0
  22. infrahub/core/constants/infrahubkind.py +1 -0
  23. infrahub/core/diff/model/path.py +4 -0
  24. infrahub/core/diff/payload_builder.py +1 -1
  25. infrahub/core/diff/query/artifact.py +1 -0
  26. infrahub/core/diff/query/field_summary.py +1 -0
  27. infrahub/core/graph/__init__.py +1 -1
  28. infrahub/core/initialization.py +5 -2
  29. infrahub/core/ipam/utilization.py +1 -1
  30. infrahub/core/manager.py +6 -3
  31. infrahub/core/migrations/__init__.py +3 -0
  32. infrahub/core/migrations/exceptions.py +4 -0
  33. infrahub/core/migrations/graph/__init__.py +12 -11
  34. infrahub/core/migrations/graph/load_schema_branch.py +21 -0
  35. infrahub/core/migrations/graph/m013_convert_git_password_credential.py +1 -1
  36. infrahub/core/migrations/graph/m040_duplicated_attributes.py +81 -0
  37. infrahub/core/migrations/graph/m041_profile_attrs_in_db.py +145 -0
  38. infrahub/core/migrations/graph/m042_create_hfid_display_label_in_db.py +164 -0
  39. infrahub/core/migrations/graph/m043_backfill_hfid_display_label_in_db.py +866 -0
  40. infrahub/core/migrations/query/__init__.py +7 -8
  41. infrahub/core/migrations/query/attribute_add.py +8 -6
  42. infrahub/core/migrations/query/attribute_remove.py +134 -0
  43. infrahub/core/migrations/runner.py +54 -0
  44. infrahub/core/migrations/schema/attribute_kind_update.py +9 -3
  45. infrahub/core/migrations/schema/attribute_supports_profile.py +90 -0
  46. infrahub/core/migrations/schema/node_attribute_add.py +35 -4
  47. infrahub/core/migrations/schema/node_attribute_remove.py +13 -109
  48. infrahub/core/migrations/schema/node_kind_update.py +2 -1
  49. infrahub/core/migrations/schema/node_remove.py +2 -1
  50. infrahub/core/migrations/schema/placeholder_dummy.py +3 -2
  51. infrahub/core/migrations/shared.py +52 -19
  52. infrahub/core/node/__init__.py +158 -51
  53. infrahub/core/node/constraints/attribute_uniqueness.py +3 -1
  54. infrahub/core/node/create.py +46 -63
  55. infrahub/core/node/lock_utils.py +70 -44
  56. infrahub/core/node/node_property_attribute.py +230 -0
  57. infrahub/core/node/resource_manager/ip_address_pool.py +2 -1
  58. infrahub/core/node/resource_manager/ip_prefix_pool.py +2 -1
  59. infrahub/core/node/resource_manager/number_pool.py +2 -1
  60. infrahub/core/node/standard.py +1 -1
  61. infrahub/core/protocols.py +7 -1
  62. infrahub/core/query/attribute.py +55 -0
  63. infrahub/core/query/ipam.py +1 -0
  64. infrahub/core/query/node.py +23 -4
  65. infrahub/core/query/relationship.py +1 -0
  66. infrahub/core/registry.py +2 -2
  67. infrahub/core/relationship/constraints/count.py +1 -1
  68. infrahub/core/relationship/model.py +1 -1
  69. infrahub/core/schema/__init__.py +56 -0
  70. infrahub/core/schema/attribute_schema.py +4 -0
  71. infrahub/core/schema/basenode_schema.py +42 -2
  72. infrahub/core/schema/definitions/core/__init__.py +2 -0
  73. infrahub/core/schema/definitions/core/generator.py +2 -0
  74. infrahub/core/schema/definitions/core/group.py +16 -2
  75. infrahub/core/schema/definitions/internal.py +16 -3
  76. infrahub/core/schema/generated/attribute_schema.py +2 -2
  77. infrahub/core/schema/generated/base_node_schema.py +6 -1
  78. infrahub/core/schema/manager.py +22 -1
  79. infrahub/core/schema/node_schema.py +5 -2
  80. infrahub/core/schema/schema_branch.py +300 -8
  81. infrahub/core/schema/schema_branch_display.py +123 -0
  82. infrahub/core/schema/schema_branch_hfid.py +114 -0
  83. infrahub/core/validators/aggregated_checker.py +1 -1
  84. infrahub/core/validators/determiner.py +12 -1
  85. infrahub/core/validators/relationship/peer.py +1 -1
  86. infrahub/core/validators/tasks.py +1 -1
  87. infrahub/database/graph.py +21 -0
  88. infrahub/display_labels/__init__.py +0 -0
  89. infrahub/display_labels/gather.py +48 -0
  90. infrahub/display_labels/models.py +240 -0
  91. infrahub/display_labels/tasks.py +192 -0
  92. infrahub/display_labels/triggers.py +22 -0
  93. infrahub/events/branch_action.py +27 -1
  94. infrahub/events/group_action.py +1 -1
  95. infrahub/events/node_action.py +1 -1
  96. infrahub/generators/constants.py +7 -0
  97. infrahub/generators/models.py +7 -0
  98. infrahub/generators/tasks.py +34 -22
  99. infrahub/git/base.py +4 -1
  100. infrahub/git/integrator.py +23 -15
  101. infrahub/git/models.py +2 -1
  102. infrahub/git/repository.py +22 -5
  103. infrahub/git/tasks.py +66 -10
  104. infrahub/git/utils.py +123 -1
  105. infrahub/graphql/analyzer.py +1 -1
  106. infrahub/graphql/api/endpoints.py +14 -4
  107. infrahub/graphql/manager.py +4 -9
  108. infrahub/graphql/mutations/convert_object_type.py +11 -1
  109. infrahub/graphql/mutations/display_label.py +118 -0
  110. infrahub/graphql/mutations/generator.py +25 -7
  111. infrahub/graphql/mutations/hfid.py +125 -0
  112. infrahub/graphql/mutations/ipam.py +54 -35
  113. infrahub/graphql/mutations/main.py +27 -28
  114. infrahub/graphql/mutations/relationship.py +2 -2
  115. infrahub/graphql/mutations/resource_manager.py +2 -2
  116. infrahub/graphql/mutations/schema.py +5 -5
  117. infrahub/graphql/queries/resource_manager.py +1 -1
  118. infrahub/graphql/resolvers/resolver.py +2 -0
  119. infrahub/graphql/schema.py +4 -0
  120. infrahub/graphql/schema_sort.py +170 -0
  121. infrahub/graphql/types/branch.py +4 -1
  122. infrahub/graphql/types/enums.py +3 -0
  123. infrahub/groups/tasks.py +1 -1
  124. infrahub/hfid/__init__.py +0 -0
  125. infrahub/hfid/gather.py +48 -0
  126. infrahub/hfid/models.py +240 -0
  127. infrahub/hfid/tasks.py +191 -0
  128. infrahub/hfid/triggers.py +22 -0
  129. infrahub/lock.py +67 -16
  130. infrahub/message_bus/types.py +2 -1
  131. infrahub/middleware.py +26 -1
  132. infrahub/permissions/constants.py +2 -0
  133. infrahub/proposed_change/tasks.py +35 -17
  134. infrahub/server.py +21 -4
  135. infrahub/services/__init__.py +8 -5
  136. infrahub/services/adapters/http/__init__.py +5 -0
  137. infrahub/services/adapters/workflow/worker.py +14 -3
  138. infrahub/task_manager/event.py +5 -0
  139. infrahub/task_manager/models.py +7 -0
  140. infrahub/task_manager/task.py +73 -0
  141. infrahub/trigger/catalogue.py +4 -0
  142. infrahub/trigger/models.py +2 -0
  143. infrahub/trigger/setup.py +13 -4
  144. infrahub/trigger/tasks.py +6 -0
  145. infrahub/workers/dependencies.py +10 -1
  146. infrahub/workers/infrahub_async.py +10 -2
  147. infrahub/workflows/catalogue.py +80 -0
  148. infrahub/workflows/initialization.py +21 -0
  149. infrahub/workflows/utils.py +2 -1
  150. infrahub_sdk/checks.py +1 -1
  151. infrahub_sdk/client.py +13 -10
  152. infrahub_sdk/config.py +29 -2
  153. infrahub_sdk/ctl/cli_commands.py +2 -0
  154. infrahub_sdk/ctl/generator.py +4 -0
  155. infrahub_sdk/ctl/graphql.py +184 -0
  156. infrahub_sdk/ctl/schema.py +28 -9
  157. infrahub_sdk/generator.py +7 -1
  158. infrahub_sdk/graphql/__init__.py +12 -0
  159. infrahub_sdk/graphql/constants.py +1 -0
  160. infrahub_sdk/graphql/plugin.py +85 -0
  161. infrahub_sdk/graphql/query.py +77 -0
  162. infrahub_sdk/{graphql.py → graphql/renderers.py} +81 -73
  163. infrahub_sdk/graphql/utils.py +40 -0
  164. infrahub_sdk/protocols.py +14 -0
  165. infrahub_sdk/schema/__init__.py +70 -4
  166. infrahub_sdk/schema/repository.py +8 -0
  167. infrahub_sdk/spec/models.py +7 -0
  168. infrahub_sdk/spec/object.py +53 -44
  169. infrahub_sdk/spec/processors/__init__.py +0 -0
  170. infrahub_sdk/spec/processors/data_processor.py +10 -0
  171. infrahub_sdk/spec/processors/factory.py +34 -0
  172. infrahub_sdk/spec/processors/range_expand_processor.py +56 -0
  173. infrahub_sdk/spec/range_expansion.py +1 -1
  174. infrahub_sdk/transforms.py +1 -1
  175. {infrahub_server-1.5.0b0.dist-info → infrahub_server-1.5.0b2.dist-info}/METADATA +7 -4
  176. {infrahub_server-1.5.0b0.dist-info → infrahub_server-1.5.0b2.dist-info}/RECORD +182 -143
  177. infrahub_testcontainers/container.py +115 -3
  178. infrahub_testcontainers/docker-compose-cluster.test.yml +6 -1
  179. infrahub_testcontainers/docker-compose.test.yml +6 -1
  180. infrahub/core/migrations/graph/m040_profile_attrs_in_db.py +0 -166
  181. {infrahub_server-1.5.0b0.dist-info → infrahub_server-1.5.0b2.dist-info}/LICENSE.txt +0 -0
  182. {infrahub_server-1.5.0b0.dist-info → infrahub_server-1.5.0b2.dist-info}/WHEEL +0 -0
  183. {infrahub_server-1.5.0b0.dist-info → infrahub_server-1.5.0b2.dist-info}/entry_points.txt +0 -0
@@ -8,7 +8,12 @@ if TYPE_CHECKING:
8
8
  from ..shared import AttributeSchemaMigration, SchemaMigration
9
9
 
10
10
 
11
- class MigrationQuery(Query):
11
+ class MigrationBaseQuery(Query):
12
+ def get_nbr_migrations_executed(self) -> int:
13
+ return self.num_of_results
14
+
15
+
16
+ class MigrationQuery(MigrationBaseQuery):
12
17
  type: QueryType = QueryType.WRITE
13
18
 
14
19
  def __init__(
@@ -19,11 +24,8 @@ class MigrationQuery(Query):
19
24
  self.migration = migration
20
25
  super().__init__(**kwargs)
21
26
 
22
- def get_nbr_migrations_executed(self) -> int:
23
- return self.num_of_results
24
-
25
27
 
26
- class AttributeMigrationQuery(Query):
28
+ class AttributeMigrationQuery(MigrationBaseQuery):
27
29
  type: QueryType = QueryType.WRITE
28
30
 
29
31
  def __init__(
@@ -33,6 +35,3 @@ class AttributeMigrationQuery(Query):
33
35
  ):
34
36
  self.migration = migration
35
37
  super().__init__(**kwargs)
36
-
37
- def get_nbr_migrations_executed(self) -> int:
38
- return self.num_of_results
@@ -17,26 +17,27 @@ class AttributeAddQuery(Query):
17
17
 
18
18
  def __init__(
19
19
  self,
20
- node_kind: str,
20
+ node_kinds: list[str],
21
21
  attribute_name: str,
22
22
  attribute_kind: str,
23
23
  branch_support: str,
24
24
  default_value: Any | None = None,
25
+ uuids: list[str] | None = None,
25
26
  **kwargs: Any,
26
27
  ) -> None:
27
- self.node_kind = node_kind
28
+ self.node_kinds = node_kinds
28
29
  self.attribute_name = attribute_name
29
30
  self.attribute_kind = attribute_kind
30
31
  self.branch_support = branch_support
31
32
  self.default_value = default_value
32
-
33
+ self.uuids = uuids
33
34
  super().__init__(**kwargs)
34
35
 
35
36
  async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002
36
37
  branch_filter, branch_params = self.branch.get_query_filter_path(at=self.at.to_string())
37
38
  self.params.update(branch_params)
38
39
 
39
- self.params["node_kind"] = self.node_kind
40
+ self.params["node_kinds"] = self.node_kinds
40
41
  self.params["attr_name"] = self.attribute_name
41
42
  self.params["branch_support"] = self.branch_support
42
43
  self.params["current_time"] = self.at.to_string()
@@ -79,12 +80,13 @@ class AttributeAddQuery(Query):
79
80
  LIMIT 1
80
81
  """ % {"attr_value_label": attr_value_label}
81
82
 
83
+ node_kinds_str = "|".join(self.node_kinds)
82
84
  query = """
83
85
  %(match_query)s
84
86
  MERGE (is_protected_value:Boolean { value: $is_protected_default })
85
87
  MERGE (is_visible_value:Boolean { value: $is_visible_default })
86
88
  WITH av, is_protected_value, is_visible_value
87
- MATCH p = (n:%(node_kind)s)
89
+ MATCH (n:%(node_kinds_str)s)
88
90
  CALL (n) {
89
91
  MATCH (:Root)<-[r:IS_PART_OF]-(n)
90
92
  WHERE %(branch_filter)s
@@ -110,7 +112,7 @@ class AttributeAddQuery(Query):
110
112
  """ % {
111
113
  "match_query": match_query,
112
114
  "branch_filter": branch_filter,
113
- "node_kind": self.node_kind,
115
+ "node_kinds_str": node_kinds_str,
114
116
  "uuid_generation": db.render_uuid_generation(node_label="a", node_attr="uuid"),
115
117
  }
116
118
 
@@ -0,0 +1,134 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from infrahub.core.constants import RelationshipStatus
6
+ from infrahub.core.graph.schema import GraphAttributeRelationships
7
+ from infrahub.core.query import Query
8
+ from infrahub.core.schema.generic_schema import GenericSchema
9
+
10
+ if TYPE_CHECKING:
11
+ from pydantic.fields import FieldInfo
12
+
13
+ from infrahub.database import InfrahubDatabase
14
+
15
+
16
+ class AttributeRemoveQuery(Query):
17
+ name = "attribute_remove"
18
+ insert_return: bool = False
19
+
20
+ def __init__(
21
+ self,
22
+ attribute_name: str,
23
+ node_kinds: list[str],
24
+ **kwargs: Any,
25
+ ) -> None:
26
+ self.attribute_name = attribute_name
27
+ self.node_kinds = node_kinds
28
+ super().__init__(**kwargs)
29
+
30
+ async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002
31
+ branch_filter, branch_params = self.branch.get_query_filter_path(at=self.at.to_string())
32
+ self.params.update(branch_params)
33
+
34
+ kinds_to_ignore = []
35
+ profile_kinds_to_update = []
36
+
37
+ for node_kind in self.node_kinds:
38
+ new_schema = db.schema.get(name=node_kind, branch=self.branch, duplicate=False)
39
+
40
+ if isinstance(new_schema, GenericSchema):
41
+ for inheriting_schema_kind in new_schema.used_by:
42
+ node_schema = db.schema.get_node_schema(
43
+ name=inheriting_schema_kind, branch=self.branch, duplicate=False
44
+ )
45
+ attr_schema = node_schema.get_attribute_or_none(name=self.attribute_name)
46
+ if attr_schema and not attr_schema.inherited:
47
+ kinds_to_ignore.append(inheriting_schema_kind)
48
+ else:
49
+ profile_kinds_to_update.append(f"Profile{inheriting_schema_kind}")
50
+
51
+ self.params["kinds_to_ignore"] = kinds_to_ignore
52
+ self.params["attr_name"] = self.attribute_name
53
+ self.params["current_time"] = self.at.to_string()
54
+ self.params["branch_name"] = self.branch.name
55
+
56
+ self.params["rel_props"] = {
57
+ "branch": self.branch.name,
58
+ "branch_level": self.branch.hierarchy_level,
59
+ "status": RelationshipStatus.DELETED.value,
60
+ "from": self.at.to_string(),
61
+ }
62
+
63
+ def render_sub_query_per_rel_type(rel_type: str, rel_def: FieldInfo) -> str:
64
+ subquery = [
65
+ "WITH peer_node, rb, active_attr",
66
+ f'WHERE type(rb) = "{rel_type}"',
67
+ ]
68
+ if rel_def.default.direction.value == "outbound":
69
+ subquery.append(f"CREATE (active_attr)-[:{rel_type} $rel_props ]->(peer_node)")
70
+ elif rel_def.default.direction.value == "inbound":
71
+ subquery.append(f"CREATE (active_attr)<-[:{rel_type} $rel_props ]-(peer_node)")
72
+ else:
73
+ subquery.append(f"CREATE (active_attr)-[:{rel_type} $rel_props ]-(peer_node)")
74
+
75
+ subquery.append("RETURN peer_node as p2")
76
+ return "\n".join(subquery)
77
+
78
+ sub_queries = [
79
+ render_sub_query_per_rel_type(rel_type, rel_def)
80
+ for rel_type, rel_def in GraphAttributeRelationships.model_fields.items()
81
+ ]
82
+ sub_query_all = "\nUNION\n".join(sub_queries)
83
+
84
+ node_kinds_str = "|".join(self.node_kinds + profile_kinds_to_update)
85
+ query = """
86
+ // Find all the active nodes
87
+ MATCH (node:%(node_kinds)s)
88
+ WHERE (size($kinds_to_ignore) = 0 OR NOT any(l IN labels(node) WHERE l IN $kinds_to_ignore))
89
+ AND exists((node)-[:HAS_ATTRIBUTE]-(:Attribute { name: $attr_name }))
90
+ CALL (node) {
91
+ MATCH (root:Root)<-[r:IS_PART_OF]-(node)
92
+ WHERE %(branch_filter)s
93
+ RETURN node as n1, r as r1
94
+ ORDER BY r.branch_level DESC, r.from DESC
95
+ LIMIT 1
96
+ }
97
+ WITH n1 as active_node, r1 as rb
98
+ WHERE rb.status = "active"
99
+ // Find all the attributes that need to be updated
100
+ CALL (active_node) {
101
+ MATCH (active_node)-[r:HAS_ATTRIBUTE]-(attr:Attribute { name: $attr_name })
102
+ WHERE %(branch_filter)s
103
+ RETURN active_node as n1, r as r1, attr as attr1
104
+ ORDER BY r.branch_level DESC, r.from DESC
105
+ LIMIT 1
106
+ }
107
+ WITH n1 as active_node, r1 as rb, attr1 as active_attr
108
+ WHERE rb.status = "active"
109
+ WITH active_attr
110
+ MATCH (active_attr)-[]-(peer)
111
+ WITH DISTINCT active_attr, peer
112
+ CALL (active_attr, peer) {
113
+ MATCH (active_attr)-[r]-(peer)
114
+ WHERE %(branch_filter)s
115
+ RETURN active_attr as a1, r as r1, peer as p1
116
+ ORDER BY r.branch_level DESC, r.from DESC
117
+ LIMIT 1
118
+ }
119
+ WITH a1 as active_attr, r1 as rb, p1 as peer_node
120
+ WHERE rb.status = "active"
121
+ CALL (peer_node, rb, active_attr) {
122
+ %(sub_query_all)s
123
+ }
124
+ WITH p2 as peer_node, rb, active_attr
125
+ FOREACH (i in CASE WHEN rb.branch = $branch_name THEN [1] ELSE [] END |
126
+ SET rb.to = $current_time
127
+ )
128
+ RETURN DISTINCT active_attr
129
+ """ % {
130
+ "branch_filter": branch_filter,
131
+ "sub_query_all": sub_query_all,
132
+ "node_kinds": node_kinds_str,
133
+ }
134
+ self.add_to_query(query)
@@ -0,0 +1,54 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Sequence
4
+
5
+ from infrahub.core import registry
6
+ from infrahub.core.constants import GLOBAL_BRANCH_NAME
7
+ from infrahub.core.migrations.graph import MIGRATIONS
8
+
9
+ from .exceptions import MigrationFailureError
10
+ from .shared import MigrationRequiringRebase
11
+
12
+ if TYPE_CHECKING:
13
+ from infrahub.core.branch import Branch
14
+ from infrahub.database import InfrahubDatabase
15
+
16
+
17
+ class MigrationRunner:
18
+ def __init__(self, branch: Branch) -> None:
19
+ if branch.name in (registry.default_branch, GLOBAL_BRANCH_NAME):
20
+ raise ValueError("MigrationRunner cannot be used to apply migration on default branches")
21
+
22
+ self.branch = branch
23
+ self.applicable_migrations = self._get_applicable_migrations()
24
+
25
+ def _get_applicable_migrations(self) -> Sequence[MigrationRequiringRebase]:
26
+ applicable_migrations = []
27
+ for migration_class in [m for m in MIGRATIONS if issubclass(m, MigrationRequiringRebase)]:
28
+ migration = migration_class.init()
29
+ if self.branch.graph_version and self.branch.graph_version > migration.minimum_version:
30
+ continue
31
+ applicable_migrations.append(migration)
32
+
33
+ return applicable_migrations
34
+
35
+ def has_migrations(self) -> bool:
36
+ return bool(self.applicable_migrations)
37
+
38
+ async def run(self, db: InfrahubDatabase) -> None:
39
+ if not self.has_migrations():
40
+ return
41
+
42
+ for migration in self.applicable_migrations:
43
+ execution_result = await migration.execute_against_branch(db=db, branch=self.branch)
44
+ validation_result = None
45
+
46
+ if execution_result.success:
47
+ validation_result = await migration.validate_migration(db=db)
48
+
49
+ if not execution_result.success or (validation_result and not validation_result.success):
50
+ if execution_result.errors:
51
+ raise MigrationFailureError(errors=execution_result.errors)
52
+
53
+ if validation_result and not validation_result.success:
54
+ raise MigrationFailureError(errors=validation_result.errors)
@@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any, Sequence
4
4
 
5
5
  from infrahub.types import is_large_attribute_type
6
6
 
7
- from ..query import AttributeMigrationQuery
7
+ from ..query import AttributeMigrationQuery, MigrationBaseQuery
8
8
  from ..shared import AttributeSchemaMigration, MigrationResult
9
9
 
10
10
  if TYPE_CHECKING:
@@ -147,10 +147,16 @@ class AttributeKindUpdateMigration(AttributeSchemaMigration):
147
147
  name: str = "attribute.kind.update"
148
148
  queries: Sequence[type[AttributeMigrationQuery]] = [AttributeKindUpdateMigrationQuery] # type: ignore[assignment]
149
149
 
150
- async def execute(self, db: InfrahubDatabase, branch: Branch, at: Timestamp | str | None = None) -> MigrationResult:
150
+ async def execute(
151
+ self,
152
+ db: InfrahubDatabase,
153
+ branch: Branch,
154
+ at: Timestamp | str | None = None,
155
+ queries: Sequence[type[MigrationBaseQuery]] | None = None,
156
+ ) -> MigrationResult:
151
157
  is_indexed_previous = is_large_attribute_type(self.previous_attribute_schema.kind)
152
158
  is_indexed_new = is_large_attribute_type(self.new_attribute_schema.kind)
153
159
  if is_indexed_previous is is_indexed_new:
154
160
  return MigrationResult()
155
161
 
156
- return await super().execute(db=db, branch=branch, at=at)
162
+ return await super().execute(db=db, branch=branch, at=at, queries=queries)
@@ -0,0 +1,90 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any, Sequence
4
+
5
+ from infrahub.core.migrations.query.attribute_remove import AttributeRemoveQuery
6
+ from infrahub.core.schema.generic_schema import GenericSchema
7
+ from infrahub.core.schema.node_schema import NodeSchema
8
+
9
+ from ..query import AttributeMigrationQuery, MigrationBaseQuery
10
+ from ..query.attribute_add import AttributeAddQuery
11
+ from ..shared import AttributeSchemaMigration, MigrationResult
12
+
13
+ if TYPE_CHECKING:
14
+ from infrahub.core.branch.models import Branch
15
+ from infrahub.core.schema import MainSchemaTypes
16
+ from infrahub.core.timestamp import Timestamp
17
+ from infrahub.database import InfrahubDatabase
18
+
19
+
20
+ def _get_node_kinds(schema: MainSchemaTypes) -> list[str]:
21
+ if not isinstance(schema, (NodeSchema, GenericSchema)):
22
+ return [schema.kind]
23
+ schema_kinds = [f"Profile{schema.kind}"]
24
+ if isinstance(schema, GenericSchema) and schema.used_by:
25
+ schema_kinds += [f"Profile{kind}" for kind in schema.used_by]
26
+ return schema_kinds
27
+
28
+
29
+ class ProfilesAttributeAddMigrationQuery(AttributeMigrationQuery, AttributeAddQuery):
30
+ name = "migration_profiles_attribute_add"
31
+
32
+ def __init__(
33
+ self,
34
+ migration: AttributeSchemaMigration,
35
+ **kwargs: Any,
36
+ ):
37
+ node_kinds = _get_node_kinds(migration.new_schema)
38
+ super().__init__(
39
+ migration=migration,
40
+ node_kinds=node_kinds,
41
+ attribute_name=migration.new_attribute_schema.name,
42
+ attribute_kind=migration.new_attribute_schema.kind,
43
+ branch_support=migration.new_attribute_schema.get_branch(),
44
+ default_value=migration.new_attribute_schema.default_value,
45
+ **kwargs,
46
+ )
47
+
48
+
49
+ class ProfilesAttributeRemoveMigrationQuery(AttributeMigrationQuery, AttributeRemoveQuery):
50
+ name = "migration_profiles_attribute_remove"
51
+
52
+ def __init__(
53
+ self,
54
+ migration: AttributeSchemaMigration,
55
+ **kwargs: Any,
56
+ ):
57
+ node_kinds = _get_node_kinds(migration.new_schema)
58
+ super().__init__(
59
+ migration=migration,
60
+ attribute_name=migration.new_attribute_schema.name,
61
+ node_kinds=node_kinds,
62
+ **kwargs,
63
+ )
64
+
65
+
66
+ class AttributeSupportsProfileUpdateMigration(AttributeSchemaMigration):
67
+ name: str = "attribute.supports_profile.update"
68
+ queries: Sequence[type[MigrationBaseQuery]] = []
69
+
70
+ async def execute(
71
+ self,
72
+ db: InfrahubDatabase,
73
+ branch: Branch,
74
+ at: Timestamp | str | None = None,
75
+ queries: Sequence[type[MigrationBaseQuery]] | None = None, # noqa: ARG002
76
+ ) -> MigrationResult:
77
+ if (
78
+ # no change in whether the attribute should be used on profiles
79
+ self.previous_attribute_schema.support_profiles == self.new_attribute_schema.support_profiles
80
+ # the attribute is new, so there cannot be existing profiles to update
81
+ or self.previous_attribute_schema.id is None
82
+ ):
83
+ return MigrationResult()
84
+ profiles_queries: list[type[AttributeMigrationQuery]] = []
85
+ if self.new_attribute_schema.support_profiles:
86
+ profiles_queries.append(ProfilesAttributeAddMigrationQuery)
87
+ if not self.new_attribute_schema.support_profiles:
88
+ profiles_queries.append(ProfilesAttributeRemoveMigrationQuery)
89
+
90
+ return await super().execute(db=db, branch=branch, at=at, queries=profiles_queries)
@@ -4,15 +4,19 @@ from typing import TYPE_CHECKING, Any, Sequence
4
4
 
5
5
  from infrahub.core import registry
6
6
  from infrahub.core.node import Node
7
+ from infrahub.core.schema.generic_schema import GenericSchema
8
+ from infrahub.core.schema.node_schema import NodeSchema
7
9
  from infrahub.exceptions import PoolExhaustedError
8
10
  from infrahub.tasks.registry import update_branch_registry
9
11
 
10
- from ..query import AttributeMigrationQuery
12
+ from ..query import AttributeMigrationQuery, MigrationBaseQuery
11
13
  from ..query.attribute_add import AttributeAddQuery
12
14
  from ..shared import AttributeSchemaMigration, MigrationResult
13
15
 
14
16
  if TYPE_CHECKING:
15
17
  from infrahub.core.node.resource_manager.number_pool import CoreNumberPool
18
+ from infrahub.core.schema import MainSchemaTypes
19
+ from infrahub.core.schema.attribute_schema import AttributeSchema
16
20
  from infrahub.database import InfrahubDatabase
17
21
 
18
22
  from ...branch import Branch
@@ -22,14 +26,27 @@ if TYPE_CHECKING:
22
26
  class NodeAttributeAddMigrationQuery01(AttributeMigrationQuery, AttributeAddQuery):
23
27
  name = "migration_node_attribute_add_01"
24
28
 
29
+ def _get_node_kinds(self, schema: MainSchemaTypes, new_attribute_schema: AttributeSchema) -> list[str]:
30
+ schema_kinds = [schema.kind]
31
+ if not isinstance(schema, (NodeSchema, GenericSchema)):
32
+ return schema_kinds
33
+ if new_attribute_schema.support_profiles:
34
+ schema_kinds.append(f"Profile{schema.kind}")
35
+ if isinstance(schema, GenericSchema) and schema.used_by:
36
+ schema_kinds.extend([f"Profile{kind}" for kind in schema.used_by])
37
+ return schema_kinds
38
+
25
39
  def __init__(
26
40
  self,
27
41
  migration: AttributeSchemaMigration,
28
42
  **kwargs: Any,
29
43
  ):
44
+ node_kinds = self._get_node_kinds(
45
+ schema=migration.new_schema, new_attribute_schema=migration.new_attribute_schema
46
+ )
30
47
  super().__init__(
31
48
  migration=migration,
32
- node_kind=migration.new_schema.kind,
49
+ node_kinds=node_kinds,
33
50
  attribute_name=migration.new_attribute_schema.name,
34
51
  attribute_kind=migration.new_attribute_schema.kind,
35
52
  branch_support=migration.new_attribute_schema.get_branch().value,
@@ -42,6 +59,17 @@ class NodeAttributeAddMigration(AttributeSchemaMigration):
42
59
  name: str = "node.attribute.add"
43
60
  queries: Sequence[type[AttributeMigrationQuery]] = [NodeAttributeAddMigrationQuery01] # type: ignore[assignment]
44
61
 
62
+ async def execute(
63
+ self,
64
+ db: InfrahubDatabase,
65
+ branch: Branch,
66
+ at: Timestamp | str | None = None,
67
+ queries: Sequence[type[MigrationBaseQuery]] | None = None,
68
+ ) -> MigrationResult:
69
+ if self.new_attribute_schema.inherited is True:
70
+ return MigrationResult()
71
+ return await super().execute(db=db, branch=branch, at=at, queries=queries)
72
+
45
73
  async def execute_post_queries(
46
74
  self,
47
75
  db: InfrahubDatabase,
@@ -52,8 +80,11 @@ class NodeAttributeAddMigration(AttributeSchemaMigration):
52
80
  if self.new_attribute_schema.kind != "NumberPool":
53
81
  return result
54
82
 
55
- number_pool: CoreNumberPool = await Node.fetch_or_create_number_pool( # type: ignore[assignment]
56
- db=db, branch=branch, schema_node=self.new_schema, schema_attribute=self.new_attribute_schema
83
+ number_pool: CoreNumberPool = await Node.fetch_or_create_number_pool(
84
+ db=db,
85
+ branch=branch,
86
+ schema_node=self.new_schema, # type: ignore
87
+ schema_attribute=self.new_attribute_schema,
57
88
  )
58
89
 
59
90
  await update_branch_registry(db=db, branch=branch)
@@ -1,123 +1,27 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING, Any, Sequence
4
-
5
- from infrahub.core.constants import RelationshipStatus
6
- from infrahub.core.graph.schema import GraphAttributeRelationships
7
- from infrahub.core.schema.generic_schema import GenericSchema
3
+ from typing import Any, Sequence
8
4
 
9
5
  from ..query import AttributeMigrationQuery
6
+ from ..query.attribute_remove import AttributeRemoveQuery
10
7
  from ..shared import AttributeSchemaMigration
11
8
 
12
- if TYPE_CHECKING:
13
- from pydantic.fields import FieldInfo
14
-
15
- from infrahub.database import InfrahubDatabase
16
-
17
9
 
18
- class NodeAttributeRemoveMigrationQuery01(AttributeMigrationQuery):
10
+ class NodeAttributeRemoveMigrationQuery01(AttributeMigrationQuery, AttributeRemoveQuery):
19
11
  name = "migration_node_attribute_remove_01"
20
12
  insert_return: bool = False
21
13
 
22
- async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002
23
- branch_filter, branch_params = self.branch.get_query_filter_path(at=self.at.to_string())
24
- self.params.update(branch_params)
25
-
26
- attr_name = self.migration.schema_path.field_name
27
- kinds_to_ignore = []
28
- if isinstance(self.migration.new_node_schema, GenericSchema) and attr_name is not None:
29
- for inheriting_schema_kind in self.migration.new_node_schema.used_by:
30
- node_schema = db.schema.get_node_schema(
31
- name=inheriting_schema_kind, branch=self.branch, duplicate=False
32
- )
33
- attr_schema = node_schema.get_attribute_or_none(name=attr_name)
34
- if attr_schema and not attr_schema.inherited:
35
- kinds_to_ignore.append(inheriting_schema_kind)
36
-
37
- self.params["node_kind"] = self.migration.new_schema.kind
38
- self.params["kinds_to_ignore"] = kinds_to_ignore
39
- self.params["attr_name"] = attr_name
40
- self.params["current_time"] = self.at.to_string()
41
- self.params["branch_name"] = self.branch.name
42
- self.params["branch_support"] = self.migration.previous_attribute_schema.get_branch().value
43
-
44
- self.params["rel_props"] = {
45
- "branch": self.branch.name,
46
- "branch_level": self.branch.hierarchy_level,
47
- "status": RelationshipStatus.DELETED.value,
48
- "from": self.at.to_string(),
49
- }
50
-
51
- def render_sub_query_per_rel_type(rel_type: str, rel_def: FieldInfo) -> str:
52
- subquery = [
53
- "WITH peer_node, rb, active_attr",
54
- f'WHERE type(rb) = "{rel_type}"',
55
- ]
56
- if rel_def.default.direction.value == "outbound":
57
- subquery.append(f"CREATE (active_attr)-[:{rel_type} $rel_props ]->(peer_node)")
58
- elif rel_def.default.direction.value == "inbound":
59
- subquery.append(f"CREATE (active_attr)<-[:{rel_type} $rel_props ]-(peer_node)")
60
- else:
61
- subquery.append(f"CREATE (active_attr)-[:{rel_type} $rel_props ]-(peer_node)")
62
-
63
- subquery.append("RETURN peer_node as p2")
64
- return "\n".join(subquery)
65
-
66
- sub_queries = [
67
- render_sub_query_per_rel_type(rel_type, rel_def)
68
- for rel_type, rel_def in GraphAttributeRelationships.model_fields.items()
69
- ]
70
- sub_query_all = "\nUNION\n".join(sub_queries)
71
-
72
- query = """
73
- // Find all the active nodes
74
- MATCH (node:%(node_kind)s)
75
- WHERE (size($kinds_to_ignore) = 0 OR NOT any(l IN labels(node) WHERE l IN $kinds_to_ignore))
76
- AND exists((node)-[:HAS_ATTRIBUTE]-(:Attribute { name: $attr_name }))
77
- CALL (node) {
78
- MATCH (root:Root)<-[r:IS_PART_OF]-(node)
79
- WHERE %(branch_filter)s
80
- RETURN node as n1, r as r1
81
- ORDER BY r.branch_level DESC, r.from DESC
82
- LIMIT 1
83
- }
84
- WITH n1 as active_node, r1 as rb
85
- WHERE rb.status = "active"
86
- // Find all the attributes that need to be updated
87
- CALL (active_node) {
88
- MATCH (active_node)-[r:HAS_ATTRIBUTE]-(attr:Attribute { name: $attr_name })
89
- WHERE %(branch_filter)s
90
- RETURN active_node as n1, r as r1, attr as attr1
91
- ORDER BY r.branch_level DESC, r.from DESC
92
- LIMIT 1
93
- }
94
- WITH n1 as active_node, r1 as rb, attr1 as active_attr
95
- WHERE rb.status = "active"
96
- WITH active_attr
97
- MATCH (active_attr)-[]-(peer)
98
- CALL (active_attr, peer) {
99
- MATCH (active_attr)-[r]-(peer)
100
- WHERE %(branch_filter)s
101
- RETURN active_attr as a1, r as r1, peer as p1
102
- ORDER BY r.branch_level DESC, r.from DESC
103
- LIMIT 1
104
- }
105
- WITH a1 as active_attr, r1 as rb, p1 as peer_node
106
- WHERE rb.status = "active"
107
- CALL (peer_node, rb, active_attr) {
108
- %(sub_query_all)s
109
- }
110
- WITH p2 as peer_node, rb, active_attr
111
- FOREACH (i in CASE WHEN rb.branch = $branch_name THEN [1] ELSE [] END |
112
- SET rb.to = $current_time
14
+ def __init__(
15
+ self,
16
+ migration: AttributeSchemaMigration,
17
+ **kwargs: Any,
18
+ ):
19
+ super().__init__(
20
+ migration=migration,
21
+ attribute_name=migration.previous_attribute_schema.name,
22
+ node_kinds=[migration.new_schema.kind],
23
+ **kwargs,
113
24
  )
114
- RETURN DISTINCT active_attr
115
- """ % {
116
- "branch_filter": branch_filter,
117
- "sub_query_all": sub_query_all,
118
- "node_kind": self.migration.new_schema.kind,
119
- }
120
- self.add_to_query(query)
121
25
 
122
26
 
123
27
  class NodeAttributeRemoveMigration(AttributeSchemaMigration):
@@ -2,8 +2,9 @@ from __future__ import annotations
2
2
 
3
3
  from typing import Any, Sequence
4
4
 
5
+ from ..query import MigrationQuery
5
6
  from ..query.node_duplicate import NodeDuplicateQuery, SchemaNodeInfo
6
- from ..shared import MigrationQuery, SchemaMigration
7
+ from ..shared import SchemaMigration
7
8
 
8
9
 
9
10
  class NodeKindUpdateMigrationQuery01(MigrationQuery, NodeDuplicateQuery):
@@ -5,7 +5,8 @@ from typing import TYPE_CHECKING, Any, Sequence
5
5
  from infrahub.core.constants import RelationshipStatus
6
6
  from infrahub.core.graph.schema import GraphNodeRelationships, GraphRelDirection
7
7
 
8
- from ..shared import MigrationQuery, SchemaMigration
8
+ from ..query import MigrationQuery
9
+ from ..shared import SchemaMigration
9
10
 
10
11
  if TYPE_CHECKING:
11
12
  from pydantic.fields import FieldInfo
@@ -4,9 +4,10 @@ from typing import Sequence
4
4
 
5
5
  from pydantic import Field
6
6
 
7
- from ..shared import MigrationQuery, SchemaMigration
7
+ from ..query import MigrationBaseQuery # noqa: TC001
8
+ from ..shared import SchemaMigration
8
9
 
9
10
 
10
11
  class PlaceholderDummyMigration(SchemaMigration):
11
12
  name: str = "dummy.placeholder"
12
- queries: Sequence[type[MigrationQuery]] = Field(default_factory=list)
13
+ queries: Sequence[type[MigrationBaseQuery]] = Field(default_factory=list)