infrahub-server 1.3.7__py3-none-any.whl → 1.4.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 (174) hide show
  1. infrahub/api/internal.py +5 -0
  2. infrahub/artifacts/tasks.py +17 -22
  3. infrahub/branch/merge_mutation_checker.py +38 -0
  4. infrahub/cli/__init__.py +2 -2
  5. infrahub/cli/context.py +7 -3
  6. infrahub/cli/db.py +5 -16
  7. infrahub/cli/upgrade.py +10 -29
  8. infrahub/computed_attribute/tasks.py +36 -46
  9. infrahub/config.py +57 -6
  10. infrahub/constants/environment.py +1 -0
  11. infrahub/core/attribute.py +15 -7
  12. infrahub/core/branch/tasks.py +43 -41
  13. infrahub/core/constants/__init__.py +21 -6
  14. infrahub/core/constants/infrahubkind.py +2 -0
  15. infrahub/core/diff/coordinator.py +3 -1
  16. infrahub/core/diff/model/path.py +0 -39
  17. infrahub/core/diff/repository/repository.py +0 -8
  18. infrahub/core/diff/tasks.py +11 -8
  19. infrahub/core/graph/__init__.py +1 -1
  20. infrahub/core/graph/index.py +1 -2
  21. infrahub/core/graph/schema.py +50 -29
  22. infrahub/core/initialization.py +81 -47
  23. infrahub/core/ipam/tasks.py +4 -3
  24. infrahub/core/merge.py +8 -10
  25. infrahub/core/migrations/__init__.py +2 -0
  26. infrahub/core/migrations/graph/__init__.py +4 -0
  27. infrahub/core/migrations/graph/m036_drop_attr_value_index.py +45 -0
  28. infrahub/core/migrations/graph/m037_index_attr_vals.py +577 -0
  29. infrahub/core/migrations/query/attribute_add.py +27 -2
  30. infrahub/core/migrations/schema/attribute_kind_update.py +156 -0
  31. infrahub/core/migrations/schema/tasks.py +6 -5
  32. infrahub/core/models.py +5 -1
  33. infrahub/core/node/proposed_change.py +43 -0
  34. infrahub/core/protocols.py +12 -0
  35. infrahub/core/query/attribute.py +32 -14
  36. infrahub/core/query/diff.py +11 -0
  37. infrahub/core/query/ipam.py +13 -7
  38. infrahub/core/query/node.py +51 -10
  39. infrahub/core/query/resource_manager.py +3 -3
  40. infrahub/core/schema/basenode_schema.py +8 -0
  41. infrahub/core/schema/definitions/core/__init__.py +10 -1
  42. infrahub/core/schema/definitions/core/ipam.py +28 -2
  43. infrahub/core/schema/definitions/core/propose_change.py +15 -0
  44. infrahub/core/schema/definitions/core/webhook.py +3 -0
  45. infrahub/core/schema/definitions/internal.py +1 -1
  46. infrahub/core/schema/generated/attribute_schema.py +1 -1
  47. infrahub/core/schema/generic_schema.py +10 -0
  48. infrahub/core/schema/manager.py +10 -1
  49. infrahub/core/schema/node_schema.py +22 -22
  50. infrahub/core/schema/profile_schema.py +8 -0
  51. infrahub/core/schema/schema_branch.py +11 -7
  52. infrahub/core/schema/template_schema.py +8 -0
  53. infrahub/core/validators/attribute/kind.py +5 -1
  54. infrahub/core/validators/checks_runner.py +5 -5
  55. infrahub/core/validators/determiner.py +22 -2
  56. infrahub/core/validators/tasks.py +6 -7
  57. infrahub/core/validators/uniqueness/checker.py +4 -2
  58. infrahub/core/validators/uniqueness/model.py +1 -0
  59. infrahub/core/validators/uniqueness/query.py +57 -7
  60. infrahub/database/__init__.py +2 -1
  61. infrahub/events/__init__.py +20 -0
  62. infrahub/events/constants.py +7 -0
  63. infrahub/events/generator.py +29 -2
  64. infrahub/events/proposed_change_action.py +203 -0
  65. infrahub/generators/tasks.py +24 -20
  66. infrahub/git/base.py +4 -7
  67. infrahub/git/integrator.py +21 -12
  68. infrahub/git/repository.py +15 -30
  69. infrahub/git/tasks.py +121 -106
  70. infrahub/graphql/app.py +2 -1
  71. infrahub/graphql/field_extractor.py +69 -0
  72. infrahub/graphql/manager.py +15 -11
  73. infrahub/graphql/mutations/account.py +2 -2
  74. infrahub/graphql/mutations/action.py +8 -2
  75. infrahub/graphql/mutations/artifact_definition.py +4 -1
  76. infrahub/graphql/mutations/branch.py +10 -5
  77. infrahub/graphql/mutations/graphql_query.py +2 -1
  78. infrahub/graphql/mutations/main.py +14 -8
  79. infrahub/graphql/mutations/menu.py +2 -1
  80. infrahub/graphql/mutations/proposed_change.py +230 -8
  81. infrahub/graphql/mutations/relationship.py +5 -0
  82. infrahub/graphql/mutations/repository.py +2 -1
  83. infrahub/graphql/mutations/tasks.py +7 -9
  84. infrahub/graphql/mutations/webhook.py +4 -1
  85. infrahub/graphql/parser.py +15 -6
  86. infrahub/graphql/queries/__init__.py +10 -1
  87. infrahub/graphql/queries/account.py +3 -3
  88. infrahub/graphql/queries/branch.py +2 -2
  89. infrahub/graphql/queries/diff/tree.py +56 -5
  90. infrahub/graphql/queries/event.py +13 -3
  91. infrahub/graphql/queries/ipam.py +23 -1
  92. infrahub/graphql/queries/proposed_change.py +84 -0
  93. infrahub/graphql/queries/relationship.py +2 -2
  94. infrahub/graphql/queries/resource_manager.py +3 -3
  95. infrahub/graphql/queries/search.py +3 -2
  96. infrahub/graphql/queries/status.py +3 -2
  97. infrahub/graphql/queries/task.py +2 -2
  98. infrahub/graphql/resolvers/ipam.py +440 -0
  99. infrahub/graphql/resolvers/many_relationship.py +4 -3
  100. infrahub/graphql/resolvers/resolver.py +5 -5
  101. infrahub/graphql/resolvers/single_relationship.py +3 -2
  102. infrahub/graphql/schema.py +25 -5
  103. infrahub/graphql/types/__init__.py +2 -2
  104. infrahub/graphql/types/attribute.py +3 -3
  105. infrahub/graphql/types/event.py +68 -0
  106. infrahub/groups/tasks.py +6 -6
  107. infrahub/lock.py +3 -2
  108. infrahub/menu/generator.py +8 -0
  109. infrahub/message_bus/operations/__init__.py +9 -12
  110. infrahub/message_bus/operations/git/file.py +6 -5
  111. infrahub/message_bus/operations/git/repository.py +12 -20
  112. infrahub/message_bus/operations/refresh/registry.py +15 -9
  113. infrahub/message_bus/operations/send/echo.py +7 -4
  114. infrahub/message_bus/types.py +1 -0
  115. infrahub/permissions/__init__.py +2 -1
  116. infrahub/permissions/constants.py +13 -0
  117. infrahub/permissions/globals.py +31 -2
  118. infrahub/permissions/manager.py +8 -5
  119. infrahub/pools/prefix.py +7 -5
  120. infrahub/prefect_server/app.py +31 -0
  121. infrahub/prefect_server/bootstrap.py +18 -0
  122. infrahub/proposed_change/action_checker.py +206 -0
  123. infrahub/proposed_change/approval_revoker.py +40 -0
  124. infrahub/proposed_change/branch_diff.py +3 -1
  125. infrahub/proposed_change/checker.py +45 -0
  126. infrahub/proposed_change/constants.py +32 -2
  127. infrahub/proposed_change/tasks.py +182 -150
  128. infrahub/py.typed +0 -0
  129. infrahub/server.py +29 -17
  130. infrahub/services/__init__.py +13 -28
  131. infrahub/services/adapters/cache/__init__.py +4 -0
  132. infrahub/services/adapters/cache/nats.py +2 -0
  133. infrahub/services/adapters/cache/redis.py +3 -0
  134. infrahub/services/adapters/message_bus/__init__.py +0 -2
  135. infrahub/services/adapters/message_bus/local.py +1 -2
  136. infrahub/services/adapters/message_bus/nats.py +6 -8
  137. infrahub/services/adapters/message_bus/rabbitmq.py +7 -9
  138. infrahub/services/adapters/workflow/__init__.py +1 -0
  139. infrahub/services/adapters/workflow/local.py +1 -8
  140. infrahub/services/component.py +2 -1
  141. infrahub/task_manager/event.py +56 -0
  142. infrahub/task_manager/models.py +9 -0
  143. infrahub/tasks/artifact.py +6 -7
  144. infrahub/tasks/check.py +4 -7
  145. infrahub/telemetry/tasks.py +15 -18
  146. infrahub/transformations/tasks.py +10 -6
  147. infrahub/trigger/tasks.py +4 -3
  148. infrahub/types.py +4 -0
  149. infrahub/validators/events.py +7 -7
  150. infrahub/validators/tasks.py +6 -7
  151. infrahub/webhook/models.py +45 -45
  152. infrahub/webhook/tasks.py +25 -24
  153. infrahub/workers/dependencies.py +143 -0
  154. infrahub/workers/infrahub_async.py +19 -43
  155. infrahub/workflows/catalogue.py +16 -2
  156. infrahub/workflows/initialization.py +5 -4
  157. infrahub/workflows/models.py +2 -0
  158. infrahub_sdk/client.py +2 -2
  159. infrahub_sdk/ctl/repository.py +51 -0
  160. infrahub_sdk/ctl/schema.py +9 -9
  161. infrahub_sdk/node/node.py +2 -2
  162. infrahub_sdk/pytest_plugin/items/graphql_query.py +1 -1
  163. infrahub_sdk/schema/repository.py +1 -1
  164. infrahub_sdk/testing/docker.py +1 -1
  165. infrahub_sdk/utils.py +2 -2
  166. {infrahub_server-1.3.7.dist-info → infrahub_server-1.4.0.dist-info}/METADATA +7 -5
  167. {infrahub_server-1.3.7.dist-info → infrahub_server-1.4.0.dist-info}/RECORD +174 -158
  168. infrahub_testcontainers/container.py +17 -0
  169. infrahub_testcontainers/docker-compose-cluster.test.yml +56 -1
  170. infrahub_testcontainers/docker-compose.test.yml +56 -1
  171. infrahub_testcontainers/helpers.py +4 -1
  172. {infrahub_server-1.3.7.dist-info → infrahub_server-1.4.0.dist-info}/LICENSE.txt +0 -0
  173. {infrahub_server-1.3.7.dist-info → infrahub_server-1.4.0.dist-info}/WHEEL +0 -0
  174. {infrahub_server-1.3.7.dist-info → infrahub_server-1.4.0.dist-info}/entry_points.txt +0 -0
@@ -2,10 +2,11 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING, Any
4
4
 
5
- from graphene import Argument, Boolean, DateTime, Field, Int, List, NonNull, ObjectType, String
6
- from infrahub_sdk.utils import extract_fields_first_node
5
+ from graphene import Argument, Boolean, DateTime, Enum, Field, Int, List, NonNull, ObjectType, String
7
6
 
7
+ from infrahub.events.constants import EventSortOrder
8
8
  from infrahub.exceptions import ValidationError
9
+ from infrahub.graphql.field_extractor import extract_graphql_fields
9
10
  from infrahub.graphql.types.event import EventNodes, EventTypeFilter
10
11
  from infrahub.task_manager.event import PrefectEvent
11
12
  from infrahub.task_manager.models import InfrahubEventFilter
@@ -15,6 +16,8 @@ if TYPE_CHECKING:
15
16
 
16
17
  from graphql import GraphQLResolveInfo
17
18
 
19
+ InfrahubEventSortOrder = Enum.from_enum(EventSortOrder)
20
+
18
21
 
19
22
  class Events(ObjectType):
20
23
  edges = List(NonNull(EventNodes), required=True)
@@ -24,6 +27,7 @@ class Events(ObjectType):
24
27
  async def resolve(
25
28
  root: dict, # noqa: ARG004
26
29
  info: GraphQLResolveInfo,
30
+ order: EventSortOrder,
27
31
  limit: int = 10,
28
32
  has_children: bool | None = None,
29
33
  level: int | None = None,
@@ -57,6 +61,7 @@ class Events(ObjectType):
57
61
  since=since,
58
62
  until=until,
59
63
  level=level,
64
+ order=order,
60
65
  )
61
66
 
62
67
  return await Events.query(
@@ -74,7 +79,7 @@ class Events(ObjectType):
74
79
  limit: int,
75
80
  offset: int | None = None,
76
81
  ) -> dict[str, Any]:
77
- fields = await extract_fields_first_node(info)
82
+ fields = extract_graphql_fields(info)
78
83
 
79
84
  prefect_tasks = await PrefectEvent.query(
80
85
  fields=fields,
@@ -110,6 +115,11 @@ Event = Field(
110
115
  branches=List(NonNull(String), required=False, description="Filter the query to specific branches"),
111
116
  account__ids=List(NonNull(String), required=False, description="Filter the query to specific accounts"),
112
117
  ids=List(NonNull(String)),
118
+ order=InfrahubEventSortOrder(
119
+ required=False,
120
+ default_value=EventSortOrder.DESC,
121
+ description="Sort order of the events, defaults to descending order",
122
+ ),
113
123
  resolver=Events.resolve,
114
124
  required=True,
115
125
  )
@@ -74,7 +74,7 @@ class IPPrefixGetNextAvailable(ObjectType):
74
74
  root: dict, # noqa: ARG004
75
75
  info: GraphQLResolveInfo,
76
76
  prefix_id: str,
77
- prefix_length: int,
77
+ prefix_length: int | None = None,
78
78
  ) -> dict[str, str]:
79
79
  graphql_context: GraphqlContext = info.context
80
80
 
@@ -119,3 +119,25 @@ InfrahubIPPrefixGetNextAvailable = Field(
119
119
  resolver=IPPrefixGetNextAvailable.resolve,
120
120
  required=True,
121
121
  )
122
+
123
+ # The following two query fields must be removed once we are sure that people are not using the old queries anymore. Those fields only exist to
124
+ # expose a deprecation message.
125
+
126
+ DeprecatedIPAddressGetNextAvailable = Field(
127
+ IPAddressGetNextAvailable,
128
+ prefix_id=String(required=True),
129
+ prefix_length=Int(required=False),
130
+ resolver=IPAddressGetNextAvailable.resolve,
131
+ required=True,
132
+ deprecation_reason="This query has been renamed to 'InfrahubIPAddressGetNextAvailable'. It will be removed in the next version of Infrahub.",
133
+ )
134
+
135
+
136
+ DeprecatedIPPrefixGetNextAvailable = Field(
137
+ IPPrefixGetNextAvailable,
138
+ prefix_id=String(required=True),
139
+ prefix_length=Int(required=False),
140
+ resolver=IPPrefixGetNextAvailable.resolve,
141
+ required=True,
142
+ deprecation_reason="This query has been renamed to 'InfrahubIPPrefixGetNextAvailable'. It will be removed in the next version of Infrahub.",
143
+ )
@@ -0,0 +1,84 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from graphene import Boolean, Field, Int, List, NonNull, ObjectType, String
6
+ from infrahub_sdk.utils import extract_fields_first_node
7
+
8
+ from infrahub.core.manager import NodeManager
9
+ from infrahub.core.protocols import CoreGenericAccount, CoreProposedChange
10
+ from infrahub.proposed_change.action_checker import ACTION_RULES, ActionRulesEvaluator
11
+
12
+ if TYPE_CHECKING:
13
+ from graphql import GraphQLResolveInfo
14
+
15
+ from infrahub.graphql.initialization import GraphqlContext
16
+
17
+
18
+ class ActionAvailability(ObjectType):
19
+ action = Field(String, required=True, description="The action that a user may want to take on a proposed change")
20
+ available = Field(Boolean, required=True, description="Tells if the action is available")
21
+ unavailability_reason = Field(String, required=False, description="The reason why an action may be unavailable")
22
+
23
+
24
+ class ActionAvailabilityEdge(ObjectType):
25
+ node = Field(ActionAvailability, required=True)
26
+
27
+
28
+ class AvailableActions(ObjectType):
29
+ count = Field(Int, required=True, description="The number of available actions for the proposed change.")
30
+ edges = Field(List(of_type=NonNull(ActionAvailabilityEdge), required=True), required=True)
31
+
32
+ @staticmethod
33
+ async def resolve(
34
+ root: dict, # noqa: ARG004
35
+ info: GraphQLResolveInfo,
36
+ proposed_change_id: str,
37
+ ) -> dict:
38
+ graphql_context: GraphqlContext = info.context
39
+ proposed_change = await NodeManager.get_one(
40
+ kind=CoreProposedChange,
41
+ id=proposed_change_id,
42
+ db=graphql_context.db,
43
+ branch=graphql_context.branch,
44
+ raise_on_error=True,
45
+ )
46
+ proposed_change_author = await proposed_change.created_by.get_peer(
47
+ db=graphql_context.db, peer_type=CoreGenericAccount, raise_on_error=True
48
+ )
49
+ actions = await ActionRulesEvaluator(rules=ACTION_RULES).evaluate(
50
+ proposed_change=proposed_change,
51
+ graphql_context=graphql_context,
52
+ proposed_change_author=proposed_change_author,
53
+ )
54
+
55
+ fields = await extract_fields_first_node(info=info)
56
+ response: dict[str, Any] = {}
57
+
58
+ if "count" in fields:
59
+ response["count"] = len(actions)
60
+
61
+ if edges := fields.get("edges"):
62
+ node_fields = edges.get("node", {})
63
+
64
+ nodes = []
65
+ for action in actions:
66
+ node = {}
67
+
68
+ if "action" in node_fields:
69
+ node["action"] = action["action"]
70
+ if "available" in node_fields:
71
+ node["available"] = action["available"]
72
+ if "unavailability_reason" in node_fields:
73
+ node["unavailability_reason"] = action["unavailability_reason"]
74
+
75
+ nodes.append({"node": node})
76
+
77
+ response["edges"] = nodes
78
+
79
+ return response
80
+
81
+
82
+ ProposedChangeAvailableActions = Field(
83
+ AvailableActions, proposed_change_id=String(required=True), resolver=AvailableActions.resolve, required=True
84
+ )
@@ -3,9 +3,9 @@ from __future__ import annotations
3
3
  from typing import TYPE_CHECKING, Any
4
4
 
5
5
  from graphene import Field, Int, List, NonNull, ObjectType, String
6
- from infrahub_sdk.utils import extract_fields_first_node
7
6
 
8
7
  from infrahub.core.query.relationship import RelationshipGetByIdentifierQuery
8
+ from infrahub.graphql.field_extractor import extract_graphql_fields
9
9
  from infrahub.graphql.types import RelationshipNode
10
10
 
11
11
  if TYPE_CHECKING:
@@ -29,7 +29,7 @@ class Relationships(ObjectType):
29
29
  ) -> dict[str, Any]:
30
30
  graphql_context: GraphqlContext = info.context
31
31
 
32
- fields = await extract_fields_first_node(info)
32
+ fields = extract_graphql_fields(info)
33
33
  excluded_namespaces = excluded_namespaces or []
34
34
 
35
35
  response: dict[str, Any] = {"edges": [], "count": None}
@@ -3,7 +3,6 @@ from __future__ import annotations
3
3
  from typing import TYPE_CHECKING, Any
4
4
 
5
5
  from graphene import BigInt, Field, Float, Int, List, NonNull, ObjectType, String
6
- from infrahub_sdk.utils import extract_fields_first_node
7
6
 
8
7
  from infrahub.core import registry
9
8
  from infrahub.core.constants import InfrahubKind
@@ -16,6 +15,7 @@ from infrahub.core.query.resource_manager import (
16
15
  PrefixPoolGetIdentifiers,
17
16
  )
18
17
  from infrahub.exceptions import NodeNotFoundError, SchemaNotFoundError, ValidationError
18
+ from infrahub.graphql.field_extractor import extract_graphql_fields
19
19
  from infrahub.pools.number import NumberUtilizationGetter
20
20
 
21
21
  if TYPE_CHECKING:
@@ -87,7 +87,7 @@ class PoolAllocated(ObjectType):
87
87
  id=pool_id, db=graphql_context.db, branch=graphql_context.branch
88
88
  )
89
89
 
90
- fields = await extract_fields_first_node(info=info)
90
+ fields = extract_graphql_fields(info=info)
91
91
 
92
92
  allocated_kinds: list[str] = []
93
93
  pool = _validate_pool_type(pool_id=pool_id, pool=pool)
@@ -207,7 +207,7 @@ class PoolUtilization(ObjectType):
207
207
  utilization_getter = PrefixUtilizationGetter(
208
208
  db=db, ip_prefixes=list(resources_map.values()), at=graphql_context.at
209
209
  )
210
- fields = await extract_fields_first_node(info=info)
210
+ fields = extract_graphql_fields(info=info)
211
211
  response: dict[str, Any] = {}
212
212
  total_utilization = None
213
213
  default_branch_utilization = None
@@ -4,10 +4,11 @@ import ipaddress
4
4
  from typing import TYPE_CHECKING, Any
5
5
 
6
6
  from graphene import Boolean, Field, Int, List, NonNull, ObjectType, String
7
- from infrahub_sdk.utils import extract_fields_first_node, is_valid_uuid
7
+ from infrahub_sdk.utils import is_valid_uuid
8
8
 
9
9
  from infrahub.core.constants import InfrahubKind
10
10
  from infrahub.core.manager import NodeManager
11
+ from infrahub.graphql.field_extractor import extract_graphql_fields
11
12
 
12
13
  if TYPE_CHECKING:
13
14
  from graphql import GraphQLResolveInfo
@@ -107,7 +108,7 @@ async def search_resolver(
107
108
  response: dict[str, Any] = {}
108
109
  results: list[CoreNode] = []
109
110
 
110
- fields = await extract_fields_first_node(info)
111
+ fields = extract_graphql_fields(info=info)
111
112
 
112
113
  if is_valid_uuid(q):
113
114
  matching: CoreNode | None = await NodeManager.get_one(
@@ -3,7 +3,8 @@ from __future__ import annotations
3
3
  from typing import TYPE_CHECKING, Any
4
4
 
5
5
  from graphene import Boolean, Field, List, NonNull, ObjectType, String
6
- from infrahub_sdk.utils import extract_fields_first_node
6
+
7
+ from infrahub.graphql.field_extractor import extract_graphql_fields
7
8
 
8
9
  if TYPE_CHECKING:
9
10
  from graphql import GraphQLResolveInfo
@@ -45,7 +46,7 @@ async def resolve_status(
45
46
  if service is None:
46
47
  raise ValueError("GraphqlContext.service is None")
47
48
 
48
- fields = await extract_fields_first_node(info)
49
+ fields = extract_graphql_fields(info=info)
49
50
  response: dict[str, Any] = {}
50
51
  workers = await service.component.list_workers(
51
52
  branch=str(graphql_context.branch.uuid) or graphql_context.branch.name, schema_hash=True
@@ -3,9 +3,9 @@ from __future__ import annotations
3
3
  from typing import TYPE_CHECKING, Any
4
4
 
5
5
  from graphene import Field, Int, List, NonNull, ObjectType, String
6
- from infrahub_sdk.utils import extract_fields_first_node
7
6
  from prefect.client.schemas.objects import StateType
8
7
 
8
+ from infrahub.graphql.field_extractor import extract_graphql_fields
9
9
  from infrahub.graphql.types.task import TaskNodes, TaskState
10
10
  from infrahub.task_manager.task import PrefectTask
11
11
  from infrahub.workflows.constants import WorkflowTag
@@ -79,7 +79,7 @@ class Tasks(ObjectType):
79
79
  log_offset: int | None = None,
80
80
  ) -> dict[str, Any]:
81
81
  graphql_context: GraphqlContext = info.context
82
- fields = await extract_fields_first_node(info)
82
+ fields = extract_graphql_fields(info=info)
83
83
 
84
84
  prefect_tasks = await PrefectTask.query(
85
85
  db=graphql_context.db,