infrahub-server 1.6.3__py3-none-any.whl → 1.7.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 (250) hide show
  1. infrahub/actions/tasks.py +4 -2
  2. infrahub/api/exceptions.py +2 -2
  3. infrahub/api/schema.py +3 -1
  4. infrahub/artifacts/tasks.py +1 -0
  5. infrahub/auth.py +2 -2
  6. infrahub/cli/db.py +54 -28
  7. infrahub/computed_attribute/gather.py +3 -4
  8. infrahub/computed_attribute/tasks.py +23 -6
  9. infrahub/config.py +8 -0
  10. infrahub/constants/enums.py +12 -0
  11. infrahub/core/account.py +4 -4
  12. infrahub/core/attribute.py +106 -108
  13. infrahub/core/branch/models.py +44 -71
  14. infrahub/core/branch/tasks.py +5 -3
  15. infrahub/core/changelog/diff.py +1 -20
  16. infrahub/core/changelog/models.py +0 -7
  17. infrahub/core/constants/__init__.py +17 -0
  18. infrahub/core/constants/database.py +0 -1
  19. infrahub/core/constants/schema.py +0 -1
  20. infrahub/core/convert_object_type/repository_conversion.py +3 -4
  21. infrahub/core/diff/branch_differ.py +1 -1
  22. infrahub/core/diff/conflict_transferer.py +1 -1
  23. infrahub/core/diff/data_check_synchronizer.py +4 -3
  24. infrahub/core/diff/enricher/cardinality_one.py +2 -2
  25. infrahub/core/diff/enricher/hierarchy.py +1 -1
  26. infrahub/core/diff/enricher/labels.py +1 -1
  27. infrahub/core/diff/merger/merger.py +28 -2
  28. infrahub/core/diff/merger/serializer.py +3 -10
  29. infrahub/core/diff/model/diff.py +1 -1
  30. infrahub/core/diff/query/merge.py +376 -135
  31. infrahub/core/diff/repository/repository.py +3 -1
  32. infrahub/core/graph/__init__.py +1 -1
  33. infrahub/core/graph/constraints.py +3 -3
  34. infrahub/core/graph/schema.py +2 -12
  35. infrahub/core/ipam/reconciler.py +8 -6
  36. infrahub/core/ipam/utilization.py +8 -15
  37. infrahub/core/manager.py +133 -152
  38. infrahub/core/merge.py +1 -1
  39. infrahub/core/metadata/__init__.py +0 -0
  40. infrahub/core/metadata/interface.py +37 -0
  41. infrahub/core/metadata/model.py +31 -0
  42. infrahub/core/metadata/query/__init__.py +0 -0
  43. infrahub/core/metadata/query/node_metadata.py +301 -0
  44. infrahub/core/migrations/graph/__init__.py +4 -0
  45. infrahub/core/migrations/graph/m012_convert_account_generic.py +12 -12
  46. infrahub/core/migrations/graph/m013_convert_git_password_credential.py +7 -12
  47. infrahub/core/migrations/graph/m017_add_core_profile.py +5 -2
  48. infrahub/core/migrations/graph/m018_uniqueness_nulls.py +2 -1
  49. infrahub/core/migrations/graph/m019_restore_rels_to_time.py +0 -10
  50. infrahub/core/migrations/graph/m020_duplicate_edges.py +0 -8
  51. infrahub/core/migrations/graph/m025_uniqueness_nulls.py +2 -1
  52. infrahub/core/migrations/graph/m026_0000_prefix_fix.py +2 -1
  53. infrahub/core/migrations/graph/m029_duplicates_cleanup.py +0 -1
  54. infrahub/core/migrations/graph/m031_check_number_attributes.py +2 -2
  55. infrahub/core/migrations/graph/m038_redo_0000_prefix_fix.py +2 -1
  56. infrahub/core/migrations/graph/m041_deleted_dup_edges.py +1 -1
  57. infrahub/core/migrations/graph/m049_remove_is_visible_relationship.py +53 -0
  58. infrahub/core/migrations/graph/m050_backfill_vertex_metadata.py +168 -0
  59. infrahub/core/migrations/query/__init__.py +2 -2
  60. infrahub/core/migrations/query/attribute_add.py +17 -6
  61. infrahub/core/migrations/query/attribute_remove.py +19 -5
  62. infrahub/core/migrations/query/attribute_rename.py +21 -5
  63. infrahub/core/migrations/query/node_duplicate.py +19 -4
  64. infrahub/core/migrations/query/schema_attribute_update.py +1 -1
  65. infrahub/core/migrations/schema/attribute_kind_update.py +21 -2
  66. infrahub/core/migrations/schema/attribute_name_update.py +1 -1
  67. infrahub/core/migrations/schema/attribute_supports_profile.py +5 -3
  68. infrahub/core/migrations/schema/models.py +3 -0
  69. infrahub/core/migrations/schema/node_attribute_add.py +5 -2
  70. infrahub/core/migrations/schema/node_attribute_remove.py +1 -1
  71. infrahub/core/migrations/schema/node_kind_update.py +1 -1
  72. infrahub/core/migrations/schema/node_remove.py +24 -2
  73. infrahub/core/migrations/schema/tasks.py +4 -1
  74. infrahub/core/migrations/shared.py +13 -6
  75. infrahub/core/models.py +6 -6
  76. infrahub/core/node/__init__.py +157 -58
  77. infrahub/core/node/base.py +9 -5
  78. infrahub/core/node/create.py +7 -3
  79. infrahub/core/node/delete_validator.py +1 -1
  80. infrahub/core/node/standard.py +100 -14
  81. infrahub/core/order.py +30 -0
  82. infrahub/core/property.py +0 -1
  83. infrahub/core/protocols.py +1 -0
  84. infrahub/core/protocols_base.py +10 -2
  85. infrahub/core/query/__init__.py +5 -3
  86. infrahub/core/query/attribute.py +164 -49
  87. infrahub/core/query/branch.py +58 -70
  88. infrahub/core/query/delete.py +1 -1
  89. infrahub/core/query/diff.py +7 -7
  90. infrahub/core/query/ipam.py +104 -43
  91. infrahub/core/query/node.py +1072 -281
  92. infrahub/core/query/relationship.py +531 -325
  93. infrahub/core/query/resource_manager.py +107 -18
  94. infrahub/core/query/standard_node.py +25 -5
  95. infrahub/core/query/utils.py +2 -4
  96. infrahub/core/relationship/constraints/count.py +1 -1
  97. infrahub/core/relationship/constraints/peer_kind.py +1 -1
  98. infrahub/core/relationship/constraints/peer_parent.py +1 -1
  99. infrahub/core/relationship/constraints/peer_relatives.py +1 -1
  100. infrahub/core/relationship/constraints/profiles_kind.py +1 -1
  101. infrahub/core/relationship/constraints/profiles_removal.py +168 -0
  102. infrahub/core/relationship/model.py +293 -139
  103. infrahub/core/schema/attribute_schema.py +2 -2
  104. infrahub/core/schema/basenode_schema.py +3 -0
  105. infrahub/core/schema/definitions/core/__init__.py +8 -2
  106. infrahub/core/schema/definitions/core/account.py +10 -10
  107. infrahub/core/schema/definitions/core/artifact.py +14 -8
  108. infrahub/core/schema/definitions/core/check.py +10 -4
  109. infrahub/core/schema/definitions/core/generator.py +26 -6
  110. infrahub/core/schema/definitions/core/graphql_query.py +1 -1
  111. infrahub/core/schema/definitions/core/group.py +9 -2
  112. infrahub/core/schema/definitions/core/ipam.py +80 -10
  113. infrahub/core/schema/definitions/core/menu.py +41 -7
  114. infrahub/core/schema/definitions/core/permission.py +16 -2
  115. infrahub/core/schema/definitions/core/profile.py +16 -2
  116. infrahub/core/schema/definitions/core/propose_change.py +24 -4
  117. infrahub/core/schema/definitions/core/propose_change_comment.py +23 -11
  118. infrahub/core/schema/definitions/core/propose_change_validator.py +50 -21
  119. infrahub/core/schema/definitions/core/repository.py +10 -0
  120. infrahub/core/schema/definitions/core/resource_pool.py +8 -1
  121. infrahub/core/schema/definitions/core/template.py +19 -2
  122. infrahub/core/schema/definitions/core/transform.py +11 -5
  123. infrahub/core/schema/definitions/core/webhook.py +27 -9
  124. infrahub/core/schema/manager.py +63 -43
  125. infrahub/core/schema/relationship_schema.py +6 -2
  126. infrahub/core/schema/schema_branch.py +48 -10
  127. infrahub/core/task/task.py +4 -2
  128. infrahub/core/utils.py +3 -25
  129. infrahub/core/validators/aggregated_checker.py +1 -1
  130. infrahub/core/validators/attribute/choices.py +1 -1
  131. infrahub/core/validators/attribute/enum.py +1 -1
  132. infrahub/core/validators/attribute/kind.py +1 -1
  133. infrahub/core/validators/attribute/length.py +1 -1
  134. infrahub/core/validators/attribute/min_max.py +1 -1
  135. infrahub/core/validators/attribute/number_pool.py +1 -1
  136. infrahub/core/validators/attribute/optional.py +1 -1
  137. infrahub/core/validators/attribute/regex.py +1 -1
  138. infrahub/core/validators/determiner.py +3 -3
  139. infrahub/core/validators/node/attribute.py +1 -1
  140. infrahub/core/validators/node/relationship.py +1 -1
  141. infrahub/core/validators/relationship/peer.py +1 -1
  142. infrahub/database/__init__.py +4 -4
  143. infrahub/dependencies/builder/constraint/grouped/node_runner.py +2 -0
  144. infrahub/dependencies/builder/constraint/relationship_manager/profiles_removal.py +8 -0
  145. infrahub/dependencies/registry.py +2 -0
  146. infrahub/display_labels/tasks.py +12 -3
  147. infrahub/git/integrator.py +18 -18
  148. infrahub/git/tasks.py +1 -1
  149. infrahub/git/utils.py +1 -1
  150. infrahub/graphql/constants.py +3 -0
  151. infrahub/graphql/context.py +1 -1
  152. infrahub/graphql/field_extractor.py +1 -1
  153. infrahub/graphql/initialization.py +11 -0
  154. infrahub/graphql/loaders/account.py +134 -0
  155. infrahub/graphql/loaders/node.py +5 -12
  156. infrahub/graphql/loaders/peers.py +5 -7
  157. infrahub/graphql/manager.py +175 -21
  158. infrahub/graphql/metadata.py +91 -0
  159. infrahub/graphql/mutations/account.py +6 -6
  160. infrahub/graphql/mutations/attribute.py +0 -2
  161. infrahub/graphql/mutations/branch.py +9 -5
  162. infrahub/graphql/mutations/computed_attribute.py +1 -1
  163. infrahub/graphql/mutations/display_label.py +1 -1
  164. infrahub/graphql/mutations/hfid.py +1 -1
  165. infrahub/graphql/mutations/ipam.py +4 -6
  166. infrahub/graphql/mutations/main.py +9 -4
  167. infrahub/graphql/mutations/profile.py +16 -22
  168. infrahub/graphql/mutations/proposed_change.py +4 -4
  169. infrahub/graphql/mutations/relationship.py +40 -10
  170. infrahub/graphql/mutations/repository.py +14 -12
  171. infrahub/graphql/mutations/schema.py +2 -2
  172. infrahub/graphql/order.py +14 -0
  173. infrahub/graphql/queries/branch.py +62 -6
  174. infrahub/graphql/queries/resource_manager.py +25 -24
  175. infrahub/graphql/resolvers/account_metadata.py +84 -0
  176. infrahub/graphql/resolvers/ipam.py +6 -8
  177. infrahub/graphql/resolvers/many_relationship.py +77 -35
  178. infrahub/graphql/resolvers/resolver.py +59 -14
  179. infrahub/graphql/resolvers/single_relationship.py +87 -23
  180. infrahub/graphql/subscription/graphql_query.py +2 -0
  181. infrahub/graphql/types/__init__.py +0 -1
  182. infrahub/graphql/types/attribute.py +10 -5
  183. infrahub/graphql/types/branch.py +40 -53
  184. infrahub/graphql/types/enums.py +3 -0
  185. infrahub/graphql/types/metadata.py +28 -0
  186. infrahub/graphql/types/node.py +22 -2
  187. infrahub/graphql/types/relationship.py +10 -2
  188. infrahub/graphql/types/standard_node.py +12 -7
  189. infrahub/hfid/tasks.py +12 -3
  190. infrahub/lock.py +7 -0
  191. infrahub/menu/repository.py +1 -1
  192. infrahub/patch/queries/base.py +1 -1
  193. infrahub/pools/number.py +1 -8
  194. infrahub/profiles/gather.py +56 -0
  195. infrahub/profiles/mandatory_fields_checker.py +116 -0
  196. infrahub/profiles/models.py +66 -0
  197. infrahub/profiles/node_applier.py +154 -13
  198. infrahub/profiles/queries/get_profile_data.py +143 -31
  199. infrahub/profiles/tasks.py +79 -27
  200. infrahub/profiles/triggers.py +22 -0
  201. infrahub/proposed_change/action_checker.py +1 -1
  202. infrahub/proposed_change/tasks.py +4 -1
  203. infrahub/services/__init__.py +1 -1
  204. infrahub/services/adapters/cache/nats.py +1 -1
  205. infrahub/services/adapters/cache/redis.py +7 -0
  206. infrahub/tasks/artifact.py +1 -0
  207. infrahub/transformations/tasks.py +2 -2
  208. infrahub/trigger/catalogue.py +2 -0
  209. infrahub/trigger/models.py +1 -0
  210. infrahub/trigger/setup.py +3 -3
  211. infrahub/trigger/tasks.py +3 -0
  212. infrahub/validators/tasks.py +1 -0
  213. infrahub/webhook/gather.py +1 -1
  214. infrahub/webhook/models.py +1 -1
  215. infrahub/webhook/tasks.py +23 -7
  216. infrahub/workers/dependencies.py +9 -3
  217. infrahub/workers/infrahub_async.py +13 -4
  218. infrahub/workflows/catalogue.py +19 -0
  219. infrahub_sdk/analyzer.py +2 -2
  220. infrahub_sdk/branch.py +12 -39
  221. infrahub_sdk/checks.py +4 -4
  222. infrahub_sdk/client.py +36 -0
  223. infrahub_sdk/ctl/cli_commands.py +2 -1
  224. infrahub_sdk/ctl/graphql.py +15 -4
  225. infrahub_sdk/ctl/utils.py +2 -2
  226. infrahub_sdk/enums.py +6 -0
  227. infrahub_sdk/graphql/renderers.py +21 -0
  228. infrahub_sdk/graphql/utils.py +85 -0
  229. infrahub_sdk/node/attribute.py +12 -2
  230. infrahub_sdk/node/constants.py +12 -0
  231. infrahub_sdk/node/metadata.py +69 -0
  232. infrahub_sdk/node/node.py +65 -14
  233. infrahub_sdk/node/property.py +3 -0
  234. infrahub_sdk/node/related_node.py +37 -5
  235. infrahub_sdk/node/relationship.py +18 -1
  236. infrahub_sdk/operation.py +2 -2
  237. infrahub_sdk/schema/repository.py +1 -2
  238. infrahub_sdk/transforms.py +2 -2
  239. infrahub_sdk/types.py +18 -2
  240. {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0.dist-info}/METADATA +17 -16
  241. {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0.dist-info}/RECORD +249 -228
  242. infrahub_testcontainers/container.py +3 -3
  243. infrahub_testcontainers/docker-compose-cluster.test.yml +7 -7
  244. infrahub_testcontainers/docker-compose.test.yml +13 -5
  245. infrahub_testcontainers/models.py +3 -3
  246. infrahub_testcontainers/performance_test.py +1 -1
  247. infrahub/graphql/models.py +0 -6
  248. {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0.dist-info}/WHEEL +0 -0
  249. {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0.dist-info}/entry_points.txt +0 -0
  250. {infrahub_server-1.6.3.dist-info → infrahub_server-1.7.0.dist-info}/licenses/LICENSE.txt +0 -0
@@ -4,12 +4,16 @@ from graphql import GraphQLResolveInfo
4
4
  from graphql.type.definition import GraphQLNonNull
5
5
 
6
6
  from infrahub.core.branch.models import Branch
7
- from infrahub.core.constants import BranchSupportType
7
+ from infrahub.core.constants import BranchSupportType, MetadataOptions
8
8
  from infrahub.core.manager import NodeManager
9
+ from infrahub.core.metadata.model import MetadataQueryOptions
10
+ from infrahub.core.node import Node
11
+ from infrahub.core.relationship import Relationship
9
12
  from infrahub.core.schema.relationship_schema import RelationshipSchema
10
13
  from infrahub.core.timestamp import Timestamp
11
14
  from infrahub.database import InfrahubDatabase
12
15
  from infrahub.graphql.field_extractor import extract_graphql_fields
16
+ from infrahub.graphql.metadata import build_metadata_query_options, get_metadata_options_from_fields
13
17
 
14
18
  from ..loaders.node import GetManyParams, NodeDataLoader
15
19
  from ..types import RELATIONS_PROPERTY_MAP, RELATIONS_PROPERTY_MAP_REVERSED
@@ -24,6 +28,25 @@ class SingleRelationshipResolver:
24
28
  def __init__(self) -> None:
25
29
  self._data_loader_instances: dict[GetManyParams, NodeDataLoader] = {}
26
30
 
31
+ def _build_relationship_meta_response(
32
+ self, relationship: Relationship, metadata_fields: dict[str, Any]
33
+ ) -> dict[str, Any]:
34
+ data: dict[str, Any] = {}
35
+ for meta_field in metadata_fields.keys():
36
+ if meta_field == "created_at":
37
+ created_at = relationship._get_created_at()
38
+ data["created_at"] = created_at.to_datetime() if created_at else None
39
+ elif meta_field == "created_by":
40
+ account_id = relationship._get_created_by()
41
+ data["created_by"] = {"id": account_id} if account_id else None
42
+ elif meta_field == "updated_at":
43
+ updated_at = relationship._get_updated_at()
44
+ data["updated_at"] = updated_at.to_datetime() if updated_at else None
45
+ elif meta_field == "updated_by":
46
+ account_id = relationship._get_updated_by()
47
+ data["updated_by"] = {"id": account_id} if account_id else None
48
+ return data
49
+
27
50
  async def resolve(self, parent: dict, info: GraphQLResolveInfo, **kwargs: Any) -> dict[str, Any]:
28
51
  """Resolver for relationships of cardinality=one for Edged responses
29
52
 
@@ -45,50 +68,92 @@ class SingleRelationshipResolver:
45
68
  fields = extract_graphql_fields(info=info)
46
69
  node_fields = fields.get("node", {})
47
70
  property_fields = fields.get("properties", {})
71
+ metadata_fields = {
72
+ "node_metadata": fields.get("node_metadata", {}),
73
+ "relationship_metadata": fields.get("relationship_metadata", {}),
74
+ }
48
75
  for key, value in property_fields.items():
49
76
  mapped_name = RELATIONS_PROPERTY_MAP[key]
50
77
  node_fields[mapped_name] = value
51
78
 
52
79
  metadata_field_names = {prop_name for prop_name in RELATIONS_PROPERTY_MAP if prop_name != "__typename"}
53
- requires_relationship_metadata = bool(set(property_fields.keys()) & metadata_field_names)
80
+ requires_relationship_properties = bool(set(property_fields.keys()) & metadata_field_names)
81
+ requires_relationship_metadata = bool(metadata_fields["relationship_metadata"])
54
82
 
55
83
  # Extract the schema of the node on the other end of the relationship from the GQL Schema
56
84
  node_rel = node_schema.get_relationship(info.field_name)
57
85
 
58
86
  response: dict[str, Any] = {"node": None, "properties": {}}
59
87
 
60
- if requires_relationship_metadata:
61
- node_graph = await self._get_entities_simple(
88
+ relationship: Relationship | None = None
89
+ peer_node: Node | None = None
90
+
91
+ if requires_relationship_properties or requires_relationship_metadata:
92
+ include_metadata = build_metadata_query_options(
93
+ node_metadata_fields=metadata_fields.get("node_metadata"),
94
+ relationship_metadata_fields=metadata_fields.get("relationship_metadata"),
95
+ node_fields=node_fields,
96
+ )
97
+ # Add relationship properties metadata to relationship_level
98
+ include_metadata |= MetadataQueryOptions(
99
+ relationship_level=get_metadata_options_from_fields(property_fields)
100
+ )
101
+ relationship = await self._get_entities_simple(
62
102
  db=graphql_context.db,
63
103
  branch=graphql_context.branch,
64
104
  at=graphql_context.at,
65
- related_node_ids=graphql_context.related_node_ids,
66
105
  field_name=info.field_name,
67
106
  parent_id=parent["id"],
68
107
  source_kind=node_schema.kind,
69
108
  rel_schema=node_rel,
70
109
  node_fields=node_fields,
110
+ include_metadata=include_metadata,
71
111
  **kwargs,
72
112
  )
73
113
  else:
74
- node_graph = await self._get_entities_with_data_loader(
114
+ include_metadata = build_metadata_query_options(
115
+ node_metadata_fields=metadata_fields.get("node_metadata"),
116
+ node_fields=node_fields,
117
+ )
118
+ peer_node = await self._get_entities_with_data_loader(
75
119
  db=graphql_context.db,
76
120
  branch=graphql_context.branch,
77
121
  at=graphql_context.at,
78
- related_node_ids=graphql_context.related_node_ids,
79
122
  rel_schema=node_rel,
80
123
  parent=parent,
81
124
  node_fields=node_fields,
125
+ include_metadata=include_metadata,
82
126
  )
83
127
 
84
- if not node_graph:
128
+ if not relationship and not peer_node:
85
129
  return response
86
- response["node"] = node_graph
87
130
 
88
- for key, mapped in RELATIONS_PROPERTY_MAP_REVERSED.items():
89
- value = node_graph.pop(key, None)
90
- if value:
91
- response["properties"][mapped] = value
131
+ async with graphql_context.db.start_session(read_only=True) as db:
132
+ if relationship:
133
+ node_graph = await relationship.to_graphql(
134
+ db=db, fields=node_fields, related_node_ids=graphql_context.related_node_ids
135
+ )
136
+ peer_node = await relationship.get_peer(db=db)
137
+ elif peer_node:
138
+ node_graph = await peer_node.to_graphql(
139
+ db=db, fields=node_fields, related_node_ids=graphql_context.related_node_ids
140
+ )
141
+
142
+ response["node"] = node_graph
143
+
144
+ for key, mapped in RELATIONS_PROPERTY_MAP_REVERSED.items():
145
+ value = node_graph.pop(key, None)
146
+ if value:
147
+ response["properties"][mapped] = value
148
+
149
+ if metadata_fields.get("node_metadata") and peer_node:
150
+ response["node_metadata"] = await peer_node._build_meta_response("node_metadata", fields)
151
+
152
+ if metadata_fields.get("relationship_metadata") and relationship:
153
+ response["relationship_metadata"] = self._build_relationship_meta_response(
154
+ relationship=relationship, metadata_fields=metadata_fields["relationship_metadata"]
155
+ )
156
+
92
157
  return response
93
158
 
94
159
  async def _get_entities_simple(
@@ -96,14 +161,14 @@ class SingleRelationshipResolver:
96
161
  db: InfrahubDatabase,
97
162
  branch: Branch,
98
163
  at: Timestamp | None,
99
- related_node_ids: set[str] | None,
100
164
  field_name: str,
101
165
  parent_id: str,
102
166
  source_kind: str,
103
167
  rel_schema: RelationshipSchema,
104
168
  node_fields: dict[str, Any],
169
+ include_metadata: MetadataQueryOptions,
105
170
  **kwargs: Any,
106
- ) -> dict[str, Any] | None:
171
+ ) -> Relationship | None:
107
172
  filters = {
108
173
  f"{field_name}__{key}": value
109
174
  for key, value in kwargs.items()
@@ -121,21 +186,22 @@ class SingleRelationshipResolver:
121
186
  branch=branch,
122
187
  branch_agnostic=rel_schema.branch is BranchSupportType.AGNOSTIC,
123
188
  fetch_peers=True,
189
+ include_metadata=include_metadata,
124
190
  )
125
191
  if not objs:
126
192
  return None
127
- return await objs[0].to_graphql(db=dbs, fields=node_fields, related_node_ids=related_node_ids)
193
+ return objs[0]
128
194
 
129
195
  async def _get_entities_with_data_loader(
130
196
  self,
131
197
  db: InfrahubDatabase,
132
198
  branch: Branch,
133
199
  at: Timestamp | None,
134
- related_node_ids: set[str] | None,
135
200
  rel_schema: RelationshipSchema,
136
201
  parent: dict[str, Any],
137
202
  node_fields: dict[str, Any],
138
- ) -> dict[str, Any] | None:
203
+ include_metadata: MetadataQueryOptions,
204
+ ) -> Node | None:
139
205
  try:
140
206
  peer_id: str = parent[rel_schema.name][0]["node"]["id"]
141
207
  except (KeyError, IndexError):
@@ -144,14 +210,13 @@ class SingleRelationshipResolver:
144
210
  if node_fields and "hfid" in node_fields:
145
211
  node_fields["human_friendly_id"] = None
146
212
 
213
+ include_metadata |= MetadataQueryOptions(attribute_level=MetadataOptions.LINKED_NODES)
147
214
  query_params = GetManyParams(
148
215
  fields=node_fields,
149
216
  at=at,
150
217
  branch=branch,
151
- include_source=True,
152
- include_owner=True,
218
+ include_metadata=include_metadata,
153
219
  prefetch_relationships=False,
154
- account=None,
155
220
  branch_agnostic=rel_schema.branch is BranchSupportType.AGNOSTIC,
156
221
  )
157
222
  if query_params in self._data_loader_instances:
@@ -162,5 +227,4 @@ class SingleRelationshipResolver:
162
227
  node = await loader.load(key=peer_id)
163
228
  if not node:
164
229
  return None
165
- async with db.start_session(read_only=True) as dbs:
166
- return await node.to_graphql(db=dbs, fields=node_fields, related_node_ids=related_node_ids)
230
+ return node
@@ -8,6 +8,7 @@ from infrahub.core.constants import InfrahubKind
8
8
  from infrahub.core.manager import NodeManager
9
9
  from infrahub.core.protocols import CoreGraphQLQuery
10
10
  from infrahub.core.timestamp import Timestamp
11
+ from infrahub.graphql.resolvers.account_metadata import AccountMetadataResolver
11
12
  from infrahub.graphql.resolvers.many_relationship import ManyRelationshipResolver
12
13
  from infrahub.graphql.resolvers.single_relationship import SingleRelationshipResolver
13
14
  from infrahub.log import get_logger
@@ -50,6 +51,7 @@ async def resolver_graphql_query(
50
51
  types=graphql_context.types,
51
52
  single_relationship_resolver=SingleRelationshipResolver(),
52
53
  many_relationship_resolver=ManyRelationshipResolver(),
54
+ account_metadata_resolver=AccountMetadataResolver(),
53
55
  ),
54
56
  root_value=None,
55
57
  variable_values=params or {},
@@ -66,7 +66,6 @@ __all__ = [
66
66
 
67
67
 
68
68
  RELATIONS_PROPERTY_MAP: dict[str, str] = {
69
- "is_visible": "_relation__is_visible",
70
69
  "is_protected": "_relation__is_protected",
71
70
  "owner": "_relation__owner",
72
71
  "source": "_relation__source",
@@ -22,7 +22,6 @@ class RelatedNodeInput(InputObjectType):
22
22
  hfid = Field(List(of_type=String), required=False)
23
23
  kind = String(required=False) # Only used to resolve hfid of a related node on a generic relationship, see #4649
24
24
  from_pool = Field(GenericPoolInput, required=False)
25
- _relation__is_visible = Boolean(required=False)
26
25
  _relation__is_protected = Boolean(required=False)
27
26
  _relation__owner = String(required=False)
28
27
  _relation__source = String(required=False)
@@ -41,7 +40,6 @@ class IPPrefixPoolInput(GenericPoolInput):
41
40
  class RelatedIPAddressNodeInput(InputObjectType):
42
41
  id = String(required=False)
43
42
  from_pool = Field(IPAddressPoolInput, required=False)
44
- _relation__is_visible = Boolean(required=False)
45
43
  _relation__is_protected = Boolean(required=False)
46
44
  _relation__owner = String(required=False)
47
45
  _relation__source = String(required=False)
@@ -51,7 +49,6 @@ class RelatedIPPrefixNodeInput(InputObjectType):
51
49
  id = String(required=False)
52
50
  hfid = Field(List(of_type=String), required=False)
53
51
  from_pool = Field(IPPrefixPoolInput, required=False)
54
- _relation__is_visible = Boolean(required=False)
55
52
  _relation__is_protected = Boolean(required=False)
56
53
  _relation__owner = String(required=False)
57
54
  _relation__source = String(required=False)
@@ -65,7 +62,6 @@ class AttributeInterface(InfrahubInterface):
65
62
  is_default = Field(Boolean)
66
63
  is_inherited = Field(Boolean)
67
64
  is_protected = Field(Boolean)
68
- is_visible = Field(Boolean)
69
65
  updated_at = Field(DateTime)
70
66
  # Since source and owner are using a Type that is generated dynamically
71
67
  # these 2 fields will be dynamically inserted when we generate the GraphQL Schema
@@ -73,7 +69,16 @@ class AttributeInterface(InfrahubInterface):
73
69
  # owner = Field("DataOwner")
74
70
 
75
71
 
76
- class BaseAttribute(ObjectType):
72
+ class InfrahubAttributeMetaObject(ObjectType):
73
+ # updated_by is dynamically added in GraphQLSchemaManager.generate_object_types()
74
+ # to use the account_type (CoreGenericAccount) instead of a plain String
75
+ updated_at = DateTime(
76
+ required=False,
77
+ description="Date/Time when the attribute was last modified by a user or a system task",
78
+ )
79
+
80
+
81
+ class BaseAttribute(InfrahubAttributeMetaObject):
77
82
  id = Field(String)
78
83
  is_from_profile = Field(Boolean)
79
84
  permissions = Field(PermissionType, required=False)
@@ -2,13 +2,14 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING, Any
4
4
 
5
- from graphene import Boolean, Field, Int, List, NonNull, String
5
+ from graphene import Boolean, Field, Int, List, NonNull, ObjectType, String
6
6
 
7
7
  from infrahub.core.branch import Branch
8
- from infrahub.core.constants import GLOBAL_BRANCH_NAME
8
+ from infrahub.core.node.standard import StandardNodeQueryFields
9
9
 
10
10
  from ...exceptions import BranchNotFoundError
11
11
  from .enums import InfrahubBranchStatus
12
+ from .metadata import InfrahubStandardNodeMetaData
12
13
  from .standard_node import InfrahubObjectType
13
14
 
14
15
  if TYPE_CHECKING:
@@ -34,56 +35,35 @@ class BranchType(InfrahubObjectType):
34
35
  name = "Branch"
35
36
  model = Branch
36
37
 
37
- @staticmethod
38
- async def _map_fields_to_graphql(objs: list[Branch], fields: dict) -> list[dict[str, Any]]:
39
- return [await obj.to_graphql(fields=fields) for obj in objs]
40
-
41
38
  @classmethod
42
39
  async def get_list(
43
40
  cls,
44
- fields: dict,
41
+ fields: StandardNodeQueryFields,
45
42
  graphql_context: GraphqlContext,
46
43
  **kwargs: Any,
47
44
  ) -> list[dict[str, Any]]:
48
45
  async with graphql_context.db.start_session(read_only=True) as db:
49
46
  objs = await Branch.get_list(db=db, **kwargs)
47
+ return [await obj.to_graphql_flat(fields=fields.node) for obj in objs]
50
48
 
51
- if not objs:
52
- return []
53
-
54
- return await cls._map_fields_to_graphql(objs=objs, fields=fields)
55
49
 
56
- @classmethod
57
- async def get_by_name(
58
- cls,
59
- fields: dict,
60
- graphql_context: GraphqlContext,
61
- name: str,
62
- ) -> dict[str, Any]:
63
- branch_responses = await cls.get_list(fields=fields, graphql_context=graphql_context, name=name)
64
-
65
- if branch_responses:
66
- return branch_responses[0]
67
- raise BranchNotFoundError(f"Branch with name '{name}' not found")
68
-
69
-
70
- class RequiredStringValueField(InfrahubObjectType):
50
+ class RequiredStringValueField(ObjectType):
71
51
  value = String(required=True)
72
52
 
73
53
 
74
- class NonRequiredStringValueField(InfrahubObjectType):
54
+ class NonRequiredStringValueField(ObjectType):
75
55
  value = String(required=False)
76
56
 
77
57
 
78
- class NonRequiredIntValueField(InfrahubObjectType):
58
+ class NonRequiredIntValueField(ObjectType):
79
59
  value = Int(required=False)
80
60
 
81
61
 
82
- class NonRequiredBooleanValueField(InfrahubObjectType):
62
+ class NonRequiredBooleanValueField(ObjectType):
83
63
  value = Boolean(required=False)
84
64
 
85
65
 
86
- class StatusField(InfrahubObjectType):
66
+ class StatusField(ObjectType):
87
67
  value = InfrahubBranchStatus(required=True)
88
68
 
89
69
 
@@ -101,36 +81,43 @@ class InfrahubBranch(BranchType):
101
81
  )
102
82
  has_schema_changes = Field(NonRequiredBooleanValueField, required=False)
103
83
 
84
+ @classmethod
85
+ async def get_list(
86
+ cls,
87
+ fields: StandardNodeQueryFields,
88
+ graphql_context: GraphqlContext,
89
+ **kwargs: Any,
90
+ ) -> list[dict[str, Any]]:
91
+ async with graphql_context.db.start_session(read_only=True) as db:
92
+ objs = await Branch.get_list(db=db, **kwargs)
93
+ return [await obj.to_graphql(fields=fields) for obj in objs]
94
+
95
+ @classmethod
96
+ async def get_by_name(
97
+ cls,
98
+ fields: dict,
99
+ graphql_context: GraphqlContext,
100
+ name: str,
101
+ ) -> dict[str, Any]:
102
+ branch_responses = await cls.get_list(
103
+ fields=StandardNodeQueryFields(node=fields), graphql_context=graphql_context, name=name
104
+ )
105
+
106
+ if branch_responses:
107
+ return branch_responses[0]
108
+ raise BranchNotFoundError(f"Branch with name '{name}' not found")
109
+
104
110
  class Meta:
105
111
  description = "InfrahubBranch"
106
112
  name = "InfrahubBranch"
107
113
 
108
- @staticmethod
109
- async def _map_fields_to_graphql(objs: list[Branch], fields: dict) -> list[dict[str, Any]]:
110
- field_keys = fields.keys()
111
- result: list[dict[str, Any]] = []
112
- for obj in objs:
113
- if obj.name == GLOBAL_BRANCH_NAME:
114
- continue
115
- data: dict[str, Any] = {}
116
- for field in field_keys:
117
- if field == "id":
118
- data["id"] = obj.uuid
119
- continue
120
- value = getattr(obj, field, None)
121
- if isinstance(fields.get(field), dict):
122
- data[field] = {"value": value}
123
- else:
124
- data[field] = value
125
- result.append(data)
126
- return result
127
-
128
-
129
- class InfrahubBranchEdge(InfrahubObjectType):
114
+
115
+ class InfrahubBranchEdge(ObjectType):
130
116
  node = Field(InfrahubBranch, required=True)
117
+ node_metadata = Field(InfrahubStandardNodeMetaData, required=True)
131
118
 
132
119
 
133
- class InfrahubBranchType(InfrahubObjectType):
120
+ class InfrahubBranchType(ObjectType):
134
121
  count = Field(Int, description="Total number of items")
135
122
  edges = Field(NonNull(List(of_type=NonNull(InfrahubBranchEdge))))
136
123
  default_branch = Field(
@@ -1,5 +1,6 @@
1
1
  from graphene import Enum
2
2
 
3
+ from infrahub.constants.enums import OrderDirection
3
4
  from infrahub.core import constants
4
5
  from infrahub.core.branch.enums import BranchStatus
5
6
  from infrahub.permissions import constants as permission_constants
@@ -13,3 +14,5 @@ Severity = Enum.from_enum(constants.Severity)
13
14
  BranchRelativePermissionDecision = Enum.from_enum(permission_constants.BranchRelativePermissionDecision)
14
15
 
15
16
  InfrahubBranchStatus = Enum.from_enum(BranchStatus)
17
+
18
+ InfrahubOrderDirection = Enum.from_enum(OrderDirection)
@@ -0,0 +1,28 @@
1
+ from __future__ import annotations
2
+
3
+ from graphene import Boolean, DateTime, Field, InputObjectType, ObjectType
4
+
5
+ from infrahub.graphql.types.enums import InfrahubOrderDirection
6
+
7
+
8
+ class InfrahubNodeMetadataOrder(InputObjectType):
9
+ created_at = Field(InfrahubOrderDirection, required=False, description="Order by creation timestamp")
10
+ updated_at = Field(InfrahubOrderDirection, required=False, description="Order by updated timestamp")
11
+
12
+
13
+ class OrderInput(InputObjectType):
14
+ disable = Boolean(required=False)
15
+ node_metadata = Field(InfrahubNodeMetadataOrder, required=False, description="Order settings for branch metadata")
16
+
17
+
18
+ class InfrahubStandardNodeMetaData(ObjectType):
19
+ """Base metadata type for standard nodes.
20
+
21
+ Note: created_by and updated_by fields are added dynamically by
22
+ GraphQLSchemaManager._patch_static_types() to use the GenericAccount interface.
23
+ """
24
+
25
+ created_at = DateTime(required=False, description="Date/Time the object has been created")
26
+ updated_at = DateTime(
27
+ required=False, description="Date/Time when the object was last modified by a user or a system task"
28
+ )
@@ -5,7 +5,7 @@ from typing import Any
5
5
  from graphene import ObjectType
6
6
  from graphene.types.objecttype import ObjectTypeOptions
7
7
 
8
- from infrahub.core.schema import GenericSchema, MainSchemaTypes, NodeSchema, ProfileSchema, TemplateSchema
8
+ from infrahub.core.schema import MainSchemaTypes
9
9
 
10
10
 
11
11
  class InfrahubObjectOptions(ObjectTypeOptions):
@@ -21,7 +21,27 @@ class InfrahubObject(ObjectType):
21
21
  _meta: InfrahubObjectOptions | None = None,
22
22
  **options: Any,
23
23
  ) -> None:
24
- if not isinstance(schema, NodeSchema | GenericSchema | ProfileSchema | TemplateSchema):
24
+ if not isinstance(schema, MainSchemaTypes):
25
+ raise ValueError(f"You need to pass a valid NodeSchema in '{cls.__name__}.Meta', received '{schema}'")
26
+
27
+ if not _meta:
28
+ _meta = InfrahubObjectOptions(cls)
29
+
30
+ _meta.schema = schema
31
+
32
+ super().__init_subclass_with_meta__(_meta=_meta, interfaces=interfaces, **options)
33
+
34
+
35
+ class InfrahubObjectWithoutMeta(ObjectType):
36
+ @classmethod
37
+ def __init_subclass_with_meta__(
38
+ cls,
39
+ schema: MainSchemaTypes | None = None,
40
+ interfaces: tuple = (),
41
+ _meta: InfrahubObjectOptions | None = None,
42
+ **options: Any,
43
+ ) -> None:
44
+ if not isinstance(schema, MainSchemaTypes):
25
45
  raise ValueError(f"You need to pass a valid NodeSchema in '{cls.__name__}.Meta', received '{schema}'")
26
46
 
27
47
  if not _meta:
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from graphene import Field, List, NonNull, ObjectType, String
3
+ from graphene import DateTime, Field, List, NonNull, ObjectType, String
4
4
 
5
5
 
6
6
  class RelationshipPeer(ObjectType):
@@ -8,7 +8,15 @@ class RelationshipPeer(ObjectType):
8
8
  kind = String(required=False)
9
9
 
10
10
 
11
- class Relationship(ObjectType):
11
+ class InfrahubRelationshipMetaObject(ObjectType):
12
+ updated_by = String(required=False, description="User that last modified the relationship")
13
+ updated_at = DateTime(
14
+ required=False,
15
+ description="Date/Time when the relationship was last modified by a user or a system task",
16
+ )
17
+
18
+
19
+ class Relationship(InfrahubRelationshipMetaObject):
12
20
  id = String(required=False)
13
21
  identifier = String(required=False)
14
22
  peers = List(NonNull(RelationshipPeer))
@@ -8,16 +8,23 @@ from graphene.types.objecttype import ObjectTypeOptions
8
8
  from infrahub import config
9
9
 
10
10
  if TYPE_CHECKING:
11
+ from infrahub.core.node.standard import StandardNodeQueryFields
11
12
  from infrahub.graphql.initialization import GraphqlContext
12
13
 
13
14
 
14
15
  class InfrahubObjectTypeOptions(ObjectTypeOptions):
15
- model = None
16
+ model: type | None = None
16
17
 
17
18
 
18
19
  class InfrahubObjectType(ObjectType):
19
20
  @classmethod
20
- def __init_subclass_with_meta__(cls, model=None, interfaces=(), _meta=None, **options) -> None:
21
+ def __init_subclass_with_meta__(
22
+ cls,
23
+ model: type | None = None,
24
+ interfaces: tuple[type, ...] = (),
25
+ _meta: InfrahubObjectTypeOptions | None = None,
26
+ **options: Any,
27
+ ) -> None:
21
28
  if not _meta:
22
29
  _meta = InfrahubObjectTypeOptions(cls)
23
30
 
@@ -26,7 +33,9 @@ class InfrahubObjectType(ObjectType):
26
33
  super().__init_subclass_with_meta__(_meta=_meta, interfaces=interfaces, **options)
27
34
 
28
35
  @classmethod
29
- async def get_list(cls, fields: dict[str, Any], graphql_context: GraphqlContext, **kwargs) -> list[dict[str, Any]]:
36
+ async def get_list(
37
+ cls, fields: StandardNodeQueryFields, graphql_context: GraphqlContext, **kwargs
38
+ ) -> list[dict[str, Any]]:
30
39
  async with graphql_context.db.session(database=config.SETTINGS.database.database_name) as db:
31
40
  filters = {key: value for key, value in kwargs.items() if "__" in key and value}
32
41
 
@@ -35,16 +44,12 @@ class InfrahubObjectType(ObjectType):
35
44
  filters=filters,
36
45
  at=graphql_context.at,
37
46
  branch=graphql_context.branch,
38
- account=graphql_context.account_session,
39
- include_source=True,
40
47
  db=db,
41
48
  )
42
49
  else:
43
50
  objs = await cls._meta.model.get_list(
44
51
  at=graphql_context.at,
45
52
  branch=graphql_context.branch,
46
- account=graphql_context.account_session,
47
- include_source=True,
48
53
  db=db,
49
54
  )
50
55
 
infrahub/hfid/tasks.py CHANGED
@@ -20,9 +20,11 @@ UPDATE_HFID = """
20
20
  mutation UpdateHFID(
21
21
  $id: String!,
22
22
  $kind: String!,
23
- $value: [String!]!
23
+ $value: [String!]!,
24
+ $context_account_id: String!
24
25
  ) {
25
26
  InfrahubUpdateHFID(
27
+ context: {account: {id: $context_account_id}},
26
28
  data: {id: $id, value: $value, kind: $kind}
27
29
  ) {
28
30
  ok
@@ -40,6 +42,7 @@ async def hfid_update_value(
40
42
  obj: HFIDGraphQLResponse,
41
43
  node_kind: str,
42
44
  hfid_definition: list[str],
45
+ context: InfrahubContext,
43
46
  ) -> None:
44
47
  log = get_run_logger()
45
48
  client = get_client()
@@ -58,7 +61,12 @@ async def hfid_update_value(
58
61
  try:
59
62
  await client.execute_graphql(
60
63
  query=UPDATE_HFID,
61
- variables={"id": obj.node_id, "kind": node_kind, "value": rendered_hfid},
64
+ variables={
65
+ "id": obj.node_id,
66
+ "kind": node_kind,
67
+ "value": rendered_hfid,
68
+ "context_account_id": context.account.account_id,
69
+ },
62
70
  branch_name=branch_name,
63
71
  )
64
72
  log.info(f"Updating {node_kind}.human_friendly_id='{rendered_hfid}' ({obj.node_id})")
@@ -77,7 +85,7 @@ async def process_hfid(
77
85
  node_kind: str,
78
86
  object_id: str,
79
87
  target_kind: str,
80
- context: InfrahubContext, # noqa: ARG001
88
+ context: InfrahubContext,
81
89
  ) -> None:
82
90
  log = get_run_logger()
83
91
  client = get_client()
@@ -115,6 +123,7 @@ async def process_hfid(
115
123
  obj=node,
116
124
  node_kind=node_schema.kind,
117
125
  hfid_definition=hfid_definition.hfid,
126
+ context=context,
118
127
  )
119
128
 
120
129
  _ = [response async for _, response in batch.execute()]
infrahub/lock.py CHANGED
@@ -10,6 +10,7 @@ from typing import TYPE_CHECKING
10
10
 
11
11
  import redis.asyncio as redis
12
12
  from prometheus_client import Histogram
13
+ from redis import UsernamePasswordCredentialProvider
13
14
  from redis.asyncio.lock import Lock as GlobalLock
14
15
 
15
16
  from infrahub import config
@@ -275,10 +276,16 @@ class InfrahubLockRegistry:
275
276
  ) -> None:
276
277
  if config.SETTINGS.cache.enable and not local_only:
277
278
  if config.SETTINGS.cache.driver == config.CacheDriver.Redis:
279
+ credential_provider: UsernamePasswordCredentialProvider | None = None
280
+ if config.SETTINGS.cache.username and config.SETTINGS.cache.password:
281
+ credential_provider = UsernamePasswordCredentialProvider(
282
+ username=config.SETTINGS.cache.username, password=config.SETTINGS.cache.password
283
+ )
278
284
  self.connection = redis.Redis(
279
285
  host=config.SETTINGS.cache.address,
280
286
  port=config.SETTINGS.cache.service_port,
281
287
  db=config.SETTINGS.cache.database,
288
+ credential_provider=credential_provider,
282
289
  ssl=config.SETTINGS.cache.tls_enabled,
283
290
  ssl_cert_reqs="optional" if not config.SETTINGS.cache.tls_insecure else "none",
284
291
  ssl_check_hostname=not config.SETTINGS.cache.tls_insecure,