infrahub-server 1.3.0a0__py3-none-any.whl → 1.3.0b2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. infrahub/actions/tasks.py +4 -11
  2. infrahub/branch/__init__.py +0 -0
  3. infrahub/branch/tasks.py +29 -0
  4. infrahub/branch/triggers.py +22 -0
  5. infrahub/cli/db.py +2 -2
  6. infrahub/computed_attribute/gather.py +3 -1
  7. infrahub/computed_attribute/tasks.py +23 -29
  8. infrahub/core/attribute.py +3 -3
  9. infrahub/core/constants/__init__.py +10 -0
  10. infrahub/core/constants/database.py +1 -0
  11. infrahub/core/constants/infrahubkind.py +2 -0
  12. infrahub/core/convert_object_type/conversion.py +1 -1
  13. infrahub/core/diff/query/save.py +67 -40
  14. infrahub/core/diff/query/time_range_query.py +0 -1
  15. infrahub/core/graph/__init__.py +1 -1
  16. infrahub/core/migrations/graph/__init__.py +6 -0
  17. infrahub/core/migrations/graph/m013_convert_git_password_credential.py +0 -2
  18. infrahub/core/migrations/graph/m029_duplicates_cleanup.py +662 -0
  19. infrahub/core/migrations/graph/m030_illegal_edges.py +82 -0
  20. infrahub/core/migrations/query/attribute_add.py +13 -9
  21. infrahub/core/migrations/query/attribute_rename.py +2 -4
  22. infrahub/core/migrations/query/delete_element_in_schema.py +16 -11
  23. infrahub/core/migrations/query/node_duplicate.py +16 -15
  24. infrahub/core/migrations/query/relationship_duplicate.py +16 -12
  25. infrahub/core/migrations/schema/node_attribute_remove.py +1 -2
  26. infrahub/core/migrations/schema/node_remove.py +16 -14
  27. infrahub/core/node/__init__.py +74 -14
  28. infrahub/core/node/base.py +1 -1
  29. infrahub/core/node/resource_manager/ip_address_pool.py +6 -2
  30. infrahub/core/node/resource_manager/ip_prefix_pool.py +6 -2
  31. infrahub/core/node/resource_manager/number_pool.py +31 -5
  32. infrahub/core/node/standard.py +6 -1
  33. infrahub/core/path.py +1 -1
  34. infrahub/core/protocols.py +10 -0
  35. infrahub/core/query/node.py +1 -1
  36. infrahub/core/query/relationship.py +4 -6
  37. infrahub/core/query/standard_node.py +19 -5
  38. infrahub/core/relationship/constraints/peer_relatives.py +72 -0
  39. infrahub/core/relationship/model.py +1 -1
  40. infrahub/core/schema/attribute_parameters.py +129 -5
  41. infrahub/core/schema/attribute_schema.py +62 -14
  42. infrahub/core/schema/basenode_schema.py +2 -2
  43. infrahub/core/schema/definitions/core/__init__.py +16 -2
  44. infrahub/core/schema/definitions/core/group.py +45 -0
  45. infrahub/core/schema/definitions/core/resource_pool.py +29 -0
  46. infrahub/core/schema/definitions/internal.py +25 -4
  47. infrahub/core/schema/generated/attribute_schema.py +12 -5
  48. infrahub/core/schema/generated/relationship_schema.py +6 -1
  49. infrahub/core/schema/manager.py +7 -2
  50. infrahub/core/schema/schema_branch.py +69 -5
  51. infrahub/core/validators/__init__.py +8 -0
  52. infrahub/core/validators/attribute/choices.py +0 -1
  53. infrahub/core/validators/attribute/enum.py +0 -1
  54. infrahub/core/validators/attribute/kind.py +0 -1
  55. infrahub/core/validators/attribute/length.py +0 -1
  56. infrahub/core/validators/attribute/min_max.py +118 -0
  57. infrahub/core/validators/attribute/number_pool.py +106 -0
  58. infrahub/core/validators/attribute/optional.py +0 -2
  59. infrahub/core/validators/attribute/regex.py +0 -1
  60. infrahub/core/validators/enum.py +5 -0
  61. infrahub/core/validators/tasks.py +1 -1
  62. infrahub/database/__init__.py +16 -4
  63. infrahub/database/validation.py +100 -0
  64. infrahub/dependencies/builder/constraint/grouped/node_runner.py +2 -0
  65. infrahub/dependencies/builder/constraint/relationship_manager/peer_relatives.py +8 -0
  66. infrahub/dependencies/builder/diff/deserializer.py +1 -1
  67. infrahub/dependencies/registry.py +2 -0
  68. infrahub/events/models.py +1 -1
  69. infrahub/git/base.py +5 -3
  70. infrahub/git/integrator.py +102 -3
  71. infrahub/graphql/mutations/main.py +1 -1
  72. infrahub/graphql/mutations/resource_manager.py +54 -6
  73. infrahub/graphql/queries/resource_manager.py +7 -1
  74. infrahub/graphql/queries/task.py +10 -0
  75. infrahub/graphql/resolvers/many_relationship.py +1 -1
  76. infrahub/graphql/resolvers/resolver.py +2 -2
  77. infrahub/graphql/resolvers/single_relationship.py +1 -1
  78. infrahub/graphql/types/task_log.py +3 -2
  79. infrahub/menu/menu.py +8 -7
  80. infrahub/message_bus/operations/refresh/registry.py +3 -3
  81. infrahub/patch/queries/delete_duplicated_edges.py +40 -29
  82. infrahub/pools/number.py +5 -3
  83. infrahub/pools/registration.py +22 -0
  84. infrahub/pools/tasks.py +56 -0
  85. infrahub/schema/__init__.py +0 -0
  86. infrahub/schema/tasks.py +27 -0
  87. infrahub/schema/triggers.py +23 -0
  88. infrahub/task_manager/task.py +44 -4
  89. infrahub/trigger/catalogue.py +4 -0
  90. infrahub/trigger/models.py +5 -4
  91. infrahub/trigger/setup.py +26 -2
  92. infrahub/trigger/tasks.py +1 -1
  93. infrahub/types.py +6 -0
  94. infrahub/webhook/tasks.py +6 -9
  95. infrahub/workflows/catalogue.py +27 -1
  96. infrahub_sdk/client.py +43 -10
  97. infrahub_sdk/node/__init__.py +39 -0
  98. infrahub_sdk/node/attribute.py +122 -0
  99. infrahub_sdk/node/constants.py +21 -0
  100. infrahub_sdk/{node.py → node/node.py} +50 -749
  101. infrahub_sdk/node/parsers.py +15 -0
  102. infrahub_sdk/node/property.py +24 -0
  103. infrahub_sdk/node/related_node.py +266 -0
  104. infrahub_sdk/node/relationship.py +302 -0
  105. infrahub_sdk/protocols.py +112 -0
  106. infrahub_sdk/protocols_base.py +34 -2
  107. infrahub_sdk/query_groups.py +13 -2
  108. infrahub_sdk/schema/main.py +1 -0
  109. infrahub_sdk/schema/repository.py +16 -0
  110. infrahub_sdk/spec/object.py +1 -1
  111. infrahub_sdk/store.py +1 -1
  112. infrahub_sdk/testing/schemas/car_person.py +1 -0
  113. {infrahub_server-1.3.0a0.dist-info → infrahub_server-1.3.0b2.dist-info}/METADATA +3 -3
  114. {infrahub_server-1.3.0a0.dist-info → infrahub_server-1.3.0b2.dist-info}/RECORD +122 -100
  115. {infrahub_server-1.3.0a0.dist-info → infrahub_server-1.3.0b2.dist-info}/WHEEL +1 -1
  116. infrahub_testcontainers/container.py +239 -64
  117. infrahub_testcontainers/docker-compose-cluster.test.yml +321 -0
  118. infrahub_testcontainers/docker-compose.test.yml +1 -0
  119. infrahub_testcontainers/helpers.py +15 -1
  120. infrahub_testcontainers/plugin.py +9 -0
  121. infrahub/patch/queries/consolidate_duplicated_nodes.py +0 -106
  122. {infrahub_server-1.3.0a0.dist-info → infrahub_server-1.3.0b2.dist-info}/LICENSE.txt +0 -0
  123. {infrahub_server-1.3.0a0.dist-info → infrahub_server-1.3.0b2.dist-info}/entry_points.txt +0 -0
@@ -1,21 +1,27 @@
1
1
  from __future__ import annotations
2
2
 
3
- from pydantic import Field
3
+ import sys
4
+ from typing import Self
4
5
 
6
+ from pydantic import ConfigDict, Field, model_validator
7
+
8
+ from infrahub import config
5
9
  from infrahub.core.constants.schema import UpdateSupport
6
10
  from infrahub.core.models import HashableModel
7
11
 
8
12
 
9
13
  def get_attribute_parameters_class_for_kind(kind: str) -> type[AttributeParameters]:
10
- return {
14
+ param_classes: dict[str, type[AttributeParameters]] = {
15
+ "NumberPool": NumberPoolParameters,
11
16
  "Text": TextAttributeParameters,
12
17
  "TextArea": TextAttributeParameters,
13
- }.get(kind, AttributeParameters)
18
+ "Number": NumberAttributeParameters,
19
+ }
20
+ return param_classes.get(kind, AttributeParameters)
14
21
 
15
22
 
16
23
  class AttributeParameters(HashableModel):
17
- class Config:
18
- extra = "forbid"
24
+ model_config = ConfigDict(extra="forbid")
19
25
 
20
26
 
21
27
  class TextAttributeParameters(AttributeParameters):
@@ -34,3 +40,121 @@ class TextAttributeParameters(AttributeParameters):
34
40
  description="Set a maximum number of characters allowed.",
35
41
  json_schema_extra={"update": UpdateSupport.VALIDATE_CONSTRAINT.value},
36
42
  )
43
+
44
+ @model_validator(mode="after")
45
+ def validate_min_max(self) -> Self:
46
+ if (
47
+ config.SETTINGS.initialized
48
+ and config.SETTINGS.main.schema_strict_mode
49
+ and self.min_length is not None
50
+ and self.max_length is not None
51
+ ):
52
+ if self.min_length > self.max_length:
53
+ raise ValueError(
54
+ "`max_length` can't be less than `min_length` when the schema is configured with strict mode"
55
+ )
56
+
57
+ return self
58
+
59
+
60
+ class NumberAttributeParameters(AttributeParameters):
61
+ min_value: int | None = Field(
62
+ default=None,
63
+ description="Set a minimum value allowed.",
64
+ json_schema_extra={"update": UpdateSupport.VALIDATE_CONSTRAINT.value},
65
+ )
66
+ max_value: int | None = Field(
67
+ default=None,
68
+ description="Set a maximum value allowed.",
69
+ json_schema_extra={"update": UpdateSupport.VALIDATE_CONSTRAINT.value},
70
+ )
71
+ excluded_values: str | None = Field(
72
+ default=None,
73
+ description="List of values or range of values not allowed for the attribute, format is: '100,150-200,280,300-400'",
74
+ pattern=r"^(\d+(?:-\d+)?)(?:,\d+(?:-\d+)?)*$",
75
+ json_schema_extra={"update": UpdateSupport.VALIDATE_CONSTRAINT.value},
76
+ )
77
+
78
+ @model_validator(mode="after")
79
+ def validate_ranges(self) -> Self:
80
+ ranges = self.get_excluded_ranges()
81
+ for i, (start_range_1, end_range_1) in enumerate(ranges):
82
+ if start_range_1 > end_range_1:
83
+ raise ValueError("`start_range` can't be less than `end_range`")
84
+
85
+ # Check for overlapping ranges
86
+ for start_range_2, end_range_2 in ranges[i + 1 :]:
87
+ if not (end_range_1 < start_range_2 or start_range_1 > end_range_2):
88
+ raise ValueError("Excluded ranges cannot overlap")
89
+
90
+ return self
91
+
92
+ @model_validator(mode="after")
93
+ def validate_min_max(self) -> Self:
94
+ if (
95
+ config.SETTINGS.initialized
96
+ and config.SETTINGS.main.schema_strict_mode
97
+ and self.min_value is not None
98
+ and self.max_value is not None
99
+ ):
100
+ if self.min_value > self.max_value:
101
+ raise ValueError(
102
+ "`max_value` can't be less than `min_value` when the schema is configured with strict mode"
103
+ )
104
+
105
+ return self
106
+
107
+ def get_excluded_single_values(self) -> list[int]:
108
+ if not self.excluded_values:
109
+ return []
110
+
111
+ results = [int(value) for value in self.excluded_values.split(",") if "-" not in value]
112
+ return results
113
+
114
+ def get_excluded_ranges(self) -> list[tuple[int, int]]:
115
+ if not self.excluded_values:
116
+ return []
117
+
118
+ ranges = []
119
+ for value in self.excluded_values.split(","):
120
+ if "-" in value:
121
+ start, end = map(int, value.split("-"))
122
+ ranges.append((start, end))
123
+
124
+ return ranges
125
+
126
+ def is_valid_value(self, value: int) -> bool:
127
+ if self.min_value is not None and value < self.min_value:
128
+ return False
129
+ if self.max_value is not None and value > self.max_value:
130
+ return False
131
+ if value in self.get_excluded_single_values():
132
+ return False
133
+ for start, end in self.get_excluded_ranges():
134
+ if start <= value <= end:
135
+ return False
136
+ return True
137
+
138
+
139
+ class NumberPoolParameters(AttributeParameters):
140
+ end_range: int = Field(
141
+ default=sys.maxsize,
142
+ description="End range for numbers for the associated NumberPool",
143
+ json_schema_extra={"update": UpdateSupport.VALIDATE_CONSTRAINT.value},
144
+ )
145
+ start_range: int = Field(
146
+ default=1,
147
+ description="Start range for numbers for the associated NumberPool",
148
+ json_schema_extra={"update": UpdateSupport.VALIDATE_CONSTRAINT.value},
149
+ )
150
+ number_pool_id: str | None = Field(
151
+ default=None,
152
+ description="The ID of the numberpool associated with this attribute",
153
+ json_schema_extra={"update": UpdateSupport.NOT_SUPPORTED.value},
154
+ )
155
+
156
+ @model_validator(mode="after")
157
+ def validate_ranges(self) -> Self:
158
+ if self.start_range > self.end_range:
159
+ raise ValueError("`start_range` can't be less than `end_range`")
160
+ return self
@@ -12,7 +12,13 @@ from infrahub.core.enums import generate_python_enum
12
12
  from infrahub.core.query.attribute import default_attribute_query_filter
13
13
  from infrahub.types import ATTRIBUTE_KIND_LABELS, ATTRIBUTE_TYPES
14
14
 
15
- from .attribute_parameters import AttributeParameters, TextAttributeParameters, get_attribute_parameters_class_for_kind
15
+ from .attribute_parameters import (
16
+ AttributeParameters,
17
+ NumberAttributeParameters,
18
+ NumberPoolParameters,
19
+ TextAttributeParameters,
20
+ get_attribute_parameters_class_for_kind,
21
+ )
16
22
  from .generated.attribute_schema import GeneratedAttributeSchema
17
23
 
18
24
  if TYPE_CHECKING:
@@ -24,10 +30,6 @@ if TYPE_CHECKING:
24
30
 
25
31
 
26
32
  def get_attribute_schema_class_for_kind(kind: str) -> type[AttributeSchema]:
27
- attribute_schema_class_by_kind: dict[str, type[AttributeSchema]] = {
28
- "Text": TextAttributeSchema,
29
- "TextArea": TextAttributeSchema,
30
- }
31
33
  return attribute_schema_class_by_kind.get(kind, AttributeSchema)
32
34
 
33
35
 
@@ -35,6 +37,24 @@ class AttributeSchema(GeneratedAttributeSchema):
35
37
  _sort_by: list[str] = ["name"]
36
38
  _enum_class: type[enum.Enum] | None = None
37
39
 
40
+ @classmethod
41
+ def model_json_schema(cls, *args: Any, **kwargs: Any) -> dict[str, Any]:
42
+ schema = super().model_json_schema(*args, **kwargs)
43
+
44
+ # Build conditional schema based on attribute_schema_class_by_kind mapping
45
+ # This override allows people using the Yaml language server to get the correct mappings
46
+ # for the parameters when selecting the appropriate kind
47
+ schema["allOf"] = []
48
+ for kind, schema_class in attribute_schema_class_by_kind.items():
49
+ schema["allOf"].append(
50
+ {
51
+ "if": {"properties": {"kind": {"const": kind}}},
52
+ "then": {"properties": {"parameters": {"$ref": f"#/definitions/{schema_class.__name__}"}}},
53
+ }
54
+ )
55
+
56
+ return schema
57
+
38
58
  @property
39
59
  def is_attribute(self) -> bool:
40
60
  return True
@@ -93,6 +113,16 @@ class AttributeSchema(GeneratedAttributeSchema):
93
113
  return expected_parameters_class(**value.model_dump())
94
114
  return value
95
115
 
116
+ @model_validator(mode="after")
117
+ def validate_parameters(self) -> Self:
118
+ if isinstance(self.parameters, NumberPoolParameters) and not self.kind == "NumberPool":
119
+ raise ValueError(f"NumberPoolParameters can't be used as parameters for {self.kind}")
120
+
121
+ if isinstance(self.parameters, TextAttributeParameters) and self.kind not in ["Text", "TextArea"]:
122
+ raise ValueError(f"TextAttributeParameters can't be used as parameters for {self.kind}")
123
+
124
+ return self
125
+
96
126
  def get_class(self) -> type[BaseAttribute]:
97
127
  return ATTRIBUTE_TYPES[self.kind].get_infrahub_class()
98
128
 
@@ -185,6 +215,14 @@ class AttributeSchema(GeneratedAttributeSchema):
185
215
  )
186
216
 
187
217
 
218
+ class NumberPoolSchema(AttributeSchema):
219
+ parameters: NumberPoolParameters = Field(
220
+ default_factory=NumberPoolParameters,
221
+ description="Extra parameters specific to NumberPool attributes",
222
+ json_schema_extra={"update": UpdateSupport.VALIDATE_CONSTRAINT.value},
223
+ )
224
+
225
+
188
226
  class TextAttributeSchema(AttributeSchema):
189
227
  parameters: TextAttributeParameters = Field(
190
228
  default_factory=TextAttributeParameters,
@@ -195,19 +233,13 @@ class TextAttributeSchema(AttributeSchema):
195
233
  @model_validator(mode="after")
196
234
  def reconcile_parameters(self) -> Self:
197
235
  if self.regex != self.parameters.regex:
198
- final_regex = self.parameters.regex or self.regex
199
- if not final_regex: # falsy parameters.regex override falsy regex
200
- final_regex = self.parameters.regex
236
+ final_regex = self.parameters.regex if self.parameters.regex is not None else self.regex
201
237
  self.regex = self.parameters.regex = final_regex
202
238
  if self.min_length != self.parameters.min_length:
203
- final_min_length = self.parameters.min_length or self.min_length
204
- if not final_min_length: # falsy parameters.min_length override falsy min_length
205
- final_min_length = self.parameters.min_length
239
+ final_min_length = self.parameters.min_length if self.parameters.min_length is not None else self.min_length
206
240
  self.min_length = self.parameters.min_length = final_min_length
207
241
  if self.max_length != self.parameters.max_length:
208
- final_max_length = self.parameters.max_length or self.max_length
209
- if not final_max_length: # falsy parameters.max_length override falsy max_length
210
- final_max_length = self.parameters.max_length
242
+ final_max_length = self.parameters.max_length if self.parameters.max_length is not None else self.max_length
211
243
  self.max_length = self.parameters.max_length = final_max_length
212
244
  return self
213
245
 
@@ -219,3 +251,19 @@ class TextAttributeSchema(AttributeSchema):
219
251
 
220
252
  def get_max_length(self) -> int | None:
221
253
  return self.parameters.max_length
254
+
255
+
256
+ class NumberAttributeSchema(AttributeSchema):
257
+ parameters: NumberAttributeParameters = Field(
258
+ default_factory=NumberAttributeParameters,
259
+ description="Extra parameters specific to number attributes",
260
+ json_schema_extra={"update": UpdateSupport.VALIDATE_CONSTRAINT.value},
261
+ )
262
+
263
+
264
+ attribute_schema_class_by_kind: dict[str, type[AttributeSchema]] = {
265
+ "NumberPool": NumberPoolSchema,
266
+ "Text": TextAttributeSchema,
267
+ "TextArea": TextAttributeSchema,
268
+ "Number": NumberAttributeSchema,
269
+ }
@@ -386,7 +386,7 @@ class BaseNodeSchema(GeneratedBaseNodeSchema):
386
386
  if not self.display_labels:
387
387
  return None
388
388
 
389
- fields: dict[str, str | None | dict[str, None]] = {}
389
+ fields: dict[str, str | dict[str, None] | None] = {}
390
390
  for item in self.display_labels:
391
391
  fields.update(self.convert_path_to_graphql_fields(path=item))
392
392
  return fields
@@ -401,7 +401,7 @@ class BaseNodeSchema(GeneratedBaseNodeSchema):
401
401
  if not self.human_friendly_id:
402
402
  return None
403
403
 
404
- fields: dict[str, str | None | dict[str, None]] = {}
404
+ fields: dict[str, str | dict[str, None] | None] = {}
405
405
  for item in self.human_friendly_id:
406
406
  fields.update(self.convert_path_to_graphql_fields(path=item))
407
407
  return fields
@@ -28,7 +28,13 @@ from .check import core_check_definition
28
28
  from .core import core_node, core_task_target
29
29
  from .generator import core_generator_definition, core_generator_instance
30
30
  from .graphql_query import core_graphql_query
31
- from .group import core_generator_group, core_graphql_query_group, core_group, core_standard_group
31
+ from .group import (
32
+ core_generator_group,
33
+ core_graphql_query_group,
34
+ core_group,
35
+ core_repository_group,
36
+ core_standard_group,
37
+ )
32
38
  from .ipam import builtin_ip_address, builtin_ip_prefix, builtin_ipam, core_ipam_namespace
33
39
  from .lineage import lineage_owner, lineage_source
34
40
  from .menu import generic_menu_item, menu_item
@@ -68,7 +74,13 @@ from .propose_change_validator import (
68
74
  core_user_validator,
69
75
  )
70
76
  from .repository import core_generic_repository, core_read_only_repository, core_repository
71
- from .resource_pool import core_ip_address_pool, core_ip_prefix_pool, core_number_pool, core_resource_pool
77
+ from .resource_pool import (
78
+ core_ip_address_pool,
79
+ core_ip_prefix_pool,
80
+ core_number_pool,
81
+ core_resource_pool,
82
+ core_weighted_pool_resource,
83
+ )
72
84
  from .template import core_object_component_template, core_object_template
73
85
  from .transform import core_transform, core_transform_jinja2, core_transform_python
74
86
  from .webhook import core_custom_webhook, core_standard_webhook, core_webhook
@@ -96,6 +108,7 @@ core_models_mixed: dict[str, list] = {
96
108
  builtin_ip_prefix,
97
109
  builtin_ip_address,
98
110
  core_resource_pool,
111
+ core_weighted_pool_resource,
99
112
  core_generic_account,
100
113
  core_base_permission,
101
114
  core_credential,
@@ -109,6 +122,7 @@ core_models_mixed: dict[str, list] = {
109
122
  core_standard_group,
110
123
  core_generator_group,
111
124
  core_graphql_query_group,
125
+ core_repository_group,
112
126
  builtin_tag,
113
127
  core_account,
114
128
  core_account_token,
@@ -1,9 +1,11 @@
1
1
  from infrahub.core.constants import (
2
2
  BranchSupportType,
3
3
  InfrahubKind,
4
+ RepositoryObjects,
4
5
  )
5
6
  from infrahub.core.constants import RelationshipCardinality as Cardinality
6
7
  from infrahub.core.constants import RelationshipKind as RelKind
8
+ from infrahub.core.schema.dropdown import DropdownChoice
7
9
 
8
10
  from ...attribute_schema import AttributeSchema as Attr
9
11
  from ...generic_schema import GenericSchema
@@ -80,6 +82,7 @@ core_generator_group = NodeSchema(
80
82
  generate_profile=False,
81
83
  )
82
84
 
85
+
83
86
  core_graphql_query_group = NodeSchema(
84
87
  name="GraphQLQueryGroup",
85
88
  namespace="Core",
@@ -106,3 +109,45 @@ core_graphql_query_group = NodeSchema(
106
109
  ),
107
110
  ],
108
111
  )
112
+
113
+
114
+ core_repository_group = NodeSchema(
115
+ name="RepositoryGroup",
116
+ namespace="Core",
117
+ description="Group of nodes associated with a given repository.",
118
+ include_in_menu=False,
119
+ icon="mdi:account-group",
120
+ label="Repository Group",
121
+ default_filter="name__value",
122
+ order_by=["name__value"],
123
+ display_labels=["name__value"],
124
+ branch=BranchSupportType.LOCAL,
125
+ inherit_from=[InfrahubKind.GENERICGROUP],
126
+ generate_profile=False,
127
+ attributes=[
128
+ Attr(
129
+ name="content",
130
+ kind="Dropdown",
131
+ description="Type of data to load, can be either `object` or `menu`",
132
+ choices=[
133
+ DropdownChoice(
134
+ name=RepositoryObjects.OBJECT.value,
135
+ label="Objects",
136
+ ),
137
+ DropdownChoice(
138
+ name=RepositoryObjects.MENU.value,
139
+ label="Menus",
140
+ ),
141
+ ],
142
+ optional=False,
143
+ ),
144
+ ],
145
+ relationships=[
146
+ Rel(
147
+ name="repository",
148
+ peer=InfrahubKind.GENERICREPOSITORY,
149
+ optional=False,
150
+ cardinality=Cardinality.ONE,
151
+ ),
152
+ ],
153
+ )
@@ -1,6 +1,7 @@
1
1
  from infrahub.core.constants import (
2
2
  BranchSupportType,
3
3
  InfrahubKind,
4
+ NumberPoolType,
4
5
  )
5
6
  from infrahub.core.constants import RelationshipCardinality as Cardinality
6
7
  from infrahub.core.constants import RelationshipKind as RelKind
@@ -32,6 +33,26 @@ core_resource_pool = GenericSchema(
32
33
  ],
33
34
  )
34
35
 
36
+ core_weighted_pool_resource = GenericSchema(
37
+ name="WeightedPoolResource",
38
+ namespace="Core",
39
+ label="Weighted Pool Resource",
40
+ description="Resource to be used in a pool, its weight is used to determine its priority on allocation.",
41
+ include_in_menu=False,
42
+ branch=BranchSupportType.AWARE,
43
+ generate_profile=False,
44
+ attributes=[
45
+ Attr(
46
+ name="allocation_weight",
47
+ label="Weight",
48
+ description="Weight determines allocation priority, resources with higher values are selected first.",
49
+ kind="Number",
50
+ optional=True,
51
+ order_weight=10000,
52
+ )
53
+ ],
54
+ )
55
+
35
56
  core_ip_prefix_pool = NodeSchema(
36
57
  name="IPPrefixPool",
37
58
  namespace="Core",
@@ -166,5 +187,13 @@ core_number_pool = NodeSchema(
166
187
  Attr(
167
188
  name="end_range", kind="Number", optional=False, description="The end range for the pool", order_weight=6000
168
189
  ),
190
+ Attr(
191
+ name="pool_type",
192
+ kind="Text",
193
+ description="Defines how this number pool was created",
194
+ default_value=NumberPoolType.USER.value,
195
+ enum=NumberPoolType.available_types(),
196
+ read_only=True,
197
+ ),
169
198
  ],
170
199
  )
@@ -31,7 +31,12 @@ from infrahub.core.constants import (
31
31
  RelationshipKind,
32
32
  UpdateSupport,
33
33
  )
34
- from infrahub.core.schema.attribute_parameters import AttributeParameters
34
+ from infrahub.core.schema.attribute_parameters import (
35
+ AttributeParameters,
36
+ NumberAttributeParameters,
37
+ NumberPoolParameters,
38
+ TextAttributeParameters,
39
+ )
35
40
  from infrahub.core.schema.attribute_schema import AttributeSchema
36
41
  from infrahub.core.schema.computed_attribute import ComputedAttribute
37
42
  from infrahub.core.schema.dropdown import DropdownChoice
@@ -48,7 +53,7 @@ class SchemaAttribute(BaseModel):
48
53
  kind: str
49
54
  description: str
50
55
  extra: ExtraField
51
- internal_kind: type[Any] | GenericAlias | None = None
56
+ internal_kind: type[Any] | GenericAlias | list[type[Any]] | None = None
52
57
  regex: str | None = None
53
58
  unique: bool | None = None
54
59
  optional: bool | None = None
@@ -77,7 +82,7 @@ class SchemaAttribute(BaseModel):
77
82
 
78
83
  @property
79
84
  def optional_in_model(self) -> bool:
80
- if self.optional and self.default_value is None and self.default_factory is None or self.default_to_none:
85
+ if (self.optional and self.default_value is None and self.default_factory is None) or self.default_to_none:
81
86
  return True
82
87
 
83
88
  return False
@@ -94,6 +99,9 @@ class SchemaAttribute(BaseModel):
94
99
  if isinstance(self.internal_kind, GenericAlias):
95
100
  return str(self.internal_kind)
96
101
 
102
+ if isinstance(self.internal_kind, list):
103
+ return " | ".join([internal_kind.__name__ for internal_kind in self.internal_kind])
104
+
97
105
  if self.internal_kind and self.kind == "List":
98
106
  return f"list[{self.internal_kind.__name__}]"
99
107
 
@@ -621,7 +629,12 @@ attribute_schema = SchemaNode(
621
629
  SchemaAttribute(
622
630
  name="parameters",
623
631
  kind="JSON",
624
- internal_kind=AttributeParameters,
632
+ internal_kind=[
633
+ AttributeParameters,
634
+ TextAttributeParameters,
635
+ NumberAttributeParameters,
636
+ NumberPoolParameters,
637
+ ],
625
638
  optional=True,
626
639
  description="Extra parameters specific to this kind of attribute",
627
640
  extra={"update": UpdateSupport.VALIDATE_CONSTRAINT},
@@ -741,6 +754,14 @@ relationship_schema = SchemaNode(
741
754
  optional=True,
742
755
  extra={"update": UpdateSupport.VALIDATE_CONSTRAINT},
743
756
  ),
757
+ SchemaAttribute(
758
+ name="common_relatives",
759
+ kind="List",
760
+ internal_kind=str,
761
+ optional=True,
762
+ description="List of relationship names on the peer schema for which all objects must share the same set of peers.",
763
+ extra={"update": UpdateSupport.VALIDATE_CONSTRAINT},
764
+ ),
744
765
  SchemaAttribute(
745
766
  name="order_weight",
746
767
  kind="Number",
@@ -8,7 +8,12 @@ from pydantic import Field
8
8
 
9
9
  from infrahub.core.constants import AllowOverrideType, BranchSupportType, HashableModelState
10
10
  from infrahub.core.models import HashableModel
11
- from infrahub.core.schema.attribute_parameters import AttributeParameters # noqa: TC001
11
+ from infrahub.core.schema.attribute_parameters import (
12
+ AttributeParameters,
13
+ NumberAttributeParameters,
14
+ NumberPoolParameters,
15
+ TextAttributeParameters,
16
+ )
12
17
  from infrahub.core.schema.computed_attribute import ComputedAttribute # noqa: TC001
13
18
  from infrahub.core.schema.dropdown import DropdownChoice # noqa: TC001
14
19
 
@@ -113,10 +118,12 @@ class GeneratedAttributeSchema(HashableModel):
113
118
  description="Type of allowed override for the attribute.",
114
119
  json_schema_extra={"update": "allowed"},
115
120
  )
116
- parameters: AttributeParameters = Field(
117
- default_factory=AttributeParameters,
118
- description="Extra parameters specific to this kind of attribute",
119
- json_schema_extra={"update": "validate_constraint"},
121
+ parameters: AttributeParameters | TextAttributeParameters | NumberAttributeParameters | NumberPoolParameters = (
122
+ Field(
123
+ default_factory=AttributeParameters,
124
+ description="Extra parameters specific to this kind of attribute",
125
+ json_schema_extra={"update": "validate_constraint"},
126
+ )
120
127
  )
121
128
  deprecation: str | None = Field(
122
129
  default=None,
@@ -12,7 +12,7 @@ from infrahub.core.constants import (
12
12
  RelationshipDeleteBehavior,
13
13
  RelationshipDirection,
14
14
  RelationshipKind,
15
- ) # noqa: TC001
15
+ )
16
16
  from infrahub.core.models import HashableModel
17
17
 
18
18
 
@@ -73,6 +73,11 @@ class GeneratedRelationshipSchema(HashableModel):
73
73
  description="Defines the maximum objects allowed on the other side of the relationship.",
74
74
  json_schema_extra={"update": "validate_constraint"},
75
75
  )
76
+ common_relatives: list[str] | None = Field(
77
+ default=None,
78
+ description="List of relationship names on the peer schema for which all objects must share the same set of peers.",
79
+ json_schema_extra={"update": "validate_constraint"},
80
+ )
76
81
  order_weight: int | None = Field(
77
82
  default=None,
78
83
  description="Number used to order the relationship in the frontend (table and view). Lowest value will be ordered first.",
@@ -471,7 +471,7 @@ class SchemaManager(NodeManager):
471
471
  if diff_attributes:
472
472
  for item in node.local_attributes:
473
473
  # if item is in changed and has no ID, then it is being overridden from a generic and must be added
474
- if item.name in diff_attributes.added or item.name in diff_attributes.changed and item.id is None:
474
+ if item.name in diff_attributes.added or (item.name in diff_attributes.changed and item.id is None):
475
475
  created_item = await self.create_attribute_in_db(
476
476
  schema=attribute_schema, item=item, branch=branch, db=db, parent=obj
477
477
  )
@@ -491,7 +491,9 @@ class SchemaManager(NodeManager):
491
491
  if diff_relationships:
492
492
  for item in node.local_relationships:
493
493
  # if item is in changed and has no ID, then it is being overridden from a generic and must be added
494
- if item.name in diff_relationships.added or item.name in diff_relationships.changed and item.id is None:
494
+ if item.name in diff_relationships.added or (
495
+ item.name in diff_relationships.changed and item.id is None
496
+ ):
495
497
  created_rel = await self.create_relationship_in_db(
496
498
  schema=relationship_schema, item=item, branch=branch, db=db, parent=obj
497
499
  )
@@ -764,3 +766,6 @@ class SchemaManager(NodeManager):
764
766
  del self._cache[hash_key]
765
767
 
766
768
  return removed_branches
769
+
770
+ def get_branches(self) -> list[str]:
771
+ return list(self._branches.keys())