infrahub-server 1.4.10__py3-none-any.whl → 1.5.0b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) 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/query.py +2 -0
  5. infrahub/api/schema.py +3 -0
  6. infrahub/auth.py +5 -5
  7. infrahub/cli/db.py +26 -2
  8. infrahub/cli/db_commands/clean_duplicate_schema_fields.py +212 -0
  9. infrahub/config.py +7 -2
  10. infrahub/core/attribute.py +25 -22
  11. infrahub/core/branch/models.py +2 -2
  12. infrahub/core/branch/needs_rebase_status.py +11 -0
  13. infrahub/core/branch/tasks.py +4 -3
  14. infrahub/core/changelog/models.py +4 -12
  15. infrahub/core/constants/__init__.py +1 -0
  16. infrahub/core/constants/infrahubkind.py +1 -0
  17. infrahub/core/convert_object_type/object_conversion.py +201 -0
  18. infrahub/core/convert_object_type/repository_conversion.py +89 -0
  19. infrahub/core/convert_object_type/schema_mapping.py +27 -3
  20. infrahub/core/diff/model/path.py +4 -0
  21. infrahub/core/diff/payload_builder.py +1 -1
  22. infrahub/core/diff/query/artifact.py +1 -1
  23. infrahub/core/graph/__init__.py +1 -1
  24. infrahub/core/initialization.py +2 -2
  25. infrahub/core/ipam/utilization.py +1 -1
  26. infrahub/core/manager.py +9 -84
  27. infrahub/core/migrations/graph/__init__.py +6 -0
  28. infrahub/core/migrations/graph/m040_profile_attrs_in_db.py +166 -0
  29. infrahub/core/migrations/graph/m041_create_hfid_display_label_in_db.py +97 -0
  30. infrahub/core/migrations/graph/m042_backfill_hfid_display_label_in_db.py +86 -0
  31. infrahub/core/migrations/schema/node_attribute_add.py +5 -2
  32. infrahub/core/migrations/shared.py +5 -6
  33. infrahub/core/node/__init__.py +165 -42
  34. infrahub/core/node/constraints/attribute_uniqueness.py +3 -1
  35. infrahub/core/node/create.py +67 -35
  36. infrahub/core/node/lock_utils.py +98 -0
  37. infrahub/core/node/node_property_attribute.py +230 -0
  38. infrahub/core/node/standard.py +1 -1
  39. infrahub/core/property.py +11 -0
  40. infrahub/core/protocols.py +8 -1
  41. infrahub/core/query/attribute.py +27 -15
  42. infrahub/core/query/node.py +61 -185
  43. infrahub/core/query/relationship.py +43 -26
  44. infrahub/core/query/subquery.py +0 -8
  45. infrahub/core/registry.py +2 -2
  46. infrahub/core/relationship/constraints/count.py +1 -1
  47. infrahub/core/relationship/model.py +60 -20
  48. infrahub/core/schema/attribute_schema.py +0 -2
  49. infrahub/core/schema/basenode_schema.py +42 -2
  50. infrahub/core/schema/definitions/core/__init__.py +2 -0
  51. infrahub/core/schema/definitions/core/generator.py +2 -0
  52. infrahub/core/schema/definitions/core/group.py +16 -2
  53. infrahub/core/schema/definitions/core/repository.py +7 -0
  54. infrahub/core/schema/definitions/internal.py +14 -1
  55. infrahub/core/schema/generated/base_node_schema.py +6 -1
  56. infrahub/core/schema/node_schema.py +5 -2
  57. infrahub/core/schema/relationship_schema.py +0 -1
  58. infrahub/core/schema/schema_branch.py +137 -2
  59. infrahub/core/schema/schema_branch_display.py +123 -0
  60. infrahub/core/schema/schema_branch_hfid.py +114 -0
  61. infrahub/core/validators/aggregated_checker.py +1 -1
  62. infrahub/core/validators/determiner.py +12 -1
  63. infrahub/core/validators/relationship/peer.py +1 -1
  64. infrahub/core/validators/tasks.py +1 -1
  65. infrahub/display_labels/__init__.py +0 -0
  66. infrahub/display_labels/gather.py +48 -0
  67. infrahub/display_labels/models.py +240 -0
  68. infrahub/display_labels/tasks.py +186 -0
  69. infrahub/display_labels/triggers.py +22 -0
  70. infrahub/events/group_action.py +1 -1
  71. infrahub/events/node_action.py +1 -1
  72. infrahub/generators/constants.py +7 -0
  73. infrahub/generators/models.py +38 -12
  74. infrahub/generators/tasks.py +34 -16
  75. infrahub/git/base.py +38 -1
  76. infrahub/git/integrator.py +22 -14
  77. infrahub/graphql/analyzer.py +1 -1
  78. infrahub/graphql/api/dependencies.py +2 -4
  79. infrahub/graphql/api/endpoints.py +2 -2
  80. infrahub/graphql/app.py +2 -4
  81. infrahub/graphql/initialization.py +2 -3
  82. infrahub/graphql/manager.py +212 -137
  83. infrahub/graphql/middleware.py +12 -0
  84. infrahub/graphql/mutations/branch.py +11 -0
  85. infrahub/graphql/mutations/computed_attribute.py +110 -3
  86. infrahub/graphql/mutations/convert_object_type.py +34 -13
  87. infrahub/graphql/mutations/display_label.py +111 -0
  88. infrahub/graphql/mutations/generator.py +25 -7
  89. infrahub/graphql/mutations/hfid.py +118 -0
  90. infrahub/graphql/mutations/ipam.py +21 -8
  91. infrahub/graphql/mutations/main.py +37 -153
  92. infrahub/graphql/mutations/profile.py +195 -0
  93. infrahub/graphql/mutations/proposed_change.py +2 -1
  94. infrahub/graphql/mutations/relationship.py +2 -2
  95. infrahub/graphql/mutations/repository.py +22 -83
  96. infrahub/graphql/mutations/resource_manager.py +2 -2
  97. infrahub/graphql/mutations/schema.py +5 -5
  98. infrahub/graphql/mutations/webhook.py +1 -1
  99. infrahub/graphql/queries/resource_manager.py +1 -1
  100. infrahub/graphql/registry.py +173 -0
  101. infrahub/graphql/resolvers/resolver.py +2 -0
  102. infrahub/graphql/schema.py +8 -1
  103. infrahub/groups/tasks.py +1 -1
  104. infrahub/hfid/__init__.py +0 -0
  105. infrahub/hfid/gather.py +48 -0
  106. infrahub/hfid/models.py +240 -0
  107. infrahub/hfid/tasks.py +185 -0
  108. infrahub/hfid/triggers.py +22 -0
  109. infrahub/lock.py +67 -30
  110. infrahub/locks/__init__.py +0 -0
  111. infrahub/locks/tasks.py +37 -0
  112. infrahub/middleware.py +26 -1
  113. infrahub/patch/plan_writer.py +2 -2
  114. infrahub/profiles/__init__.py +0 -0
  115. infrahub/profiles/node_applier.py +101 -0
  116. infrahub/profiles/queries/__init__.py +0 -0
  117. infrahub/profiles/queries/get_profile_data.py +99 -0
  118. infrahub/profiles/tasks.py +63 -0
  119. infrahub/proposed_change/tasks.py +10 -1
  120. infrahub/repositories/__init__.py +0 -0
  121. infrahub/repositories/create_repository.py +113 -0
  122. infrahub/server.py +16 -3
  123. infrahub/services/__init__.py +8 -5
  124. infrahub/tasks/registry.py +6 -4
  125. infrahub/trigger/catalogue.py +4 -0
  126. infrahub/trigger/models.py +2 -0
  127. infrahub/trigger/tasks.py +3 -0
  128. infrahub/webhook/models.py +1 -1
  129. infrahub/workflows/catalogue.py +110 -3
  130. infrahub/workflows/initialization.py +16 -0
  131. infrahub/workflows/models.py +17 -2
  132. infrahub_sdk/branch.py +5 -8
  133. infrahub_sdk/checks.py +1 -1
  134. infrahub_sdk/client.py +364 -84
  135. infrahub_sdk/convert_object_type.py +61 -0
  136. infrahub_sdk/ctl/check.py +2 -3
  137. infrahub_sdk/ctl/cli_commands.py +18 -12
  138. infrahub_sdk/ctl/config.py +8 -2
  139. infrahub_sdk/ctl/generator.py +6 -3
  140. infrahub_sdk/ctl/graphql.py +184 -0
  141. infrahub_sdk/ctl/repository.py +39 -1
  142. infrahub_sdk/ctl/schema.py +18 -3
  143. infrahub_sdk/ctl/utils.py +4 -0
  144. infrahub_sdk/ctl/validate.py +5 -3
  145. infrahub_sdk/diff.py +4 -5
  146. infrahub_sdk/exceptions.py +2 -0
  147. infrahub_sdk/generator.py +7 -1
  148. infrahub_sdk/graphql/__init__.py +12 -0
  149. infrahub_sdk/graphql/constants.py +1 -0
  150. infrahub_sdk/graphql/plugin.py +85 -0
  151. infrahub_sdk/graphql/query.py +77 -0
  152. infrahub_sdk/{graphql.py → graphql/renderers.py} +88 -75
  153. infrahub_sdk/graphql/utils.py +40 -0
  154. infrahub_sdk/node/attribute.py +2 -0
  155. infrahub_sdk/node/node.py +28 -20
  156. infrahub_sdk/playback.py +1 -2
  157. infrahub_sdk/protocols.py +54 -6
  158. infrahub_sdk/pytest_plugin/plugin.py +7 -4
  159. infrahub_sdk/pytest_plugin/utils.py +40 -0
  160. infrahub_sdk/repository.py +1 -2
  161. infrahub_sdk/schema/__init__.py +38 -0
  162. infrahub_sdk/schema/main.py +1 -0
  163. infrahub_sdk/schema/repository.py +8 -0
  164. infrahub_sdk/spec/object.py +120 -7
  165. infrahub_sdk/spec/range_expansion.py +118 -0
  166. infrahub_sdk/timestamp.py +18 -6
  167. infrahub_sdk/transforms.py +1 -1
  168. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/METADATA +9 -11
  169. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/RECORD +177 -134
  170. infrahub_testcontainers/container.py +1 -1
  171. infrahub_testcontainers/docker-compose-cluster.test.yml +1 -1
  172. infrahub_testcontainers/docker-compose.test.yml +1 -1
  173. infrahub_testcontainers/models.py +2 -2
  174. infrahub_testcontainers/performance_test.py +4 -4
  175. infrahub/core/convert_object_type/conversion.py +0 -134
  176. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/LICENSE.txt +0 -0
  177. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/WHEEL +0 -0
  178. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/entry_points.txt +0 -0
@@ -81,7 +81,7 @@ class SchemaDropdownAdd(Mutation):
81
81
  _validate_schema_permission(graphql_context=graphql_context)
82
82
  await apply_external_context(graphql_context=graphql_context, context_input=context)
83
83
 
84
- kind = graphql_context.db.schema.get(name=str(data.kind), branch=graphql_context.branch.name)
84
+ kind = graphql_context.db.schema.get(name=str(data.kind), branch=graphql_context.branch.name, duplicate=False)
85
85
  attribute = str(data.attribute)
86
86
  validate_kind_dropdown(kind=kind, attribute=attribute)
87
87
  dropdown = str(data.dropdown)
@@ -104,7 +104,7 @@ class SchemaDropdownAdd(Mutation):
104
104
  context=graphql_context.get_context(),
105
105
  )
106
106
 
107
- kind = graphql_context.db.schema.get(name=str(data.kind), branch=graphql_context.branch.name)
107
+ kind = graphql_context.db.schema.get(name=str(data.kind), branch=graphql_context.branch.name, duplicate=False)
108
108
  attrib = kind.get_attribute(attribute)
109
109
  dropdown_entry = {}
110
110
  success = False
@@ -141,7 +141,7 @@ class SchemaDropdownRemove(Mutation):
141
141
  graphql_context: GraphqlContext = info.context
142
142
 
143
143
  _validate_schema_permission(graphql_context=graphql_context)
144
- kind = graphql_context.db.schema.get(name=str(data.kind), branch=graphql_context.branch.name)
144
+ kind = graphql_context.db.schema.get(name=str(data.kind), branch=graphql_context.branch.name, duplicate=False)
145
145
  await apply_external_context(graphql_context=graphql_context, context_input=context)
146
146
 
147
147
  attribute = str(data.attribute)
@@ -197,7 +197,7 @@ class SchemaEnumAdd(Mutation):
197
197
  graphql_context: GraphqlContext = info.context
198
198
 
199
199
  _validate_schema_permission(graphql_context=graphql_context)
200
- kind = graphql_context.db.schema.get(name=str(data.kind), branch=graphql_context.branch.name)
200
+ kind = graphql_context.db.schema.get(name=str(data.kind), branch=graphql_context.branch.name, duplicate=False)
201
201
  await apply_external_context(graphql_context=graphql_context, context_input=context)
202
202
 
203
203
  attribute = str(data.attribute)
@@ -243,7 +243,7 @@ class SchemaEnumRemove(Mutation):
243
243
  graphql_context: GraphqlContext = info.context
244
244
 
245
245
  _validate_schema_permission(graphql_context=graphql_context)
246
- kind = graphql_context.db.schema.get(name=str(data.kind), branch=graphql_context.branch.name)
246
+ kind = graphql_context.db.schema.get(name=str(data.kind), branch=graphql_context.branch.name, duplicate=False)
247
247
  await apply_external_context(graphql_context=graphql_context, context_input=context)
248
248
 
249
249
  attribute = str(data.attribute)
@@ -127,7 +127,7 @@ class InfrahubWebhookMutation(InfrahubMutationMixin, Mutation):
127
127
 
128
128
 
129
129
  def _validate_input(graphql_context: GraphqlContext, branch: Branch, input_data: WebhookCreate) -> None:
130
- if input_data.node_kind:
130
+ if input_data.node_kind and input_data.node_kind.value:
131
131
  # Validate that the requested node_kind exists, will raise an error if not
132
132
  graphql_context.db.schema.get(name=input_data.node_kind.value, branch=branch, duplicate=False)
133
133
 
@@ -242,7 +242,7 @@ class PoolUtilization(ObjectType):
242
242
  if "kind" in node_fields:
243
243
  node_response["kind"] = resource_node.get_kind()
244
244
  if "display_label" in node_fields:
245
- node_response["display_label"] = await resource_node.render_display_label(db=db)
245
+ node_response["display_label"] = await resource_node.get_display_label(db=db)
246
246
  if "weight" in node_fields:
247
247
  node_response["weight"] = await resource_node.get_resource_weight(db=db) # type: ignore[attr-defined]
248
248
  if "utilization" in node_fields:
@@ -0,0 +1,173 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import TYPE_CHECKING
5
+
6
+ from infrahub.core.timestamp import Timestamp
7
+ from infrahub.exceptions import InitializationError
8
+
9
+ if TYPE_CHECKING:
10
+ import graphene
11
+
12
+ from infrahub.core.branch import Branch
13
+ from infrahub.core.schema.schema_branch import SchemaBranch
14
+ from infrahub.graphql.manager import GraphQLSchemaManager, InterfaceReference
15
+
16
+ from .mutations.main import InfrahubMutation
17
+ from .types import InfrahubObject
18
+
19
+
20
+ @dataclass
21
+ class BranchDetails:
22
+ schema_changed_at: Timestamp
23
+ schema_hash: str
24
+ gql_manager: GraphQLSchemaManager
25
+
26
+
27
+ @dataclass
28
+ class GraphQLSchemaRegistry:
29
+ _branch_details_by_hash: dict[str, BranchDetails] = field(default_factory=dict)
30
+ _branch_name_by_hash: dict[str, set[str]] = field(default_factory=dict)
31
+ _branch_hash_activation_by_branch_name: dict[str, dict[str, str]] = field(default_factory=dict)
32
+ _registered_interface_types: dict[str, type[graphene.Interface]] = field(default_factory=dict)
33
+ _reference_hash_schema_map: dict[str, set[str]] = field(default_factory=dict)
34
+ _registered_edge_types: dict[str, type[InfrahubObject]] = field(default_factory=dict)
35
+ _registered_paginated_types: dict[str, type[InfrahubObject]] = field(default_factory=dict)
36
+ _registered_input_types: dict[str, type[graphene.InputObjectType]] = field(default_factory=dict)
37
+ _registered_object_types: dict[str, type[InfrahubObject]] = field(default_factory=dict)
38
+ _registered_mutation_types: dict[str, type[InfrahubMutation]] = field(default_factory=dict)
39
+
40
+ _manager_class: type[GraphQLSchemaManager] | None = None
41
+
42
+ def _add_branch_hash(self, branch_name: str, schema_hash: str) -> None:
43
+ if schema_hash not in self._branch_name_by_hash:
44
+ self._branch_name_by_hash[schema_hash] = set()
45
+
46
+ self._branch_name_by_hash[schema_hash].add(branch_name)
47
+
48
+ def _register_manager(self, manager: type[GraphQLSchemaManager]) -> None:
49
+ self._manager_class = manager
50
+
51
+ @property
52
+ def manager(self) -> type[GraphQLSchemaManager]:
53
+ if self._manager_class:
54
+ return self._manager_class
55
+ raise InitializationError
56
+
57
+ def clear_cache(self) -> None:
58
+ """Clear internal cache stored within this registry."""
59
+ self._branch_details_by_hash = {}
60
+ self._branch_name_by_hash = {}
61
+ self._branch_hash_activation_by_branch_name = {}
62
+ self._registered_interface_types = {}
63
+ self._reference_hash_schema_map = {}
64
+ self._registered_edge_types = {}
65
+ self._registered_paginated_types = {}
66
+ self._registered_input_types = {}
67
+ self._registered_object_types = {}
68
+ self._registered_mutation_types = {}
69
+
70
+ def _add_schema_to_reference_hash(self, reference_hash: str, schema_hash: str) -> None:
71
+ """Add the schema hash to a map containing the referenced object.
72
+
73
+ The goal of this is to be able to see all schemas that use a given reference type,
74
+ once no schemas use a specific type it's safe to remove the type from the registry.
75
+ """
76
+ if reference_hash not in self._reference_hash_schema_map:
77
+ self._reference_hash_schema_map[reference_hash] = set()
78
+ self._reference_hash_schema_map[reference_hash].add(schema_hash)
79
+
80
+ def get_edge_type(self, reference_hash: str, schema_hash: str) -> type[InfrahubObject] | None:
81
+ self._add_schema_to_reference_hash(reference_hash=reference_hash, schema_hash=schema_hash)
82
+ return self._registered_edge_types.get(reference_hash)
83
+
84
+ def set_edge_type(self, reference: type[InfrahubObject], reference_hash: str, schema_hash: str) -> None:
85
+ self._add_schema_to_reference_hash(reference_hash=reference_hash, schema_hash=schema_hash)
86
+ self._registered_edge_types[reference_hash] = reference
87
+
88
+ def get_input_type(self, reference_hash: str, schema_hash: str) -> type[graphene.InputObjectType] | None:
89
+ self._add_schema_to_reference_hash(reference_hash=reference_hash, schema_hash=schema_hash)
90
+ return self._registered_input_types.get(reference_hash)
91
+
92
+ def set_input_type(self, reference: type[graphene.InputObjectType], reference_hash: str, schema_hash: str) -> None:
93
+ self._add_schema_to_reference_hash(reference_hash=reference_hash, schema_hash=schema_hash)
94
+ self._registered_input_types[reference_hash] = reference
95
+
96
+ def get_interface_type(self, reference_hash: str, schema_hash: str) -> type[graphene.Interface] | None:
97
+ self._add_schema_to_reference_hash(reference_hash=reference_hash, schema_hash=schema_hash)
98
+ return self._registered_interface_types.get(reference_hash)
99
+
100
+ def set_interface_type(self, reference: InterfaceReference, schema_hash: str) -> None:
101
+ self._add_schema_to_reference_hash(reference_hash=reference.reference_hash, schema_hash=schema_hash)
102
+ self._registered_interface_types[reference.reference_hash] = reference.reference
103
+
104
+ def get_mutation_type(self, reference_hash: str, schema_hash: str) -> type[InfrahubMutation] | None:
105
+ self._add_schema_to_reference_hash(reference_hash=reference_hash, schema_hash=schema_hash)
106
+ return self._registered_mutation_types.get(reference_hash)
107
+
108
+ def set_mutation_type(self, reference: type[InfrahubMutation], reference_hash: str, schema_hash: str) -> None:
109
+ self._add_schema_to_reference_hash(reference_hash=reference_hash, schema_hash=schema_hash)
110
+ self._registered_mutation_types[reference_hash] = reference
111
+
112
+ def get_object_type(self, reference_hash: str, schema_hash: str) -> type[InfrahubObject] | None:
113
+ self._add_schema_to_reference_hash(reference_hash=reference_hash, schema_hash=schema_hash)
114
+ return self._registered_object_types.get(reference_hash)
115
+
116
+ def set_object_type(self, reference: type[InfrahubObject], reference_hash: str, schema_hash: str) -> None:
117
+ self._add_schema_to_reference_hash(reference_hash=reference_hash, schema_hash=schema_hash)
118
+ self._registered_object_types[reference_hash] = reference
119
+
120
+ def get_paginated_type(self, reference_hash: str, schema_hash: str) -> type[InfrahubObject] | None:
121
+ self._add_schema_to_reference_hash(reference_hash=reference_hash, schema_hash=schema_hash)
122
+ return self._registered_paginated_types.get(reference_hash)
123
+
124
+ def set_paginated_type(self, reference: type[InfrahubObject], reference_hash: str, schema_hash: str) -> None:
125
+ self._add_schema_to_reference_hash(reference_hash=reference_hash, schema_hash=schema_hash)
126
+ self._registered_paginated_types[reference_hash] = reference
127
+
128
+ def purge_inactive(self, active_branches: list[str]) -> set[str]:
129
+ """Return inactive branches that were purged"""
130
+ inactive_branches: set[str] = set()
131
+ for schema_hash in list(self._branch_name_by_hash.keys()):
132
+ branches = list(self._branch_name_by_hash[schema_hash])
133
+ for branch in branches:
134
+ if branch not in active_branches and branch in self._branch_name_by_hash[schema_hash]:
135
+ inactive_branches.add(branch)
136
+ self._branch_name_by_hash[schema_hash].discard(branch)
137
+
138
+ for schema_hash in list(self._branch_name_by_hash.keys()):
139
+ if not self._branch_name_by_hash[schema_hash]:
140
+ # If no remaining branch is using the schema remove it completely
141
+ del self._branch_name_by_hash[schema_hash]
142
+ del self._branch_details_by_hash[schema_hash]
143
+
144
+ return inactive_branches
145
+
146
+ def cache_branch(self, branch: Branch, schema_branch: SchemaBranch, schema_hash: str) -> BranchDetails:
147
+ branch_details = BranchDetails(
148
+ schema_changed_at=Timestamp(branch.schema_changed_at) if branch.schema_changed_at else Timestamp(),
149
+ schema_hash=schema_hash,
150
+ gql_manager=self.manager(schema=schema_branch),
151
+ )
152
+
153
+ self._branch_details_by_hash[schema_hash] = branch_details
154
+
155
+ return branch_details
156
+
157
+ def get_manager_for_branch(self, branch: Branch, schema_branch: SchemaBranch) -> GraphQLSchemaManager:
158
+ if branch.schema_hash:
159
+ schema_hash = branch.schema_hash.main
160
+ else:
161
+ schema_hash = schema_branch.get_hash()
162
+
163
+ if schema_hash in self._branch_details_by_hash:
164
+ branch_details = self._branch_details_by_hash[schema_hash]
165
+ else:
166
+ branch_details = self.cache_branch(branch=branch, schema_branch=schema_branch, schema_hash=schema_hash)
167
+
168
+ self._add_branch_hash(branch_name=branch.name, schema_hash=schema_hash)
169
+
170
+ return branch_details.gql_manager
171
+
172
+
173
+ registry = GraphQLSchemaRegistry()
@@ -166,6 +166,8 @@ async def default_paginated_list_resolver(
166
166
 
167
167
  edges = fields.get("edges", {})
168
168
  node_fields = edges.get("node", {})
169
+ if "hfid" in node_fields:
170
+ node_fields["human_friendly_id"] = None
169
171
 
170
172
  permission_set: dict[str, Any] | None = None
171
173
  permissions = (
@@ -15,11 +15,14 @@ from .mutations.branch import (
15
15
  BranchUpdate,
16
16
  BranchValidate,
17
17
  )
18
- from .mutations.computed_attribute import UpdateComputedAttribute
18
+ from .mutations.computed_attribute import RecomputeComputedAttribute, UpdateComputedAttribute
19
19
  from .mutations.convert_object_type import ConvertObjectType
20
20
  from .mutations.diff import DiffUpdateMutation
21
21
  from .mutations.diff_conflict import ResolveDiffConflict
22
+ from .mutations.display_label import UpdateDisplayLabel
22
23
  from .mutations.generator import GeneratorDefinitionRequestRun
24
+ from .mutations.hfid import UpdateHFID
25
+ from .mutations.profile import InfrahubProfilesRefresh
23
26
  from .mutations.proposed_change import (
24
27
  ProposedChangeCheckForApprovalRevoke,
25
28
  ProposedChangeMerge,
@@ -113,6 +116,9 @@ class InfrahubBaseMutation(ObjectType):
113
116
  InfrahubRepositoryProcess = ProcessRepository.Field()
114
117
  InfrahubRepositoryConnectivity = ValidateRepositoryConnectivity.Field()
115
118
  InfrahubUpdateComputedAttribute = UpdateComputedAttribute.Field()
119
+ InfrahubUpdateDisplayLabel = UpdateDisplayLabel.Field()
120
+ InfrahubUpdateHFID = UpdateHFID.Field()
121
+ InfrahubRecomputeComputedAttribute = RecomputeComputedAttribute.Field()
116
122
 
117
123
  RelationshipAdd = RelationshipAdd.Field()
118
124
  RelationshipRemove = RelationshipRemove.Field()
@@ -124,3 +130,4 @@ class InfrahubBaseMutation(ObjectType):
124
130
 
125
131
  ConvertObjectType = ConvertObjectType.Field()
126
132
  CoreProposedChangeCheckForApprovalRevoke = ProposedChangeCheckForApprovalRevoke.Field()
133
+ InfrahubProfilesRefresh = InfrahubProfilesRefresh.Field()
infrahub/groups/tasks.py CHANGED
@@ -20,7 +20,7 @@ async def update_graphql_query_group(model: RequestGraphQLQueryGroupUpdate) -> N
20
20
  if len(model.subscribers) == 1:
21
21
  related_nodes.append(model.subscribers[0])
22
22
 
23
- await add_tags(branches=[model.branch], nodes=related_nodes)
23
+ await add_tags(branches=[model.branch], nodes=related_nodes, namespace=False)
24
24
 
25
25
  params_hash = dict_hash(model.params)
26
26
  group_name = f"{model.query_name}__{params_hash}"
File without changes
@@ -0,0 +1,48 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+
5
+ from prefect import task
6
+ from prefect.cache_policies import NONE
7
+ from prefect.logging import get_run_logger
8
+
9
+ from infrahub.core.registry import registry
10
+ from infrahub.database import InfrahubDatabase # noqa: TC001 needed for prefect flow
11
+
12
+ from .models import HFIDTriggerDefinition
13
+
14
+
15
+ @dataclass
16
+ class BranchScope:
17
+ name: str
18
+ out_of_scope: list[str] = field(default_factory=list)
19
+
20
+
21
+ @task(
22
+ name="gather-trigger-hfid",
23
+ cache_policy=NONE,
24
+ )
25
+ async def gather_trigger_hfid(
26
+ db: InfrahubDatabase | None = None, # noqa: ARG001 Needed to have a common function signature for gathering functions
27
+ ) -> list[HFIDTriggerDefinition]:
28
+ log = get_run_logger()
29
+
30
+ # Build a list of all branches to process based on which branch is different from main
31
+ branches_with_diff_from_main = registry.get_altered_schema_branches()
32
+ branches_to_process: list[BranchScope] = [BranchScope(name=branch) for branch in branches_with_diff_from_main]
33
+ branches_to_process.append(BranchScope(name=registry.default_branch, out_of_scope=branches_with_diff_from_main))
34
+
35
+ triggers: list[HFIDTriggerDefinition] = []
36
+
37
+ for branch in branches_to_process:
38
+ schema_branch = registry.schema.get_schema_branch(name=branch.name)
39
+ branch_triggers = HFIDTriggerDefinition.from_schema_hfids(
40
+ branch=branch.name,
41
+ hfids=schema_branch.hfids,
42
+ branches_out_of_scope=branch.out_of_scope,
43
+ )
44
+ log.info(f"Generating {len(branch_triggers)} HFID trigger for {branch.name} (except {branch.out_of_scope})")
45
+
46
+ triggers.extend(branch_triggers)
47
+
48
+ return triggers
@@ -0,0 +1,240 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING, Any, Self
5
+
6
+ from infrahub_sdk.graphql import Query
7
+ from pydantic import BaseModel, Field
8
+
9
+ from infrahub.core.constants import RelationshipCardinality
10
+ from infrahub.core.registry import registry
11
+ from infrahub.core.schema import NodeSchema # noqa: TC001
12
+ from infrahub.events import NodeUpdatedEvent
13
+ from infrahub.trigger.constants import NAME_SEPARATOR
14
+ from infrahub.trigger.models import (
15
+ EventTrigger,
16
+ ExecuteWorkflow,
17
+ TriggerBranchDefinition,
18
+ TriggerType,
19
+ )
20
+ from infrahub.workflows.catalogue import HFID_PROCESS
21
+
22
+ if TYPE_CHECKING:
23
+ from infrahub.core.schema.schema_branch_hfid import HFIDs, RelationshipTriggers
24
+
25
+
26
+ @dataclass
27
+ class AttributeTarget:
28
+ hash: str
29
+ fields: set[str]
30
+
31
+
32
+ class HFIDTriggerDefinition(TriggerBranchDefinition):
33
+ type: TriggerType = TriggerType.HUMAN_FRIENDLY_ID
34
+ hfid_hash: str
35
+ target_kind: str | None = Field(default=None)
36
+
37
+ def get_description(self) -> str:
38
+ return f"{super().get_description()} | hash:{self.hfid_hash}"
39
+
40
+ @classmethod
41
+ def from_schema_hfids(
42
+ cls,
43
+ branch: str,
44
+ hfids: HFIDs,
45
+ branches_out_of_scope: list[str] | None = None,
46
+ ) -> list[HFIDTriggerDefinition]:
47
+ """
48
+ This function is used to create a trigger definition for a display labels of type Jinja2.
49
+ """
50
+
51
+ definitions: list[HFIDTriggerDefinition] = []
52
+
53
+ for node_kind, hfid_definition in hfids.get_template_nodes().items():
54
+ definitions.append(
55
+ cls.new(
56
+ branch=branch,
57
+ node_kind=node_kind,
58
+ target_kind=node_kind,
59
+ fields=[
60
+ "_trigger_placeholder"
61
+ ], # Triggers for the nodes themselves are only used to determine if all nodes should be regenerated
62
+ hfid_hash=hfid_definition.get_hash(),
63
+ branches_out_of_scope=branches_out_of_scope,
64
+ )
65
+ )
66
+
67
+ for related_kind, relationship_trigger in hfids.get_related_trigger_nodes().items():
68
+ definitions.extend(
69
+ cls.from_related_node(
70
+ branch=branch,
71
+ related_kind=related_kind,
72
+ relationship_trigger=relationship_trigger,
73
+ hfids=hfids,
74
+ branches_out_of_scope=branches_out_of_scope,
75
+ )
76
+ )
77
+
78
+ return definitions
79
+
80
+ @classmethod
81
+ def from_related_node(
82
+ cls,
83
+ branch: str,
84
+ related_kind: str,
85
+ relationship_trigger: RelationshipTriggers,
86
+ hfids: HFIDs,
87
+ branches_out_of_scope: list[str] | None = None,
88
+ ) -> list[HFIDTriggerDefinition]:
89
+ targets_by_attribute: dict[str, AttributeTarget] = {}
90
+ definitions: list[HFIDTriggerDefinition] = []
91
+ for attribute, relationship_identifiers in relationship_trigger.attributes.items():
92
+ for relationship_identifier in relationship_identifiers:
93
+ actual_node = hfids.get_node_definition(kind=relationship_identifier.kind)
94
+ if relationship_identifier.kind not in targets_by_attribute:
95
+ targets_by_attribute[relationship_identifier.kind] = AttributeTarget(
96
+ actual_node.get_hash(), fields=set()
97
+ )
98
+ targets_by_attribute[relationship_identifier.kind].fields.add(attribute)
99
+
100
+ for target_kind, attribute_target in targets_by_attribute.items():
101
+ definitions.append(
102
+ cls.new(
103
+ branch=branch,
104
+ node_kind=related_kind,
105
+ target_kind=target_kind,
106
+ fields=sorted(attribute_target.fields),
107
+ hfid_hash=attribute_target.hash,
108
+ branches_out_of_scope=branches_out_of_scope,
109
+ )
110
+ )
111
+
112
+ return definitions
113
+
114
+ @classmethod
115
+ def new(
116
+ cls,
117
+ branch: str,
118
+ node_kind: str,
119
+ target_kind: str,
120
+ hfid_hash: str,
121
+ fields: list[str],
122
+ branches_out_of_scope: list[str] | None = None,
123
+ ) -> Self:
124
+ event_trigger = EventTrigger()
125
+ event_trigger.events.add(NodeUpdatedEvent.event_name)
126
+ event_trigger.match = {"infrahub.node.kind": node_kind}
127
+ if branches_out_of_scope:
128
+ event_trigger.match["infrahub.branch.name"] = [f"!{branch}" for branch in branches_out_of_scope]
129
+ elif not branches_out_of_scope and branch != registry.default_branch:
130
+ event_trigger.match["infrahub.branch.name"] = branch
131
+
132
+ event_trigger.match_related = {
133
+ "prefect.resource.role": ["infrahub.node.attribute_update", "infrahub.node.relationship_update"],
134
+ "infrahub.field.name": fields,
135
+ }
136
+
137
+ workflow = ExecuteWorkflow(
138
+ workflow=HFID_PROCESS,
139
+ parameters={
140
+ "branch_name": "{{ event.resource['infrahub.branch.name'] }}",
141
+ "node_kind": node_kind,
142
+ "object_id": "{{ event.resource['infrahub.node.id'] }}",
143
+ "target_kind": target_kind,
144
+ "context": {
145
+ "__prefect_kind": "json",
146
+ "value": {
147
+ "__prefect_kind": "jinja",
148
+ "template": "{{ event.payload['context'] | tojson }}",
149
+ },
150
+ },
151
+ },
152
+ )
153
+
154
+ trigger_definition_target_kind = target_kind if target_kind == node_kind else None
155
+
156
+ return cls(
157
+ name=f"{target_kind}{NAME_SEPARATOR}by{NAME_SEPARATOR}{node_kind}",
158
+ hfid_hash=hfid_hash,
159
+ branch=branch,
160
+ trigger=event_trigger,
161
+ actions=[workflow],
162
+ target_kind=trigger_definition_target_kind,
163
+ )
164
+
165
+
166
+ class HFIDGraphQLResponse(BaseModel):
167
+ node_id: str
168
+ hfid_value: list[str] | None = Field(default=None)
169
+ variables: dict[str, str] = Field(default_factory=dict)
170
+
171
+
172
+ class HFIDGraphQL(BaseModel):
173
+ filter_key: str
174
+ node_schema: NodeSchema = Field(..., description="The node kind where the computed attribute is defined")
175
+ variables: list[str] = Field(..., description="The list of variable names used within the computed attribute")
176
+
177
+ def render_graphql_query(self, filter_id: str) -> str:
178
+ query_fields = self.query_fields
179
+ query_fields["id"] = None
180
+ query_fields["hfid"] = None
181
+ query = Query(
182
+ name="HFIDFilter",
183
+ query={
184
+ self.node_schema.kind: {
185
+ "@filters": {self.filter_key: filter_id},
186
+ "edges": {"node": query_fields},
187
+ }
188
+ },
189
+ )
190
+
191
+ return query.render()
192
+
193
+ @property
194
+ def query_fields(self) -> dict[str, Any]:
195
+ output: dict[str, Any] = {}
196
+ for variable in self.variables:
197
+ field_name, remainder = variable.split("__", maxsplit=1)
198
+ if field_name in self.node_schema.attribute_names:
199
+ output[field_name] = {remainder: None}
200
+ elif field_name in self.node_schema.relationship_names:
201
+ related_attribute, related_value = remainder.split("__", maxsplit=1)
202
+ relationship = self.node_schema.get_relationship(name=field_name)
203
+ if relationship.cardinality == RelationshipCardinality.ONE:
204
+ if field_name not in output:
205
+ output[field_name] = {"node": {}}
206
+ output[field_name]["node"][related_attribute] = {related_value: None}
207
+ return output
208
+
209
+ def parse_response(self, response: dict[str, Any]) -> list[HFIDGraphQLResponse]:
210
+ rendered_response: list[HFIDGraphQLResponse] = []
211
+ if kind_payload := response.get(self.node_schema.kind):
212
+ edges = kind_payload.get("edges", [])
213
+ for node in edges:
214
+ if node_response := self.to_node_response(node_dict=node):
215
+ rendered_response.append(node_response)
216
+ return rendered_response
217
+
218
+ def to_node_response(self, node_dict: dict[str, Any]) -> HFIDGraphQLResponse | None:
219
+ if node := node_dict.get("node"):
220
+ node_id = node.get("id")
221
+ else:
222
+ return None
223
+
224
+ hfid = node.get("hfid")
225
+ response = HFIDGraphQLResponse(node_id=node_id, hfid_value=hfid)
226
+ for variable in self.variables:
227
+ field_name, remainder = variable.split("__", maxsplit=1)
228
+ # response.variables[variable] = None
229
+ if field_content := node.get(field_name):
230
+ if field_name in self.node_schema.attribute_names:
231
+ response.variables[variable] = str(field_content.get(remainder, ""))
232
+ elif field_name in self.node_schema.relationship_names:
233
+ relationship = self.node_schema.get_relationship(name=field_name)
234
+ if relationship.cardinality == RelationshipCardinality.ONE:
235
+ related_attribute, related_value = remainder.split("__", maxsplit=1)
236
+ node_content = field_content.get("node") or {}
237
+ related_attribute_content = node_content.get(related_attribute) or {}
238
+ response.variables[variable] = str(related_attribute_content.get(related_value, ""))
239
+
240
+ return response