infrahub-server 1.5.0b1__py3-none-any.whl → 1.5.0b2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. infrahub/api/internal.py +2 -0
  2. infrahub/api/oauth2.py +13 -19
  3. infrahub/api/oidc.py +15 -21
  4. infrahub/api/schema.py +24 -3
  5. infrahub/artifacts/models.py +2 -1
  6. infrahub/auth.py +137 -3
  7. infrahub/cli/__init__.py +2 -0
  8. infrahub/cli/db.py +83 -102
  9. infrahub/cli/dev.py +118 -0
  10. infrahub/cli/tasks.py +46 -0
  11. infrahub/cli/upgrade.py +30 -3
  12. infrahub/computed_attribute/tasks.py +20 -8
  13. infrahub/core/attribute.py +10 -2
  14. infrahub/core/branch/enums.py +1 -1
  15. infrahub/core/branch/models.py +7 -3
  16. infrahub/core/branch/tasks.py +68 -7
  17. infrahub/core/constants/__init__.py +3 -0
  18. infrahub/core/diff/query/artifact.py +1 -0
  19. infrahub/core/diff/query/field_summary.py +1 -0
  20. infrahub/core/graph/__init__.py +1 -1
  21. infrahub/core/initialization.py +5 -2
  22. infrahub/core/migrations/__init__.py +3 -0
  23. infrahub/core/migrations/exceptions.py +4 -0
  24. infrahub/core/migrations/graph/__init__.py +10 -13
  25. infrahub/core/migrations/graph/load_schema_branch.py +21 -0
  26. infrahub/core/migrations/graph/m013_convert_git_password_credential.py +1 -1
  27. infrahub/core/migrations/graph/m040_duplicated_attributes.py +81 -0
  28. infrahub/core/migrations/graph/m041_profile_attrs_in_db.py +145 -0
  29. infrahub/core/migrations/graph/m042_create_hfid_display_label_in_db.py +164 -0
  30. infrahub/core/migrations/graph/m043_backfill_hfid_display_label_in_db.py +866 -0
  31. infrahub/core/migrations/query/__init__.py +7 -8
  32. infrahub/core/migrations/query/attribute_add.py +8 -6
  33. infrahub/core/migrations/query/attribute_remove.py +134 -0
  34. infrahub/core/migrations/runner.py +54 -0
  35. infrahub/core/migrations/schema/attribute_kind_update.py +9 -3
  36. infrahub/core/migrations/schema/attribute_supports_profile.py +90 -0
  37. infrahub/core/migrations/schema/node_attribute_add.py +30 -2
  38. infrahub/core/migrations/schema/node_attribute_remove.py +13 -109
  39. infrahub/core/migrations/schema/node_kind_update.py +2 -1
  40. infrahub/core/migrations/schema/node_remove.py +2 -1
  41. infrahub/core/migrations/schema/placeholder_dummy.py +3 -2
  42. infrahub/core/migrations/shared.py +48 -14
  43. infrahub/core/node/__init__.py +16 -11
  44. infrahub/core/node/create.py +46 -63
  45. infrahub/core/node/lock_utils.py +70 -44
  46. infrahub/core/node/resource_manager/ip_address_pool.py +2 -1
  47. infrahub/core/node/resource_manager/ip_prefix_pool.py +2 -1
  48. infrahub/core/node/resource_manager/number_pool.py +2 -1
  49. infrahub/core/query/attribute.py +55 -0
  50. infrahub/core/query/ipam.py +1 -0
  51. infrahub/core/query/node.py +9 -3
  52. infrahub/core/query/relationship.py +1 -0
  53. infrahub/core/schema/__init__.py +56 -0
  54. infrahub/core/schema/attribute_schema.py +4 -0
  55. infrahub/core/schema/definitions/internal.py +2 -2
  56. infrahub/core/schema/generated/attribute_schema.py +2 -2
  57. infrahub/core/schema/manager.py +22 -1
  58. infrahub/core/schema/schema_branch.py +180 -22
  59. infrahub/database/graph.py +21 -0
  60. infrahub/display_labels/tasks.py +13 -7
  61. infrahub/events/branch_action.py +27 -1
  62. infrahub/generators/tasks.py +3 -7
  63. infrahub/git/base.py +4 -1
  64. infrahub/git/integrator.py +1 -1
  65. infrahub/git/models.py +2 -1
  66. infrahub/git/repository.py +22 -5
  67. infrahub/git/tasks.py +66 -10
  68. infrahub/git/utils.py +123 -1
  69. infrahub/graphql/api/endpoints.py +14 -4
  70. infrahub/graphql/manager.py +4 -9
  71. infrahub/graphql/mutations/convert_object_type.py +11 -1
  72. infrahub/graphql/mutations/display_label.py +17 -10
  73. infrahub/graphql/mutations/hfid.py +17 -10
  74. infrahub/graphql/mutations/ipam.py +54 -35
  75. infrahub/graphql/mutations/main.py +27 -28
  76. infrahub/graphql/schema_sort.py +170 -0
  77. infrahub/graphql/types/branch.py +4 -1
  78. infrahub/graphql/types/enums.py +3 -0
  79. infrahub/hfid/tasks.py +13 -7
  80. infrahub/lock.py +52 -12
  81. infrahub/message_bus/types.py +2 -1
  82. infrahub/permissions/constants.py +2 -0
  83. infrahub/proposed_change/tasks.py +25 -16
  84. infrahub/server.py +6 -2
  85. infrahub/services/__init__.py +2 -2
  86. infrahub/services/adapters/http/__init__.py +5 -0
  87. infrahub/services/adapters/workflow/worker.py +14 -3
  88. infrahub/task_manager/event.py +5 -0
  89. infrahub/task_manager/models.py +7 -0
  90. infrahub/task_manager/task.py +73 -0
  91. infrahub/trigger/setup.py +13 -4
  92. infrahub/trigger/tasks.py +3 -0
  93. infrahub/workers/dependencies.py +10 -1
  94. infrahub/workers/infrahub_async.py +10 -2
  95. infrahub/workflows/catalogue.py +8 -0
  96. infrahub/workflows/initialization.py +5 -0
  97. infrahub/workflows/utils.py +2 -1
  98. infrahub_sdk/client.py +13 -10
  99. infrahub_sdk/config.py +29 -2
  100. infrahub_sdk/ctl/schema.py +22 -7
  101. infrahub_sdk/schema/__init__.py +32 -4
  102. infrahub_sdk/spec/models.py +7 -0
  103. infrahub_sdk/spec/object.py +37 -102
  104. infrahub_sdk/spec/processors/__init__.py +0 -0
  105. infrahub_sdk/spec/processors/data_processor.py +10 -0
  106. infrahub_sdk/spec/processors/factory.py +34 -0
  107. infrahub_sdk/spec/processors/range_expand_processor.py +56 -0
  108. {infrahub_server-1.5.0b1.dist-info → infrahub_server-1.5.0b2.dist-info}/METADATA +3 -1
  109. {infrahub_server-1.5.0b1.dist-info → infrahub_server-1.5.0b2.dist-info}/RECORD +115 -101
  110. infrahub_testcontainers/container.py +114 -2
  111. infrahub_testcontainers/docker-compose-cluster.test.yml +5 -0
  112. infrahub_testcontainers/docker-compose.test.yml +5 -0
  113. infrahub/core/migrations/graph/m040_profile_attrs_in_db.py +0 -166
  114. infrahub/core/migrations/graph/m041_create_hfid_display_label_in_db.py +0 -97
  115. infrahub/core/migrations/graph/m042_backfill_hfid_display_label_in_db.py +0 -86
  116. {infrahub_server-1.5.0b1.dist-info → infrahub_server-1.5.0b2.dist-info}/LICENSE.txt +0 -0
  117. {infrahub_server-1.5.0b1.dist-info → infrahub_server-1.5.0b2.dist-info}/WHEEL +0 -0
  118. {infrahub_server-1.5.0b1.dist-info → infrahub_server-1.5.0b2.dist-info}/entry_points.txt +0 -0
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import uuid
4
+ from enum import Enum
4
5
  from typing import Any, TypeAlias
5
6
 
6
7
  from infrahub_sdk.utils import deep_merge_dict
@@ -44,6 +45,21 @@ class SchemaExtension(HashableModel):
44
45
  nodes: list[NodeExtensionSchema] = Field(default_factory=list)
45
46
 
46
47
 
48
+ class SchemaWarningType(Enum):
49
+ DEPRECATION = "deprecation"
50
+
51
+
52
+ class SchemaWarningKind(BaseModel):
53
+ kind: str = Field(..., description="The kind impacted by the warning")
54
+ field: str | None = Field(default=None, description="The attribute or relationship impacted by the warning")
55
+
56
+
57
+ class SchemaWarning(BaseModel):
58
+ type: SchemaWarningType = Field(..., description="The type of warning")
59
+ kinds: list[SchemaWarningKind] = Field(default_factory=list, description="The kinds impacted by the warning")
60
+ message: str = Field(..., description="The message that describes the warning")
61
+
62
+
47
63
  class SchemaRoot(BaseModel):
48
64
  model_config = ConfigDict(extra="forbid")
49
65
 
@@ -80,6 +96,46 @@ class SchemaRoot(BaseModel):
80
96
 
81
97
  return errors
82
98
 
99
+ def gather_warnings(self) -> list[SchemaWarning]:
100
+ models = self.nodes + self.generics
101
+ warnings: list[SchemaWarning] = []
102
+ for model in models:
103
+ if model.display_labels is not None:
104
+ warnings.append(
105
+ SchemaWarning(
106
+ type=SchemaWarningType.DEPRECATION,
107
+ kinds=[SchemaWarningKind(kind=model.kind)],
108
+ message="display_labels are deprecated, use display_label instead",
109
+ )
110
+ )
111
+ if model.default_filter is not None:
112
+ warnings.append(
113
+ SchemaWarning(
114
+ type=SchemaWarningType.DEPRECATION,
115
+ kinds=[SchemaWarningKind(kind=model.kind)],
116
+ message="default_filter is deprecated",
117
+ )
118
+ )
119
+ for attribute in model.attributes:
120
+ if attribute.max_length is not None:
121
+ warnings.append(
122
+ SchemaWarning(
123
+ type=SchemaWarningType.DEPRECATION,
124
+ kinds=[SchemaWarningKind(kind=model.kind, field=attribute.name)],
125
+ message="Use of 'max_length' on attributes is deprecated, use parameters instead",
126
+ )
127
+ )
128
+ if attribute.min_length is not None:
129
+ warnings.append(
130
+ SchemaWarning(
131
+ type=SchemaWarningType.DEPRECATION,
132
+ kinds=[SchemaWarningKind(kind=model.kind, field=attribute.name)],
133
+ message="Use of 'min_length' on attributes is deprecated, use parameters instead",
134
+ )
135
+ )
136
+
137
+ return warnings
138
+
83
139
  def generate_uuid(self) -> None:
84
140
  """Generate UUID for all nodes, attributes & relationships
85
141
  Mainly useful during unit tests."""
@@ -68,6 +68,10 @@ class AttributeSchema(GeneratedAttributeSchema):
68
68
  def is_deprecated(self) -> bool:
69
69
  return bool(self.deprecation)
70
70
 
71
+ @property
72
+ def support_profiles(self) -> bool:
73
+ return self.read_only is False and self.optional is True
74
+
71
75
  def get_id(self) -> str:
72
76
  if self.id is None:
73
77
  raise InitializationError("The attribute schema has not been saved yet and doesn't have an id")
@@ -568,7 +568,7 @@ attribute_schema = SchemaNode(
568
568
  "Mainly relevant for internal object.",
569
569
  default_value=False,
570
570
  optional=True,
571
- extra={"update": UpdateSupport.ALLOWED},
571
+ extra={"update": UpdateSupport.MIGRATION_REQUIRED},
572
572
  ),
573
573
  SchemaAttribute(
574
574
  name="unique",
@@ -585,7 +585,7 @@ attribute_schema = SchemaNode(
585
585
  default_value=False,
586
586
  override_default_value=False,
587
587
  optional=True,
588
- extra={"update": UpdateSupport.VALIDATE_CONSTRAINT},
588
+ extra={"update": UpdateSupport.MIGRATION_REQUIRED},
589
589
  ),
590
590
  SchemaAttribute(
591
591
  name="branch",
@@ -78,7 +78,7 @@ class GeneratedAttributeSchema(HashableModel):
78
78
  read_only: bool = Field(
79
79
  default=False,
80
80
  description="Set the attribute as Read-Only, users won't be able to change its value. Mainly relevant for internal object.",
81
- json_schema_extra={"update": "allowed"},
81
+ json_schema_extra={"update": "migration_required"},
82
82
  )
83
83
  unique: bool = Field(
84
84
  default=False,
@@ -88,7 +88,7 @@ class GeneratedAttributeSchema(HashableModel):
88
88
  optional: bool = Field(
89
89
  default=False,
90
90
  description="Indicate if this attribute is mandatory or optional.",
91
- json_schema_extra={"update": "validate_constraint"},
91
+ json_schema_extra={"update": "migration_required"},
92
92
  )
93
93
  branch: BranchSupportType | None = Field(
94
94
  default=None,
@@ -2,6 +2,9 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING, Any
4
4
 
5
+ from cachetools import LRUCache
6
+ from infrahub_sdk.schema import BranchSchema as SDKBranchSchema
7
+
5
8
  from infrahub import lock
6
9
  from infrahub.core.manager import NodeManager
7
10
  from infrahub.core.models import (
@@ -40,6 +43,8 @@ class SchemaManager(NodeManager):
40
43
  def __init__(self) -> None:
41
44
  self._cache: dict[int, Any] = {}
42
45
  self._branches: dict[str, SchemaBranch] = {}
46
+ self._branch_hash_by_name: dict[str, str] = {}
47
+ self._sdk_branches: LRUCache[str, SDKBranchSchema] = LRUCache(maxsize=10)
43
48
 
44
49
  def _get_from_cache(self, key: int) -> Any:
45
50
  return self._cache[key]
@@ -140,12 +145,26 @@ class SchemaManager(NodeManager):
140
145
  if name in self._branches:
141
146
  return self._branches[name]
142
147
 
143
- self._branches[name] = SchemaBranch(cache=self._cache, name=name)
148
+ self.set_schema_branch(name, schema=SchemaBranch(cache=self._cache, name=name))
144
149
  return self._branches[name]
145
150
 
151
+ def get_sdk_schema_branch(self, name: str) -> SDKBranchSchema:
152
+ schema_hash = self._branch_hash_by_name[name]
153
+ branch_schema = self._sdk_branches.get(schema_hash)
154
+ if not branch_schema:
155
+ self._sdk_branches[schema_hash] = SDKBranchSchema.from_api_response(
156
+ data=self._branches[name].to_dict_api_schema_object()
157
+ )
158
+
159
+ return self._sdk_branches[schema_hash]
160
+
146
161
  def set_schema_branch(self, name: str, schema: SchemaBranch) -> None:
147
162
  schema.name = name
148
163
  self._branches[name] = schema
164
+ self._branch_hash_by_name[name] = schema.get_hash()
165
+
166
+ def has_schema_branch(self, name: str) -> bool:
167
+ return name in self._branches
149
168
 
150
169
  def process_schema_branch(self, name: str) -> None:
151
170
  schema_branch = self.get_schema_branch(name=name)
@@ -764,6 +783,8 @@ class SchemaManager(NodeManager):
764
783
  for branch_name in list(self._branches.keys()):
765
784
  if branch_name not in active_branches:
766
785
  del self._branches[branch_name]
786
+ if branch_name in self._branch_hash_by_name:
787
+ del self._branch_hash_by_name[branch_name]
767
788
  removed_branches.append(branch_name)
768
789
 
769
790
  for hash_key in list(self._cache.keys()):
@@ -169,6 +169,14 @@ class SchemaBranch:
169
169
  "templates": {name: self.get(name, duplicate=duplicate) for name in self.templates},
170
170
  }
171
171
 
172
+ def to_dict_api_schema_object(self) -> dict[str, list[dict]]:
173
+ return {
174
+ "nodes": [self.get(name, duplicate=False).model_dump() for name in self.nodes],
175
+ "profiles": [self.get(name, duplicate=False).model_dump() for name in self.profiles],
176
+ "generics": [self.get(name, duplicate=False).model_dump() for name in self.generics],
177
+ "templates": [self.get(name, duplicate=False).model_dump() for name in self.templates],
178
+ }
179
+
172
180
  @classmethod
173
181
  def from_dict_schema_object(cls, data: dict) -> Self:
174
182
  type_mapping = {
@@ -320,14 +328,23 @@ class SchemaBranch:
320
328
  elif name in self.templates:
321
329
  key = self.templates[name]
322
330
 
323
- if key and duplicate:
324
- return self._cache[key].duplicate()
325
- if key and not duplicate:
326
- return self._cache[key]
331
+ if not key:
332
+ raise SchemaNotFoundError(
333
+ branch_name=self.name, identifier=name, message=f"Unable to find the schema {name!r} in the registry"
334
+ )
335
+
336
+ schema: MainSchemaTypes | None = None
337
+ try:
338
+ schema = self._cache[key]
339
+ except KeyError:
340
+ pass
327
341
 
328
- raise SchemaNotFoundError(
329
- branch_name=self.name, identifier=name, message=f"Unable to find the schema {name!r} in the registry"
330
- )
342
+ if not schema:
343
+ raise ValueError(f"Schema {name!r} on branch {self.name} has incorrect hash: {key!r}")
344
+
345
+ if duplicate:
346
+ return schema.duplicate()
347
+ return schema
331
348
 
332
349
  def get_node(self, name: str, duplicate: bool = True) -> NodeSchema:
333
350
  """Access a specific NodeSchema, defined by its kind."""
@@ -540,13 +557,14 @@ class SchemaBranch:
540
557
  self.validate_identifiers()
541
558
  self.sync_uniqueness_constraints_and_unique_attributes()
542
559
  self.validate_uniqueness_constraints()
543
- self.validate_display_label()
544
560
  self.validate_display_labels()
561
+ self.validate_display_label()
545
562
  self.validate_order_by()
546
563
  self.validate_default_filters()
547
564
  self.validate_parent_component()
548
565
  self.validate_human_friendly_id()
549
566
  self.validate_required_relationships()
567
+ self.validate_inherited_relationships_fields()
550
568
 
551
569
  def process_post_validation(self) -> None:
552
570
  self.cleanup_inherited_elements()
@@ -556,6 +574,7 @@ class SchemaBranch:
556
574
  self.process_dropdowns()
557
575
  self.process_relationships()
558
576
  self.process_human_friendly_id()
577
+ self.register_human_friendly_id()
559
578
 
560
579
  def _generate_identifier_string(self, node_kind: str, peer_kind: str) -> str:
561
580
  return "__".join(sorted([node_kind, peer_kind])).lower()
@@ -774,20 +793,21 @@ class SchemaBranch:
774
793
  if len(node_schema.display_labels) == 1:
775
794
  # If the previous display_labels consist of a single attribute convert
776
795
  # it to an attribute based display label
777
- converted_display_label = node_schema.display_labels[0]
778
- if "__" not in converted_display_label:
779
- # Previously we allowed defining a raw attribute name as a component of a
780
- # display_label, if this is the case we need to append '__value'
781
- converted_display_label = f"{converted_display_label}__value"
782
- update_candidate.display_label = converted_display_label
796
+ update_candidate.display_label = _format_display_label_component(
797
+ component=node_schema.display_labels[0]
798
+ )
783
799
  else:
784
800
  # If the previous display label consists of multiple attributes
785
801
  # convert it to a Jinja2 based display label
786
802
  update_candidate.display_label = " ".join(
787
- [f"{{{{ {display_label} }}}}" for display_label in node_schema.display_labels]
803
+ [
804
+ f"{{{{ {_format_display_label_component(component=display_label)} }}}}"
805
+ for display_label in node_schema.display_labels
806
+ ]
788
807
  )
789
808
  self.set(name=name, schema=update_candidate)
790
809
 
810
+ node_schema = self.get(name=name, duplicate=False)
791
811
  if not node_schema.display_label:
792
812
  continue
793
813
 
@@ -890,7 +910,6 @@ class SchemaBranch:
890
910
  return False
891
911
 
892
912
  def validate_human_friendly_id(self) -> None:
893
- self.hfids = HFIDs()
894
913
  for name in self.generic_names_without_templates + self.node_names:
895
914
  node_schema = self.get(name=name, duplicate=False)
896
915
 
@@ -924,11 +943,6 @@ class SchemaBranch:
924
943
  rel_schemas_to_paths[rel_identifier] = (schema_path.related_schema, [])
925
944
  rel_schemas_to_paths[rel_identifier][1].append(schema_path.attribute_path_as_str)
926
945
 
927
- if node_schema.is_node_schema and node_schema.namespace not in ["Schema", "Internal"]:
928
- self.hfids.register_hfid_schema_path(
929
- kind=node_schema.kind, schema_path=schema_path, hfid=node_schema.human_friendly_id
930
- )
931
-
932
946
  if config.SETTINGS.main.schema_strict_mode:
933
947
  # For every relationship referred within hfid, check whether the combination of attributes is unique is the peer schema node
934
948
  for related_schema, attrs_paths in rel_schemas_to_paths.values():
@@ -1327,6 +1341,81 @@ class SchemaBranch:
1327
1341
  f"{node.kind}: Relationship {rel.name!r} max_count must be 0 or greater than 1 when cardinality is MANY"
1328
1342
  )
1329
1343
 
1344
+ def validate_inherited_relationships_fields(self) -> None:
1345
+ for name in self.node_names:
1346
+ node_schema = self.get(name=name, duplicate=False)
1347
+ if not node_schema.inherit_from:
1348
+ continue
1349
+
1350
+ self.validate_node_inherited_relationship_fields(node_schema)
1351
+
1352
+ def validate_node_inherited_relationship_fields(self, node_schema: NodeSchema) -> None:
1353
+ generics = [self.get(name=node_name, duplicate=False) for node_name in node_schema.inherit_from]
1354
+ relationship_names = [node.relationship_names for node in generics]
1355
+ related_relationship_names = set().union(
1356
+ *[
1357
+ set(relationship_name_a) & set(relationship_name_b)
1358
+ for index, relationship_name_a in enumerate(relationship_names)
1359
+ for relationship_name_b in relationship_names[index + 1 :]
1360
+ ]
1361
+ )
1362
+ # Check that the relationship properties match
1363
+ # for every generic node in generics list having related relationship names
1364
+ for index, generic_a in enumerate(generics):
1365
+ for generic_b in generics[index + 1 :]:
1366
+ for relationship_name in related_relationship_names:
1367
+ try:
1368
+ relationship_a = generic_a.get_relationship(name=relationship_name)
1369
+ relationship_b = generic_b.get_relationship(name=relationship_name)
1370
+ except ValueError:
1371
+ continue
1372
+
1373
+ matched, _property = self._check_relationship_properties_match(
1374
+ relationship_a=relationship_a, relationship_b=relationship_b
1375
+ )
1376
+ if not matched:
1377
+ raise ValueError(
1378
+ f"{node_schema.kind} inherits from '{generic_a.kind}' & '{generic_b.kind}'"
1379
+ f" with different '{_property}' on the '{relationship_name}' relationship"
1380
+ )
1381
+
1382
+ def _check_relationship_properties_match(
1383
+ self, relationship_a: RelationshipSchema, relationship_b: RelationshipSchema
1384
+ ) -> tuple[bool, str | None]:
1385
+ compulsorily_matching_properties = (
1386
+ "name",
1387
+ "peer",
1388
+ "kind",
1389
+ "identifier",
1390
+ "cardinality",
1391
+ "min_count",
1392
+ "max_count",
1393
+ "common_parent",
1394
+ "common_relatives",
1395
+ "optional",
1396
+ "branch",
1397
+ "direction",
1398
+ "on_delete",
1399
+ "read_only",
1400
+ "hierarchical",
1401
+ "allow_override",
1402
+ )
1403
+ for _property in compulsorily_matching_properties:
1404
+ if not hasattr(relationship_a, _property) or not hasattr(relationship_b, _property):
1405
+ continue
1406
+
1407
+ equal_delete_actions = (None, RelationshipDeleteBehavior.NO_ACTION)
1408
+ if (
1409
+ _property == "on_delete"
1410
+ and getattr(relationship_a, _property) in equal_delete_actions
1411
+ and getattr(relationship_b, _property) in equal_delete_actions
1412
+ ):
1413
+ continue
1414
+
1415
+ if getattr(relationship_a, _property) != getattr(relationship_b, _property):
1416
+ return False, _property
1417
+ return True, None
1418
+
1330
1419
  def process_dropdowns(self) -> None:
1331
1420
  for name in self.all_names:
1332
1421
  node = self.get(name=name, duplicate=False)
@@ -1455,6 +1544,34 @@ class SchemaBranch:
1455
1544
  node.uniqueness_constraints = [hfid_uniqueness_constraint]
1456
1545
  self.set(name=node.kind, schema=node)
1457
1546
 
1547
+ def register_human_friendly_id(self) -> None:
1548
+ """Register HFID automations
1549
+
1550
+ Register the HFIDs after all processing and validation has been done.
1551
+ """
1552
+
1553
+ self.hfids = HFIDs()
1554
+ for name in self.generic_names_without_templates + self.node_names:
1555
+ node_schema = self.get(name=name, duplicate=False)
1556
+
1557
+ if not node_schema.human_friendly_id:
1558
+ continue
1559
+
1560
+ allowed_types = SchemaElementPathType.ATTR_WITH_PROP | SchemaElementPathType.REL_ONE_MANDATORY_ATTR
1561
+
1562
+ for hfid_path in node_schema.human_friendly_id:
1563
+ schema_path = self.validate_schema_path(
1564
+ node_schema=node_schema,
1565
+ path=hfid_path,
1566
+ allowed_path_types=allowed_types,
1567
+ element_name="human_friendly_id",
1568
+ )
1569
+
1570
+ if node_schema.is_node_schema and node_schema.namespace not in ["Schema", "Internal"]:
1571
+ self.hfids.register_hfid_schema_path(
1572
+ kind=node_schema.kind, schema_path=schema_path, hfid=node_schema.human_friendly_id
1573
+ )
1574
+
1458
1575
  def process_hierarchy(self) -> None:
1459
1576
  for name in self.nodes.keys():
1460
1577
  node = self.get_node(name=name, duplicate=False)
@@ -1750,6 +1867,8 @@ class SchemaBranch:
1750
1867
  """Generate order_weight for all generic schemas."""
1751
1868
  for name in self.generic_names:
1752
1869
  node = self.get(name=name, duplicate=False)
1870
+ if node.namespace == "Template":
1871
+ continue
1753
1872
 
1754
1873
  items_to_update = [item for item in node.attributes + node.relationships if not item.order_weight]
1755
1874
 
@@ -1813,10 +1932,36 @@ class SchemaBranch:
1813
1932
 
1814
1933
  self.set(name=name, schema=template)
1815
1934
 
1935
+ def _generate_generics_templates_weight(self) -> None:
1936
+ """Generate order_weight for generic templates.
1937
+
1938
+ The order of the fields for the template must respect the order of the node.
1939
+ """
1940
+ for name in self.generic_names:
1941
+ generic_node = self.get(name=name, duplicate=False)
1942
+ try:
1943
+ template = self.get(name=self._get_object_template_kind(node_kind=generic_node.kind), duplicate=True)
1944
+ except SchemaNotFoundError:
1945
+ continue
1946
+
1947
+ generic_node_weights = {
1948
+ item.name: item.order_weight
1949
+ for item in generic_node.attributes + generic_node.relationships
1950
+ if item.order_weight is not None
1951
+ }
1952
+
1953
+ for item in template.attributes + template.relationships:
1954
+ if item.order_weight:
1955
+ continue
1956
+ item.order_weight = generic_node_weights[item.name] if item.name in generic_node_weights else None
1957
+
1958
+ self.set(name=template.kind, schema=template)
1959
+
1816
1960
  def generate_weight(self) -> None:
1817
1961
  self._generate_weight_generics()
1818
1962
  self._generate_weight_nodes_profiles()
1819
1963
  self._generate_weight_templates()
1964
+ self._generate_generics_templates_weight()
1820
1965
 
1821
1966
  def cleanup_inherited_elements(self) -> None:
1822
1967
  for name in self.node_names:
@@ -2142,7 +2287,7 @@ class SchemaBranch:
2142
2287
  )
2143
2288
 
2144
2289
  for node_attr in node.attributes:
2145
- if node_attr.read_only or node_attr.optional is False:
2290
+ if not node_attr.support_profiles:
2146
2291
  continue
2147
2292
  attr_schema_class = get_attribute_schema_class_for_kind(kind=node_attr.kind)
2148
2293
  attr = attr_schema_class(
@@ -2447,3 +2592,16 @@ class SchemaBranch:
2447
2592
  updated_used_by_node = set(chain(template_schema_kinds, set(core_node_schema.used_by)))
2448
2593
  core_node_schema.used_by = sorted(updated_used_by_node)
2449
2594
  self.set(name=InfrahubKind.NODE, schema=core_node_schema)
2595
+
2596
+
2597
+ def _format_display_label_component(component: str) -> str:
2598
+ """Return correct format for display_label.
2599
+
2600
+ Previously both the format of 'name' and 'name__value' was
2601
+ supported this function ensures that the proper 'name__value'
2602
+ format is used
2603
+ """
2604
+ if "__" in component:
2605
+ return component
2606
+
2607
+ return f"{component}__value"
@@ -0,0 +1,21 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from infrahub.core.graph import GRAPH_VERSION
6
+ from infrahub.core.initialization import get_root_node
7
+ from infrahub.log import get_logger
8
+
9
+ if TYPE_CHECKING:
10
+ from infrahub.database import InfrahubDatabase
11
+
12
+
13
+ log = get_logger()
14
+
15
+
16
+ async def validate_graph_version(db: InfrahubDatabase) -> None:
17
+ root = await get_root_node(db=db)
18
+ if root.graph_version != GRAPH_VERSION:
19
+ log.warning(
20
+ f"Expected database graph version {GRAPH_VERSION} but got {root.graph_version}, possibly 'infrahub upgrade' has not been executed"
21
+ )
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from typing import cast
4
4
 
5
+ from infrahub_sdk.exceptions import URLNotFoundError
5
6
  from infrahub_sdk.template import Jinja2Template
6
7
  from prefect import flow
7
8
  from prefect.logging import get_run_logger
@@ -53,12 +54,17 @@ async def display_label_jinja2_update_value(
53
54
  log.debug(f"Ignoring to update {obj} with existing value on display_label={value}")
54
55
  return
55
56
 
56
- await client.execute_graphql(
57
- query=UPDATE_DISPLAY_LABEL,
58
- variables={"id": obj.node_id, "kind": node_kind, "value": value},
59
- branch_name=branch_name,
60
- )
61
- log.info(f"Updating {node_kind}.display_label='{value}' ({obj.node_id})")
57
+ try:
58
+ await client.execute_graphql(
59
+ query=UPDATE_DISPLAY_LABEL,
60
+ variables={"id": obj.node_id, "kind": node_kind, "value": value},
61
+ branch_name=branch_name,
62
+ )
63
+ log.info(f"Updating {node_kind}.display_label='{value}' ({obj.node_id})")
64
+ except URLNotFoundError:
65
+ log.warning(
66
+ f"Updating {node_kind}.display_label='{value}' ({obj.node_id}) failed for branch {branch_name} (branch not found)"
67
+ )
62
68
 
63
69
 
64
70
  @flow(
@@ -152,7 +158,7 @@ async def display_labels_setup_jinja2(
152
158
 
153
159
  @flow(
154
160
  name="trigger-update-display-labels",
155
- flow_run_name="Trigger updates for display labels for kind",
161
+ flow_run_name="Trigger updates for display labels for {kind}",
156
162
  )
157
163
  async def trigger_update_display_labels(
158
164
  branch_name: str,
@@ -109,7 +109,7 @@ class BranchRebasedEvent(InfrahubEvent):
109
109
 
110
110
  event_name: ClassVar[str] = f"{EVENT_NAMESPACE}.branch.rebased"
111
111
 
112
- branch_id: str = Field(..., description="The ID of the mutated node")
112
+ branch_id: str = Field(..., description="The ID of the branch")
113
113
  branch_name: str = Field(..., description="The name of the branch")
114
114
 
115
115
  def get_resource(self) -> dict[str, str]:
@@ -128,3 +128,29 @@ class BranchRebasedEvent(InfrahubEvent):
128
128
  RefreshRegistryRebasedBranch(branch=self.branch_name),
129
129
  ]
130
130
  return events
131
+
132
+
133
+ class BranchMigratedEvent(InfrahubEvent):
134
+ """Event generated when a branch has been migrated"""
135
+
136
+ event_name: ClassVar[str] = f"{EVENT_NAMESPACE}.branch.migrated"
137
+
138
+ branch_id: str = Field(..., description="The ID of the branch")
139
+ branch_name: str = Field(..., description="The name of the branch")
140
+
141
+ def get_resource(self) -> dict[str, str]:
142
+ return {
143
+ "prefect.resource.id": f"infrahub.branch.{self.branch_name}",
144
+ "infrahub.branch.id": self.branch_id,
145
+ "infrahub.branch.name": self.branch_name,
146
+ }
147
+
148
+ def get_messages(self) -> list[InfrahubMessage]:
149
+ events: list[InfrahubMessage] = [
150
+ # EventBranchMigrated(
151
+ # branch=self.branch,
152
+ # meta=self.get_message_meta(),
153
+ # ),
154
+ RefreshRegistryRebasedBranch(branch=self.branch_name),
155
+ ]
156
+ return events
@@ -23,6 +23,7 @@ from infrahub.generators.models import (
23
23
  )
24
24
  from infrahub.git.base import extract_repo_file_information
25
25
  from infrahub.git.repository import get_initialized_repo
26
+ from infrahub.git.utils import fetch_proposed_change_generator_definition_targets
26
27
  from infrahub.workers.dependencies import get_client, get_workflow
27
28
  from infrahub.workflows.catalogue import REQUEST_GENERATOR_DEFINITION_RUN, REQUEST_GENERATOR_RUN
28
29
  from infrahub.workflows.utils import add_tags
@@ -195,14 +196,9 @@ async def request_generator_definition_run(
195
196
  branch=model.branch,
196
197
  )
197
198
 
198
- group = await client.get(
199
- kind=InfrahubKind.GENERICGROUP,
200
- prefetch_relationships=True,
201
- populate_store=True,
202
- id=model.generator_definition.group_id,
203
- branch=model.branch,
199
+ group = await fetch_proposed_change_generator_definition_targets(
200
+ client=client, branch=model.branch, definition=model.generator_definition
204
201
  )
205
- await group.members.fetch()
206
202
 
207
203
  instance_by_member = {}
208
204
  for instance in existing_instances:
infrahub/git/base.py CHANGED
@@ -941,7 +941,10 @@ class InfrahubRepositoryBase(BaseModel, ABC):
941
941
  def _raise_enriched_error_static(
942
942
  error: GitCommandError, name: str, location: str, branch_name: str | None = None
943
943
  ) -> NoReturn:
944
- if "Repository not found" in error.stderr or "does not appear to be a git" in error.stderr:
944
+ if any(
945
+ err in error.stderr
946
+ for err in ("Repository not found", "does not appear to be a git", "Failed to connect to")
947
+ ):
945
948
  raise RepositoryConnectionError(identifier=name) from error
946
949
 
947
950
  if "error: pathspec" in error.stderr:
@@ -1371,7 +1371,7 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
1371
1371
  message: CheckArtifactCreate | RequestArtifactGenerate,
1372
1372
  ) -> ArtifactGenerateResult:
1373
1373
  response = await self.sdk.query_gql_query(
1374
- name=message.query,
1374
+ name=message.query_id,
1375
1375
  variables=message.variables,
1376
1376
  update_group=True,
1377
1377
  subscribers=[artifact.id],
infrahub/git/models.py CHANGED
@@ -38,7 +38,8 @@ class RequestArtifactGenerate(BaseModel):
38
38
  target_kind: str = Field(..., description="The kind of the target object for this artifact")
39
39
  target_name: str = Field(..., description="Name of the artifact target")
40
40
  artifact_id: str | None = Field(default=None, description="The id of the artifact if it previously existed")
41
- query: str = Field(..., description="The name of the query to use when collecting data")
41
+ query: str = Field(..., description="The name of the query to use when collecting data") # Deprecated
42
+ query_id: str = Field(..., description="The id of the query to use when collecting data")
42
43
  timeout: int = Field(..., description="Timeout for requests used to generate this artifact")
43
44
  variables: dict = Field(..., description="Input variables when generating the artifact")
44
45
  context: InfrahubContext = Field(..., description="The context of the task")