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
@@ -41,14 +41,16 @@ from .m036_drop_attr_value_index import Migration036
41
41
  from .m037_index_attr_vals import Migration037
42
42
  from .m038_redo_0000_prefix_fix import Migration038
43
43
  from .m039_ipam_reconcile import Migration039
44
- from .m040_profile_attrs_in_db import Migration040
44
+ from .m040_duplicated_attributes import Migration040
45
+ from .m041_profile_attrs_in_db import Migration041
46
+ from .m042_create_hfid_display_label_in_db import Migration042
47
+ from .m043_backfill_hfid_display_label_in_db import Migration043
45
48
 
46
49
  if TYPE_CHECKING:
47
- from infrahub.core.root import Root
50
+ from ..shared import MigrationTypes
48
51
 
49
- from ..shared import ArbitraryMigration, GraphMigration, InternalSchemaMigration
50
52
 
51
- MIGRATIONS: list[type[GraphMigration | InternalSchemaMigration | ArbitraryMigration]] = [
53
+ MIGRATIONS: list[type[MigrationTypes]] = [
52
54
  Migration001,
53
55
  Migration002,
54
56
  Migration003,
@@ -89,25 +91,24 @@ MIGRATIONS: list[type[GraphMigration | InternalSchemaMigration | ArbitraryMigrat
89
91
  Migration038,
90
92
  Migration039,
91
93
  Migration040,
94
+ Migration041,
95
+ Migration042,
96
+ Migration043,
92
97
  ]
93
98
 
94
99
 
95
- async def get_graph_migrations(
96
- root: Root,
97
- ) -> Sequence[GraphMigration | InternalSchemaMigration | ArbitraryMigration]:
100
+ async def get_graph_migrations(current_graph_version: int) -> Sequence[MigrationTypes]:
98
101
  applicable_migrations = []
99
102
  for migration_class in MIGRATIONS:
100
103
  migration = migration_class.init()
101
- if root.graph_version > migration.minimum_version:
104
+ if current_graph_version > migration.minimum_version:
102
105
  continue
103
106
  applicable_migrations.append(migration)
104
107
 
105
108
  return applicable_migrations
106
109
 
107
110
 
108
- def get_migration_by_number(
109
- migration_number: int | str,
110
- ) -> GraphMigration | InternalSchemaMigration | ArbitraryMigration:
111
+ def get_migration_by_number(migration_number: int | str) -> MigrationTypes:
111
112
  # Convert to string and pad with zeros if needed
112
113
  try:
113
114
  num = int(migration_number)
@@ -0,0 +1,21 @@
1
+ from infrahub.core import registry
2
+ from infrahub.core.branch import Branch
3
+ from infrahub.core.schema import SchemaRoot, internal_schema
4
+ from infrahub.core.schema.manager import SchemaManager
5
+ from infrahub.core.schema.schema_branch import SchemaBranch
6
+ from infrahub.database import InfrahubDatabase
7
+ from infrahub.exceptions import InitializationError
8
+
9
+
10
+ async def get_or_load_schema_branch(db: InfrahubDatabase, branch: Branch) -> SchemaBranch:
11
+ try:
12
+ if registry.schema.has_schema_branch(branch.name):
13
+ return registry.schema.get_schema_branch(branch.name)
14
+ except InitializationError:
15
+ schema_manager = SchemaManager()
16
+ registry.schema = schema_manager
17
+ internal_schema_root = SchemaRoot(**internal_schema)
18
+ registry.schema.register_schema(schema=internal_schema_root)
19
+ schema_branch = await registry.schema.load_schema_from_db(db=db, branch=branch)
20
+ registry.schema.set_schema_branch(name=branch.name, schema=schema_branch)
21
+ return schema_branch
@@ -286,7 +286,7 @@ class Migration013AddInternalStatusData(AttributeAddQuery):
286
286
  kwargs.pop("branch", None)
287
287
 
288
288
  super().__init__(
289
- node_kind="CoreGenericRepository",
289
+ node_kinds=["CoreGenericRepository"],
290
290
  attribute_name="internal_status",
291
291
  attribute_kind="Dropdown",
292
292
  branch_support=BranchSupportType.LOCAL.value,
@@ -0,0 +1,81 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any, Sequence
4
+
5
+ from infrahub.core.migrations.shared import MigrationResult
6
+ from infrahub.core.query import Query, QueryType
7
+
8
+ from ..shared import GraphMigration
9
+
10
+ if TYPE_CHECKING:
11
+ from infrahub.database import InfrahubDatabase
12
+
13
+
14
+ class DeleteDuplicatedAttributesQuery(Query):
15
+ name: str = "delete_duplicated_attributes"
16
+ type: QueryType = QueryType.WRITE
17
+ insert_return: bool = False
18
+ insert_limit: bool = False
19
+
20
+ async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002
21
+ query = """
22
+ // -------------
23
+ // get all the Nodes linked to multiple Attributes with the same name to drastically reduce the search space
24
+ // -------------
25
+ MATCH (n:Node)-[:HAS_ATTRIBUTE]->(attr:Attribute)
26
+ WITH DISTINCT n, attr
27
+ WITH n, attr.name AS attr_name, count(*) AS num_attrs
28
+ WHERE num_attrs > 1
29
+ // -------------
30
+ // for each Node-attr_name pair, get the possible duplicate Attributes
31
+ // -------------
32
+ MATCH (n)-[:HAS_ATTRIBUTE]->(dup_attr:Attribute {name: attr_name})
33
+ WITH DISTINCT n, dup_attr
34
+ // -------------
35
+ // get the branch(es) for each possible duplicate Attribute
36
+ // -------------
37
+ CALL (n, dup_attr) {
38
+ MATCH (n)-[r:HAS_ATTRIBUTE {status: "active"}]->(dup_attr)
39
+ WHERE r.to IS NULL
40
+ AND NOT exists((n)-[:HAS_ATTRIBUTE {status: "deleted", branch: r.branch}]->(dup_attr))
41
+ RETURN r.branch AS branch
42
+ }
43
+ // -------------
44
+ // get the latest update time for each duplicate Attribute on each branch
45
+ // -------------
46
+ CALL (dup_attr, branch) {
47
+ MATCH (dup_attr)-[r {branch: branch}]-()
48
+ RETURN max(r.from) AS latest_update
49
+ }
50
+ // -------------
51
+ // order the duplicate Attributes by latest update time
52
+ // -------------
53
+ WITH n, dup_attr, branch, latest_update
54
+ ORDER BY n, branch, dup_attr.name, latest_update DESC
55
+ // -------------
56
+ // for any Node-dup_attr_name pairs with multiple duplicate Attributes, keep the Attribute with the latest update
57
+ // on this branch and delete all the other edges on this branch for this Attribute
58
+ // -------------
59
+ WITH n, branch, dup_attr.name AS dup_attr_name, collect(dup_attr) AS dup_attrs_reverse_chronological
60
+ WHERE size(dup_attrs_reverse_chronological) > 1
61
+ WITH branch, tail(dup_attrs_reverse_chronological) AS dup_attrs_to_delete
62
+ UNWIND dup_attrs_to_delete AS dup_attr_to_delete
63
+ MATCH (dup_attr_to_delete)-[r {branch: branch}]-()
64
+ DELETE r
65
+ // -------------
66
+ // delete any orphaned Attributes
67
+ // -------------
68
+ WITH DISTINCT dup_attr_to_delete
69
+ WHERE NOT exists((dup_attr_to_delete)--())
70
+ DELETE dup_attr_to_delete
71
+ """
72
+ self.add_to_query(query)
73
+
74
+
75
+ class Migration040(GraphMigration):
76
+ name: str = "040_duplicated_attributes"
77
+ queries: Sequence[type[Query]] = [DeleteDuplicatedAttributesQuery]
78
+ minimum_version: int = 39
79
+
80
+ async def validate_migration(self, db: InfrahubDatabase) -> MigrationResult: # noqa: ARG002
81
+ return MigrationResult()
@@ -0,0 +1,145 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from rich.console import Console
6
+ from rich.progress import Progress
7
+
8
+ from infrahub.core.branch.models import Branch
9
+ from infrahub.core.initialization import get_root_node
10
+ from infrahub.core.manager import NodeManager
11
+ from infrahub.core.migrations.shared import MigrationResult
12
+ from infrahub.core.query import Query, QueryType
13
+ from infrahub.core.timestamp import Timestamp
14
+ from infrahub.log import get_logger
15
+ from infrahub.profiles.node_applier import NodeProfilesApplier
16
+
17
+ from ..shared import MigrationRequiringRebase
18
+ from .load_schema_branch import get_or_load_schema_branch
19
+
20
+ if TYPE_CHECKING:
21
+ from infrahub.database import InfrahubDatabase
22
+
23
+ log = get_logger()
24
+
25
+
26
+ class GetUpdatedProfilesForBranchQuery(Query):
27
+ """
28
+ Get CoreProfile UUIDs with updated attributes on this branch
29
+ """
30
+
31
+ name = "get_profiles_by_branch"
32
+ type = QueryType.READ
33
+
34
+ async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002
35
+ self.params["branch"] = self.branch.name
36
+ query = """
37
+ MATCH (profile:CoreProfile)-[:HAS_ATTRIBUTE]->(attr:Attribute)-[e:HAS_VALUE]->(:AttributeValue)
38
+ WHERE e.branch = $branch
39
+ WITH DISTINCT profile.uuid AS profile_uuid
40
+ """
41
+ self.add_to_query(query)
42
+ self.return_labels = ["profile_uuid"]
43
+
44
+ def get_profile_ids(self) -> list[str]:
45
+ """Get list of updated profile UUIDs"""
46
+ return [result.get_as_type("profile_uuid", str) for result in self.get_results()]
47
+
48
+
49
+ class GetNodesWithProfileUpdatesForBranchQuery(Query):
50
+ """
51
+ Get Node UUIDs by which branches they have updated profiles on
52
+ """
53
+
54
+ name = "get_nodes_with_profile_updates_by_branch"
55
+ type = QueryType.READ
56
+
57
+ async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002
58
+ self.params["branch"] = self.branch.name
59
+ query = """
60
+ MATCH (node:Node)-[e:IS_RELATED]->(:Relationship {name: "node__profile"})
61
+ WHERE NOT node:CoreProfile
62
+ AND e.branch = $branch
63
+ WITH DISTINCT node.uuid AS node_uuid
64
+ """
65
+ self.add_to_query(query)
66
+ self.return_labels = ["node_uuid"]
67
+
68
+ def get_node_ids(self) -> list[str]:
69
+ """Get list of updated node UUIDs"""
70
+ return [result.get_as_type("node_uuid", str) for result in self.get_results()]
71
+
72
+
73
+ class Migration041(MigrationRequiringRebase):
74
+ """
75
+ Save profile attribute values on each node using the profile in the database
76
+ For any profile that has updates on a given branch (including default branch)
77
+ - run NodeProfilesApplier.apply_profiles on each node related to the profile on that branch
78
+ For any node that has an updated relationship to a profile on a given branch
79
+ - run NodeProfilesApplier.apply_profiles on the node on that branch
80
+ """
81
+
82
+ name: str = "041_profile_attrs_in_db"
83
+ minimum_version: int = 40
84
+
85
+ def _get_profile_applier(self, db: InfrahubDatabase, branch: Branch) -> NodeProfilesApplier:
86
+ return NodeProfilesApplier(db=db, branch=branch)
87
+
88
+ async def validate_migration(self, db: InfrahubDatabase) -> MigrationResult: # noqa: ARG002
89
+ return MigrationResult()
90
+
91
+ async def execute(self, db: InfrahubDatabase) -> MigrationResult:
92
+ root_node = await get_root_node(db=db, initialize=False)
93
+ default_branch_name = root_node.default_branch
94
+ default_branch = await Branch.get_by_name(db=db, name=default_branch_name)
95
+ return await self._do_execute_for_branch(db=db, branch=default_branch)
96
+
97
+ async def execute_against_branch(self, db: InfrahubDatabase, branch: Branch) -> MigrationResult:
98
+ return await self._do_execute_for_branch(db=db, branch=branch)
99
+
100
+ async def _do_execute_for_branch(self, db: InfrahubDatabase, branch: Branch) -> MigrationResult:
101
+ console = Console()
102
+ result = MigrationResult()
103
+ await get_or_load_schema_branch(db=db, branch=branch)
104
+
105
+ console.print(f"Gathering profiles for branch {branch.name}...", end="")
106
+ get_updated_profiles_for_branch_query = await GetUpdatedProfilesForBranchQuery.init(db=db, branch=branch)
107
+ await get_updated_profiles_for_branch_query.execute(db=db)
108
+ profile_ids = get_updated_profiles_for_branch_query.get_profile_ids()
109
+
110
+ profiles_map = await NodeManager.get_many(db=db, branch=branch, ids=list(profile_ids))
111
+ console.print("done")
112
+
113
+ node_ids_to_update: set[str] = set()
114
+ with Progress() as progress:
115
+ gather_nodes_task = progress.add_task(
116
+ f"Gathering affected objects for each profile on branch {branch.name}...", total=len(profiles_map)
117
+ )
118
+
119
+ for profile in profiles_map.values():
120
+ node_relationship_manager = profile.get_relationship("related_nodes")
121
+ node_peers = await node_relationship_manager.get_db_peers(db=db)
122
+ node_ids_to_update.update(str(peer.peer_id) for peer in node_peers)
123
+ progress.update(gather_nodes_task, advance=1)
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
+ with Progress() as progress:
135
+ apply_task = progress.add_task("Applying profiles to nodes...", total=len(node_ids_to_update))
136
+ applier = self._get_profile_applier(db=db, branch=branch)
137
+ for node_id in node_ids_to_update:
138
+ node = await NodeManager.get_one(db=db, branch=branch, id=node_id, at=right_now)
139
+ if node:
140
+ updated_field_names = await applier.apply_profiles(node=node)
141
+ if updated_field_names:
142
+ await node.save(db=db, fields=updated_field_names, at=right_now)
143
+ progress.update(apply_task, advance=1)
144
+
145
+ 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
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 Migration042(MigrationRequiringRebase):
48
+ name: str = "042_create_hfid_display_label_in_db"
49
+ minimum_version: int = 41
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() 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() 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()