infrahub-server 1.4.12__py3-none-any.whl → 1.5.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 (234) hide show
  1. infrahub/actions/tasks.py +208 -16
  2. infrahub/api/artifact.py +3 -0
  3. infrahub/api/diff/diff.py +1 -1
  4. infrahub/api/internal.py +2 -0
  5. infrahub/api/query.py +2 -0
  6. infrahub/api/schema.py +27 -3
  7. infrahub/auth.py +5 -5
  8. infrahub/cli/__init__.py +2 -0
  9. infrahub/cli/db.py +160 -157
  10. infrahub/cli/dev.py +118 -0
  11. infrahub/cli/tasks.py +46 -0
  12. infrahub/cli/upgrade.py +56 -9
  13. infrahub/computed_attribute/tasks.py +19 -7
  14. infrahub/config.py +7 -2
  15. infrahub/core/attribute.py +35 -24
  16. infrahub/core/branch/enums.py +1 -1
  17. infrahub/core/branch/models.py +9 -5
  18. infrahub/core/branch/needs_rebase_status.py +11 -0
  19. infrahub/core/branch/tasks.py +72 -10
  20. infrahub/core/changelog/models.py +2 -10
  21. infrahub/core/constants/__init__.py +4 -0
  22. infrahub/core/constants/infrahubkind.py +1 -0
  23. infrahub/core/convert_object_type/object_conversion.py +201 -0
  24. infrahub/core/convert_object_type/repository_conversion.py +89 -0
  25. infrahub/core/convert_object_type/schema_mapping.py +27 -3
  26. infrahub/core/diff/calculator.py +2 -2
  27. infrahub/core/diff/model/path.py +4 -0
  28. infrahub/core/diff/payload_builder.py +1 -1
  29. infrahub/core/diff/query/artifact.py +1 -0
  30. infrahub/core/diff/query/delete_query.py +9 -5
  31. infrahub/core/diff/query/field_summary.py +1 -0
  32. infrahub/core/diff/query/merge.py +39 -23
  33. infrahub/core/graph/__init__.py +1 -1
  34. infrahub/core/initialization.py +7 -4
  35. infrahub/core/manager.py +3 -81
  36. infrahub/core/migrations/__init__.py +3 -0
  37. infrahub/core/migrations/exceptions.py +4 -0
  38. infrahub/core/migrations/graph/__init__.py +13 -10
  39. infrahub/core/migrations/graph/load_schema_branch.py +21 -0
  40. infrahub/core/migrations/graph/m013_convert_git_password_credential.py +1 -1
  41. infrahub/core/migrations/graph/m037_index_attr_vals.py +11 -30
  42. infrahub/core/migrations/graph/m039_ipam_reconcile.py +9 -7
  43. infrahub/core/migrations/graph/m041_deleted_dup_edges.py +149 -0
  44. infrahub/core/migrations/graph/m042_profile_attrs_in_db.py +147 -0
  45. infrahub/core/migrations/graph/m043_create_hfid_display_label_in_db.py +164 -0
  46. infrahub/core/migrations/graph/m044_backfill_hfid_display_label_in_db.py +864 -0
  47. infrahub/core/migrations/query/__init__.py +7 -8
  48. infrahub/core/migrations/query/attribute_add.py +8 -6
  49. infrahub/core/migrations/query/attribute_remove.py +134 -0
  50. infrahub/core/migrations/runner.py +54 -0
  51. infrahub/core/migrations/schema/attribute_kind_update.py +9 -3
  52. infrahub/core/migrations/schema/attribute_supports_profile.py +90 -0
  53. infrahub/core/migrations/schema/node_attribute_add.py +26 -5
  54. infrahub/core/migrations/schema/node_attribute_remove.py +13 -109
  55. infrahub/core/migrations/schema/node_kind_update.py +2 -1
  56. infrahub/core/migrations/schema/node_remove.py +2 -1
  57. infrahub/core/migrations/schema/placeholder_dummy.py +3 -2
  58. infrahub/core/migrations/shared.py +66 -19
  59. infrahub/core/models.py +2 -2
  60. infrahub/core/node/__init__.py +207 -54
  61. infrahub/core/node/create.py +53 -49
  62. infrahub/core/node/lock_utils.py +124 -0
  63. infrahub/core/node/node_property_attribute.py +230 -0
  64. infrahub/core/node/resource_manager/ip_address_pool.py +2 -1
  65. infrahub/core/node/resource_manager/ip_prefix_pool.py +2 -1
  66. infrahub/core/node/resource_manager/number_pool.py +2 -1
  67. infrahub/core/node/standard.py +1 -1
  68. infrahub/core/property.py +11 -0
  69. infrahub/core/protocols.py +8 -1
  70. infrahub/core/query/attribute.py +82 -15
  71. infrahub/core/query/diff.py +61 -16
  72. infrahub/core/query/ipam.py +16 -4
  73. infrahub/core/query/node.py +92 -212
  74. infrahub/core/query/relationship.py +44 -26
  75. infrahub/core/query/subquery.py +0 -8
  76. infrahub/core/relationship/model.py +69 -24
  77. infrahub/core/schema/__init__.py +56 -0
  78. infrahub/core/schema/attribute_schema.py +4 -2
  79. infrahub/core/schema/basenode_schema.py +42 -2
  80. infrahub/core/schema/definitions/core/__init__.py +2 -0
  81. infrahub/core/schema/definitions/core/check.py +1 -1
  82. infrahub/core/schema/definitions/core/generator.py +2 -0
  83. infrahub/core/schema/definitions/core/group.py +16 -2
  84. infrahub/core/schema/definitions/core/repository.py +7 -0
  85. infrahub/core/schema/definitions/core/transform.py +1 -1
  86. infrahub/core/schema/definitions/internal.py +12 -3
  87. infrahub/core/schema/generated/attribute_schema.py +2 -2
  88. infrahub/core/schema/generated/base_node_schema.py +6 -1
  89. infrahub/core/schema/manager.py +3 -0
  90. infrahub/core/schema/node_schema.py +1 -0
  91. infrahub/core/schema/relationship_schema.py +0 -1
  92. infrahub/core/schema/schema_branch.py +295 -10
  93. infrahub/core/schema/schema_branch_display.py +135 -0
  94. infrahub/core/schema/schema_branch_hfid.py +120 -0
  95. infrahub/core/validators/aggregated_checker.py +1 -1
  96. infrahub/database/graph.py +21 -0
  97. infrahub/display_labels/__init__.py +0 -0
  98. infrahub/display_labels/gather.py +48 -0
  99. infrahub/display_labels/models.py +240 -0
  100. infrahub/display_labels/tasks.py +192 -0
  101. infrahub/display_labels/triggers.py +22 -0
  102. infrahub/events/branch_action.py +27 -1
  103. infrahub/events/group_action.py +1 -1
  104. infrahub/events/node_action.py +1 -1
  105. infrahub/generators/constants.py +7 -0
  106. infrahub/generators/models.py +38 -12
  107. infrahub/generators/tasks.py +34 -16
  108. infrahub/git/base.py +42 -2
  109. infrahub/git/integrator.py +22 -14
  110. infrahub/git/tasks.py +52 -2
  111. infrahub/graphql/analyzer.py +9 -0
  112. infrahub/graphql/api/dependencies.py +2 -4
  113. infrahub/graphql/api/endpoints.py +16 -6
  114. infrahub/graphql/app.py +2 -4
  115. infrahub/graphql/initialization.py +2 -3
  116. infrahub/graphql/manager.py +213 -137
  117. infrahub/graphql/middleware.py +12 -0
  118. infrahub/graphql/mutations/branch.py +16 -0
  119. infrahub/graphql/mutations/computed_attribute.py +110 -3
  120. infrahub/graphql/mutations/convert_object_type.py +44 -13
  121. infrahub/graphql/mutations/display_label.py +118 -0
  122. infrahub/graphql/mutations/generator.py +25 -7
  123. infrahub/graphql/mutations/hfid.py +125 -0
  124. infrahub/graphql/mutations/ipam.py +73 -41
  125. infrahub/graphql/mutations/main.py +61 -178
  126. infrahub/graphql/mutations/profile.py +195 -0
  127. infrahub/graphql/mutations/proposed_change.py +8 -1
  128. infrahub/graphql/mutations/relationship.py +2 -2
  129. infrahub/graphql/mutations/repository.py +22 -83
  130. infrahub/graphql/mutations/resource_manager.py +2 -2
  131. infrahub/graphql/mutations/webhook.py +1 -1
  132. infrahub/graphql/queries/resource_manager.py +1 -1
  133. infrahub/graphql/registry.py +173 -0
  134. infrahub/graphql/resolvers/resolver.py +2 -0
  135. infrahub/graphql/schema.py +8 -1
  136. infrahub/graphql/schema_sort.py +170 -0
  137. infrahub/graphql/types/branch.py +4 -1
  138. infrahub/graphql/types/enums.py +3 -0
  139. infrahub/groups/tasks.py +1 -1
  140. infrahub/hfid/__init__.py +0 -0
  141. infrahub/hfid/gather.py +48 -0
  142. infrahub/hfid/models.py +240 -0
  143. infrahub/hfid/tasks.py +191 -0
  144. infrahub/hfid/triggers.py +22 -0
  145. infrahub/lock.py +119 -42
  146. infrahub/locks/__init__.py +0 -0
  147. infrahub/locks/tasks.py +37 -0
  148. infrahub/message_bus/types.py +1 -0
  149. infrahub/patch/plan_writer.py +2 -2
  150. infrahub/permissions/constants.py +2 -0
  151. infrahub/profiles/__init__.py +0 -0
  152. infrahub/profiles/node_applier.py +101 -0
  153. infrahub/profiles/queries/__init__.py +0 -0
  154. infrahub/profiles/queries/get_profile_data.py +98 -0
  155. infrahub/profiles/tasks.py +63 -0
  156. infrahub/proposed_change/tasks.py +67 -14
  157. infrahub/repositories/__init__.py +0 -0
  158. infrahub/repositories/create_repository.py +113 -0
  159. infrahub/server.py +9 -1
  160. infrahub/services/__init__.py +8 -5
  161. infrahub/services/adapters/http/__init__.py +5 -0
  162. infrahub/services/adapters/workflow/worker.py +14 -3
  163. infrahub/task_manager/event.py +5 -0
  164. infrahub/task_manager/models.py +7 -0
  165. infrahub/task_manager/task.py +73 -0
  166. infrahub/tasks/registry.py +6 -4
  167. infrahub/trigger/catalogue.py +4 -0
  168. infrahub/trigger/models.py +2 -0
  169. infrahub/trigger/setup.py +13 -4
  170. infrahub/trigger/tasks.py +6 -0
  171. infrahub/webhook/models.py +1 -1
  172. infrahub/workers/dependencies.py +3 -1
  173. infrahub/workers/infrahub_async.py +10 -2
  174. infrahub/workflows/catalogue.py +118 -3
  175. infrahub/workflows/initialization.py +21 -0
  176. infrahub/workflows/models.py +17 -2
  177. infrahub/workflows/utils.py +2 -1
  178. infrahub_sdk/branch.py +17 -8
  179. infrahub_sdk/checks.py +1 -1
  180. infrahub_sdk/client.py +376 -95
  181. infrahub_sdk/config.py +29 -2
  182. infrahub_sdk/convert_object_type.py +61 -0
  183. infrahub_sdk/ctl/branch.py +3 -0
  184. infrahub_sdk/ctl/check.py +2 -3
  185. infrahub_sdk/ctl/cli_commands.py +20 -12
  186. infrahub_sdk/ctl/config.py +8 -2
  187. infrahub_sdk/ctl/generator.py +6 -3
  188. infrahub_sdk/ctl/graphql.py +184 -0
  189. infrahub_sdk/ctl/repository.py +39 -1
  190. infrahub_sdk/ctl/schema.py +40 -10
  191. infrahub_sdk/ctl/task.py +110 -0
  192. infrahub_sdk/ctl/utils.py +4 -0
  193. infrahub_sdk/ctl/validate.py +5 -3
  194. infrahub_sdk/diff.py +4 -5
  195. infrahub_sdk/exceptions.py +2 -0
  196. infrahub_sdk/generator.py +7 -1
  197. infrahub_sdk/graphql/__init__.py +12 -0
  198. infrahub_sdk/graphql/constants.py +1 -0
  199. infrahub_sdk/graphql/plugin.py +85 -0
  200. infrahub_sdk/graphql/query.py +77 -0
  201. infrahub_sdk/{graphql.py → graphql/renderers.py} +88 -75
  202. infrahub_sdk/graphql/utils.py +40 -0
  203. infrahub_sdk/node/attribute.py +2 -0
  204. infrahub_sdk/node/node.py +28 -20
  205. infrahub_sdk/node/relationship.py +1 -3
  206. infrahub_sdk/playback.py +1 -2
  207. infrahub_sdk/protocols.py +54 -6
  208. infrahub_sdk/pytest_plugin/plugin.py +7 -4
  209. infrahub_sdk/pytest_plugin/utils.py +40 -0
  210. infrahub_sdk/repository.py +1 -2
  211. infrahub_sdk/schema/__init__.py +70 -4
  212. infrahub_sdk/schema/main.py +1 -0
  213. infrahub_sdk/schema/repository.py +8 -0
  214. infrahub_sdk/spec/models.py +7 -0
  215. infrahub_sdk/spec/object.py +54 -6
  216. infrahub_sdk/spec/processors/__init__.py +0 -0
  217. infrahub_sdk/spec/processors/data_processor.py +10 -0
  218. infrahub_sdk/spec/processors/factory.py +34 -0
  219. infrahub_sdk/spec/processors/range_expand_processor.py +56 -0
  220. infrahub_sdk/spec/range_expansion.py +118 -0
  221. infrahub_sdk/task/models.py +6 -4
  222. infrahub_sdk/timestamp.py +18 -6
  223. infrahub_sdk/transforms.py +1 -1
  224. {infrahub_server-1.4.12.dist-info → infrahub_server-1.5.0.dist-info}/METADATA +9 -10
  225. {infrahub_server-1.4.12.dist-info → infrahub_server-1.5.0.dist-info}/RECORD +233 -176
  226. infrahub_testcontainers/container.py +114 -2
  227. infrahub_testcontainers/docker-compose-cluster.test.yml +5 -0
  228. infrahub_testcontainers/docker-compose.test.yml +5 -0
  229. infrahub_testcontainers/models.py +2 -2
  230. infrahub_testcontainers/performance_test.py +4 -4
  231. infrahub/core/convert_object_type/conversion.py +0 -134
  232. {infrahub_server-1.4.12.dist-info → infrahub_server-1.5.0.dist-info}/LICENSE.txt +0 -0
  233. {infrahub_server-1.4.12.dist-info → infrahub_server-1.5.0.dist-info}/WHEEL +0 -0
  234. {infrahub_server-1.4.12.dist-info → infrahub_server-1.5.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,149 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from rich import print as rprint
6
+
7
+ from infrahub.core.branch import Branch
8
+ from infrahub.core.diff.repository.repository import DiffRepository
9
+ from infrahub.core.initialization import get_root_node
10
+ from infrahub.core.migrations.shared import MigrationResult
11
+ from infrahub.core.query import Query, QueryType
12
+ from infrahub.dependencies.registry import build_component_registry, get_component_registry
13
+ from infrahub.log import get_logger
14
+
15
+ from ..shared import ArbitraryMigration
16
+
17
+ if TYPE_CHECKING:
18
+ from infrahub.database import InfrahubDatabase
19
+
20
+ log = get_logger()
21
+
22
+
23
+ class DeletePosthumousEdges(Query):
24
+ name = "delete_posthumous_edges_query"
25
+ type = QueryType.WRITE
26
+ insert_return = False
27
+
28
+ async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa: ARG002
29
+ query = """
30
+ // ------------
31
+ // find deleted nodes
32
+ // ------------
33
+ MATCH (n:Node)-[e:IS_PART_OF]->(:Root)
34
+ WHERE e.status = "deleted" OR e.to IS NOT NULL
35
+ WITH DISTINCT n, e.branch AS delete_branch, e.branch_level AS delete_branch_level, CASE
36
+ WHEN e.status = "deleted" THEN e.from
37
+ ELSE e.to
38
+ END AS delete_time
39
+ // ------------
40
+ // find the edges added to the deleted node after the delete time
41
+ // ------------
42
+ MATCH (n)-[added_e]-(peer)
43
+ WHERE added_e.from > delete_time
44
+ AND type(added_e) <> "IS_PART_OF"
45
+ // if the node was deleted on a branch (delete_branch_level > 1), and then updated on main/global (added_e.branch_level = 1), we can ignore it
46
+ AND added_e.branch_level >= delete_branch_level
47
+ AND (added_e.branch = delete_branch OR delete_branch_level = 1)
48
+ WITH DISTINCT n, delete_branch, delete_time, added_e, peer
49
+ // ------------
50
+ // get the branched_from for the branch on which the node was deleted
51
+ // ------------
52
+ CALL (added_e) {
53
+ MATCH (b:Branch {name: added_e.branch})
54
+ RETURN b.branched_from AS added_e_branched_from
55
+ }
56
+ // ------------
57
+ // account for the following situations, given that the edge update time is after the node delete time
58
+ // - deleted on main/global, updated on branch
59
+ // - illegal if the delete is before branch.branched_from
60
+ // - deleted on branch, updated on branch
61
+ // - illegal
62
+ // ------------
63
+ WITH n, delete_branch, delete_time, added_e, peer
64
+ WHERE delete_branch = added_e.branch
65
+ OR delete_time < added_e_branched_from
66
+ DELETE added_e
67
+ """
68
+ self.add_to_query(query)
69
+
70
+
71
+ class DeleteDuplicateEdgesForMigratedKindNodes(Query):
72
+ name = "delete_duplicate_edges_for_migrated_kind_nodes_query"
73
+ type = QueryType.WRITE
74
+ insert_return = False
75
+
76
+ async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa: ARG002
77
+ query = """
78
+ // ------------
79
+ // get UUIDs for migrated kind/inheritance nodes
80
+ // ------------
81
+ MATCH (n:Node)
82
+ WITH n.uuid AS node_uuid, count(*) AS num_nodes_with_uuid
83
+ WHERE num_nodes_with_uuid > 1
84
+ CALL (node_uuid) {
85
+ // ------------
86
+ // find any Relationships for these nodes
87
+ // ------------
88
+ MATCH (n:Node {uuid: node_uuid})-[:IS_RELATED]-(rel:Relationship)
89
+ WITH DISTINCT rel
90
+ MATCH (rel)-[e]->(peer)
91
+ WITH
92
+ type(e) AS e_type,
93
+ e.branch AS e_branch,
94
+ e.from AS e_from,
95
+ e.to AS e_to,
96
+ e.status AS e_status,
97
+ e.peer AS e_peer,
98
+ CASE
99
+ WHEN startNode(e) = rel THEN "out" ELSE "in"
100
+ END AS direction,
101
+ collect(e) AS duplicate_edges
102
+ WHERE size(duplicate_edges) > 1
103
+ WITH tail(duplicate_edges) AS duplicate_edges_to_delete
104
+ UNWIND duplicate_edges_to_delete AS edge_to_delete
105
+ DELETE edge_to_delete
106
+ } IN TRANSACTIONS OF 500 ROWS
107
+ """
108
+ self.add_to_query(query)
109
+
110
+
111
+ class Migration041(ArbitraryMigration):
112
+ """Clean up improper merges that duplicated edges to nodes with migrated kinds
113
+
114
+ - delete all existing diffs b/c they could contain incorrect nodes linking to deleted nodes with migrated kind/inheritance
115
+ - delete all edges added to any nodes AFTER they were deleted on main
116
+ - delete any duplicate edges touching migrated kind/inheritance nodes on main
117
+ """
118
+
119
+ name: str = "041_deleted_dup_edges"
120
+ minimum_version: int = 40
121
+
122
+ async def validate_migration(self, db: InfrahubDatabase) -> MigrationResult: # noqa: ARG002
123
+ result = MigrationResult()
124
+
125
+ return result
126
+
127
+ async def execute(self, db: InfrahubDatabase) -> MigrationResult:
128
+ root_node = await get_root_node(db=db)
129
+ default_branch_name = root_node.default_branch
130
+ default_branch = await Branch.get_by_name(db=db, name=default_branch_name)
131
+
132
+ rprint("Deleting all diffs", end="...")
133
+ build_component_registry()
134
+ component_registry = get_component_registry()
135
+ diff_repo = await component_registry.get_component(DiffRepository, db=db, branch=default_branch)
136
+ await diff_repo.delete_all_diff_roots()
137
+ rprint("done")
138
+
139
+ rprint("Deleting edges merged after node deleted", end="...")
140
+ delete_posthumous_edges_query = await DeletePosthumousEdges.init(db=db)
141
+ await delete_posthumous_edges_query.execute(db=db)
142
+ rprint("done")
143
+
144
+ rprint("Deleting duplicate edges for migrated kind/inheritance nodes", end="...")
145
+ delete_duplicate_edges_query = await DeleteDuplicateEdgesForMigratedKindNodes.init(db=db)
146
+ await delete_duplicate_edges_query.execute(db=db)
147
+ rprint("done")
148
+
149
+ return MigrationResult()
@@ -0,0 +1,147 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from rich.progress import Progress
6
+
7
+ from infrahub.core.branch.models import Branch
8
+ from infrahub.core.initialization import get_root_node
9
+ from infrahub.core.manager import NodeManager
10
+ from infrahub.core.migrations.shared import MigrationResult, get_migration_console
11
+ from infrahub.core.query import Query, QueryType
12
+ from infrahub.core.timestamp import Timestamp
13
+ from infrahub.log import get_logger
14
+ from infrahub.profiles.node_applier import NodeProfilesApplier
15
+
16
+ from ..shared import MigrationRequiringRebase
17
+ from .load_schema_branch import get_or_load_schema_branch
18
+
19
+ if TYPE_CHECKING:
20
+ from infrahub.database import InfrahubDatabase
21
+
22
+ log = get_logger()
23
+
24
+
25
+ class GetUpdatedProfilesForBranchQuery(Query):
26
+ """
27
+ Get CoreProfile UUIDs with updated attributes on this branch
28
+ """
29
+
30
+ name = "get_profiles_by_branch"
31
+ type = QueryType.READ
32
+
33
+ async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002
34
+ self.params["branch"] = self.branch.name
35
+ query = """
36
+ MATCH (profile:CoreProfile)-[:HAS_ATTRIBUTE]->(attr:Attribute)-[e:HAS_VALUE]->(:AttributeValue)
37
+ WHERE e.branch = $branch
38
+ WITH DISTINCT profile.uuid AS profile_uuid
39
+ """
40
+ self.add_to_query(query)
41
+ self.return_labels = ["profile_uuid"]
42
+
43
+ def get_profile_ids(self) -> list[str]:
44
+ """Get list of updated profile UUIDs"""
45
+ return [result.get_as_type("profile_uuid", str) for result in self.get_results()]
46
+
47
+
48
+ class GetNodesWithProfileUpdatesForBranchQuery(Query):
49
+ """
50
+ Get Node UUIDs by which branches they have updated profiles on
51
+ """
52
+
53
+ name = "get_nodes_with_profile_updates_by_branch"
54
+ type = QueryType.READ
55
+
56
+ async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002
57
+ self.params["branch"] = self.branch.name
58
+ query = """
59
+ MATCH (node:Node)-[e:IS_RELATED]->(:Relationship {name: "node__profile"})
60
+ WHERE NOT node:CoreProfile
61
+ AND e.branch = $branch
62
+ WITH DISTINCT node.uuid AS node_uuid
63
+ """
64
+ self.add_to_query(query)
65
+ self.return_labels = ["node_uuid"]
66
+
67
+ def get_node_ids(self) -> list[str]:
68
+ """Get list of updated node UUIDs"""
69
+ return [result.get_as_type("node_uuid", str) for result in self.get_results()]
70
+
71
+
72
+ class Migration042(MigrationRequiringRebase):
73
+ """
74
+ Save profile attribute values on each node using the profile in the database
75
+ For any profile that has updates on a given branch (including default branch)
76
+ - run NodeProfilesApplier.apply_profiles on each node related to the profile on that branch
77
+ For any node that has an updated relationship to a profile on a given branch
78
+ - run NodeProfilesApplier.apply_profiles on the node on that branch
79
+ """
80
+
81
+ name: str = "042_profile_attrs_in_db"
82
+ minimum_version: int = 41
83
+
84
+ def _get_profile_applier(self, db: InfrahubDatabase, branch: Branch) -> NodeProfilesApplier:
85
+ return NodeProfilesApplier(db=db, branch=branch)
86
+
87
+ async def validate_migration(self, db: InfrahubDatabase) -> MigrationResult: # noqa: ARG002
88
+ return MigrationResult()
89
+
90
+ async def execute(self, db: InfrahubDatabase) -> MigrationResult:
91
+ root_node = await get_root_node(db=db, initialize=False)
92
+ default_branch_name = root_node.default_branch
93
+ default_branch = await Branch.get_by_name(db=db, name=default_branch_name)
94
+ return await self._do_execute_for_branch(db=db, branch=default_branch)
95
+
96
+ async def execute_against_branch(self, db: InfrahubDatabase, branch: Branch) -> MigrationResult:
97
+ return await self._do_execute_for_branch(db=db, branch=branch)
98
+
99
+ async def _do_execute_for_branch(self, db: InfrahubDatabase, branch: Branch) -> MigrationResult:
100
+ console = get_migration_console()
101
+ result = MigrationResult()
102
+ await get_or_load_schema_branch(db=db, branch=branch)
103
+
104
+ console.print(f"Gathering profiles for branch {branch.name}...", end="")
105
+ get_updated_profiles_for_branch_query = await GetUpdatedProfilesForBranchQuery.init(db=db, branch=branch)
106
+ await get_updated_profiles_for_branch_query.execute(db=db)
107
+ profile_ids = get_updated_profiles_for_branch_query.get_profile_ids()
108
+
109
+ profiles_map = await NodeManager.get_many(db=db, branch=branch, ids=list(profile_ids))
110
+ console.print("done")
111
+
112
+ node_ids_to_update: set[str] = set()
113
+ with Progress(console=console) as progress:
114
+ gather_nodes_task = progress.add_task(
115
+ f"Gathering affected objects for each profile on branch {branch.name}...", total=len(profiles_map)
116
+ )
117
+
118
+ for profile in profiles_map.values():
119
+ node_relationship_manager = profile.get_relationship("related_nodes")
120
+ node_peers = await node_relationship_manager.get_db_peers(db=db)
121
+ node_ids_to_update.update(str(peer.peer_id) for peer in node_peers)
122
+ progress.update(gather_nodes_task, advance=1)
123
+ console.log(f"Collected nodes impacted by profiles on branch {branch.name}.")
124
+
125
+ console.print("Identifying nodes with profile updates by branch...", end="")
126
+ get_nodes_with_profile_updates_by_branch_query = await GetNodesWithProfileUpdatesForBranchQuery.init(
127
+ db=db, branch=branch
128
+ )
129
+ await get_nodes_with_profile_updates_by_branch_query.execute(db=db)
130
+ node_ids_to_update.update(get_nodes_with_profile_updates_by_branch_query.get_node_ids())
131
+ console.print("done")
132
+
133
+ right_now = Timestamp()
134
+ console.log("Applying profiles to nodes...")
135
+ with Progress(console=console) as progress:
136
+ apply_task = progress.add_task("Applying profiles to nodes...", total=len(node_ids_to_update))
137
+ applier = self._get_profile_applier(db=db, branch=branch)
138
+ for node_id in node_ids_to_update:
139
+ node = await NodeManager.get_one(db=db, branch=branch, id=node_id, at=right_now)
140
+ if node:
141
+ updated_field_names = await applier.apply_profiles(node=node)
142
+ if updated_field_names:
143
+ await node.save(db=db, fields=updated_field_names, at=right_now)
144
+ progress.update(apply_task, advance=1)
145
+ console.log("Completed applying profiles to nodes.")
146
+
147
+ return result
@@ -0,0 +1,164 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from rich.progress import Progress
6
+
7
+ from infrahub.core import registry
8
+ from infrahub.core.branch import Branch
9
+ from infrahub.core.constants import SchemaPathType
10
+ from infrahub.core.initialization import get_root_node
11
+ from infrahub.core.migrations.schema.node_attribute_add import NodeAttributeAddMigration
12
+ from infrahub.core.migrations.shared import MigrationRequiringRebase, MigrationResult, get_migration_console
13
+ from infrahub.core.path import SchemaPath
14
+ from infrahub.core.query import Query, QueryType
15
+
16
+ from .load_schema_branch import get_or_load_schema_branch
17
+
18
+ if TYPE_CHECKING:
19
+ from infrahub.database import InfrahubDatabase
20
+
21
+
22
+ class GetAddedNodesByKindForBranchQuery(Query):
23
+ name = "get_added_nodes_by_kind_for_branch_query"
24
+ type = QueryType.READ
25
+ insert_return = True
26
+
27
+ async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002
28
+ self.params["branch"] = self.branch.name
29
+ query = """
30
+ MATCH (n:Node)-[e:IS_PART_OF {branch: $branch, status: "active"}]->(:Root)
31
+ WHERE e.to IS NULL
32
+ AND NOT exists((n)-[:IS_PART_OF {branch: $branch, status: "deleted"}]->(:Root))
33
+ WITH n.kind AS kind, collect(n.uuid) AS node_ids
34
+ """
35
+ self.return_labels = ["kind", "node_ids"]
36
+ self.add_to_query(query)
37
+
38
+ def get_node_ids_by_kind(self) -> dict[str, list[str]]:
39
+ node_ids_by_kind: dict[str, list[str]] = {}
40
+ for result in self.get_results():
41
+ kind = result.get_as_type(label="kind", return_type=str)
42
+ node_ids: list[str] = result.get_as_type(label="node_ids", return_type=list)
43
+ node_ids_by_kind[kind] = node_ids
44
+ return node_ids_by_kind
45
+
46
+
47
+ class Migration043(MigrationRequiringRebase):
48
+ name: str = "043_create_hfid_display_label_in_db"
49
+ minimum_version: int = 42
50
+
51
+ async def execute(self, db: InfrahubDatabase) -> MigrationResult:
52
+ result = MigrationResult()
53
+
54
+ root_node = await get_root_node(db=db, initialize=False)
55
+ default_branch_name = root_node.default_branch
56
+ default_branch = await Branch.get_by_name(db=db, name=default_branch_name)
57
+ main_schema_branch = await get_or_load_schema_branch(db=db, branch=default_branch)
58
+ schema_node = main_schema_branch.get_node(name="SchemaNode")
59
+ schema_generic = main_schema_branch.get_node(name="SchemaGeneric")
60
+
61
+ migrations = [
62
+ # HFID is not needed, it was introduced at graph v8
63
+ NodeAttributeAddMigration(
64
+ new_node_schema=schema_node,
65
+ previous_node_schema=schema_node,
66
+ schema_path=SchemaPath(
67
+ schema_kind="SchemaNode", path_type=SchemaPathType.ATTRIBUTE, field_name="display_label"
68
+ ),
69
+ ),
70
+ NodeAttributeAddMigration(
71
+ new_node_schema=schema_generic,
72
+ previous_node_schema=schema_generic,
73
+ schema_path=SchemaPath(
74
+ schema_kind="SchemaGeneric", path_type=SchemaPathType.ATTRIBUTE, field_name="display_label"
75
+ ),
76
+ ),
77
+ ]
78
+
79
+ for node_schema_kind in main_schema_branch.node_names:
80
+ schema = main_schema_branch.get(name=node_schema_kind, duplicate=False)
81
+ migrations.extend(
82
+ [
83
+ NodeAttributeAddMigration(
84
+ new_node_schema=schema,
85
+ previous_node_schema=schema,
86
+ schema_path=SchemaPath(
87
+ schema_kind=schema.kind, path_type=SchemaPathType.ATTRIBUTE, field_name="human_friendly_id"
88
+ ),
89
+ ),
90
+ NodeAttributeAddMigration(
91
+ new_node_schema=schema,
92
+ previous_node_schema=schema,
93
+ schema_path=SchemaPath(
94
+ schema_kind=schema.kind, path_type=SchemaPathType.ATTRIBUTE, field_name="display_label"
95
+ ),
96
+ ),
97
+ ]
98
+ )
99
+
100
+ with Progress(console=get_migration_console()) as progress:
101
+ update_task = progress.add_task("Adding HFID and display label to nodes", total=len(migrations))
102
+
103
+ for migration in migrations:
104
+ try:
105
+ execution_result = await migration.execute(db=db, branch=default_branch)
106
+ result.errors.extend(execution_result.errors)
107
+ progress.update(update_task, advance=1)
108
+ except Exception as exc:
109
+ result.errors.append(str(exc))
110
+ return result
111
+
112
+ return result
113
+
114
+ async def execute_against_branch(self, db: InfrahubDatabase, branch: Branch) -> MigrationResult:
115
+ result = MigrationResult()
116
+
117
+ schema_branch = await registry.schema.load_schema_from_db(db=db, branch=branch)
118
+
119
+ migrations = []
120
+ get_added_nodes_by_kind_for_branch_query = await GetAddedNodesByKindForBranchQuery.init(db=db, branch=branch)
121
+ await get_added_nodes_by_kind_for_branch_query.execute(db=db)
122
+ node_ids_by_kind = get_added_nodes_by_kind_for_branch_query.get_node_ids_by_kind()
123
+
124
+ for node_kind, node_ids in node_ids_by_kind.items():
125
+ schema = schema_branch.get(name=node_kind, duplicate=False)
126
+ migrations.extend(
127
+ [
128
+ NodeAttributeAddMigration(
129
+ uuids=node_ids,
130
+ new_node_schema=schema,
131
+ previous_node_schema=schema,
132
+ schema_path=SchemaPath(
133
+ schema_kind=schema.kind, path_type=SchemaPathType.ATTRIBUTE, field_name="human_friendly_id"
134
+ ),
135
+ ),
136
+ NodeAttributeAddMigration(
137
+ uuids=node_ids,
138
+ new_node_schema=schema,
139
+ previous_node_schema=schema,
140
+ schema_path=SchemaPath(
141
+ schema_kind=schema.kind, path_type=SchemaPathType.ATTRIBUTE, field_name="display_label"
142
+ ),
143
+ ),
144
+ ]
145
+ )
146
+
147
+ with Progress(console=get_migration_console()) as progress:
148
+ update_task = progress.add_task(
149
+ f"Adding HFID and display label to nodes on branch {branch.name}", total=len(migrations)
150
+ )
151
+
152
+ for migration in migrations:
153
+ try:
154
+ execution_result = await migration.execute(db=db, branch=branch)
155
+ result.errors.extend(execution_result.errors)
156
+ progress.update(update_task, advance=1)
157
+ except Exception as exc:
158
+ result.errors.append(str(exc))
159
+ return result
160
+
161
+ return result
162
+
163
+ async def validate_migration(self, db: InfrahubDatabase) -> MigrationResult: # noqa: ARG002
164
+ return MigrationResult()