infrahub-server 1.5.0b0__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 (183) hide show
  1. infrahub/actions/tasks.py +8 -0
  2. infrahub/api/diff/diff.py +1 -1
  3. infrahub/api/internal.py +2 -0
  4. infrahub/api/oauth2.py +13 -19
  5. infrahub/api/oidc.py +15 -21
  6. infrahub/api/schema.py +24 -3
  7. infrahub/artifacts/models.py +2 -1
  8. infrahub/auth.py +137 -3
  9. infrahub/cli/__init__.py +2 -0
  10. infrahub/cli/db.py +103 -98
  11. infrahub/cli/db_commands/clean_duplicate_schema_fields.py +212 -0
  12. infrahub/cli/dev.py +118 -0
  13. infrahub/cli/tasks.py +46 -0
  14. infrahub/cli/upgrade.py +30 -3
  15. infrahub/computed_attribute/tasks.py +20 -8
  16. infrahub/core/attribute.py +13 -5
  17. infrahub/core/branch/enums.py +1 -1
  18. infrahub/core/branch/models.py +7 -3
  19. infrahub/core/branch/tasks.py +70 -8
  20. infrahub/core/changelog/models.py +4 -12
  21. infrahub/core/constants/__init__.py +3 -0
  22. infrahub/core/constants/infrahubkind.py +1 -0
  23. infrahub/core/diff/model/path.py +4 -0
  24. infrahub/core/diff/payload_builder.py +1 -1
  25. infrahub/core/diff/query/artifact.py +1 -0
  26. infrahub/core/diff/query/field_summary.py +1 -0
  27. infrahub/core/graph/__init__.py +1 -1
  28. infrahub/core/initialization.py +5 -2
  29. infrahub/core/ipam/utilization.py +1 -1
  30. infrahub/core/manager.py +6 -3
  31. infrahub/core/migrations/__init__.py +3 -0
  32. infrahub/core/migrations/exceptions.py +4 -0
  33. infrahub/core/migrations/graph/__init__.py +12 -11
  34. infrahub/core/migrations/graph/load_schema_branch.py +21 -0
  35. infrahub/core/migrations/graph/m013_convert_git_password_credential.py +1 -1
  36. infrahub/core/migrations/graph/m040_duplicated_attributes.py +81 -0
  37. infrahub/core/migrations/graph/m041_profile_attrs_in_db.py +145 -0
  38. infrahub/core/migrations/graph/m042_create_hfid_display_label_in_db.py +164 -0
  39. infrahub/core/migrations/graph/m043_backfill_hfid_display_label_in_db.py +866 -0
  40. infrahub/core/migrations/query/__init__.py +7 -8
  41. infrahub/core/migrations/query/attribute_add.py +8 -6
  42. infrahub/core/migrations/query/attribute_remove.py +134 -0
  43. infrahub/core/migrations/runner.py +54 -0
  44. infrahub/core/migrations/schema/attribute_kind_update.py +9 -3
  45. infrahub/core/migrations/schema/attribute_supports_profile.py +90 -0
  46. infrahub/core/migrations/schema/node_attribute_add.py +35 -4
  47. infrahub/core/migrations/schema/node_attribute_remove.py +13 -109
  48. infrahub/core/migrations/schema/node_kind_update.py +2 -1
  49. infrahub/core/migrations/schema/node_remove.py +2 -1
  50. infrahub/core/migrations/schema/placeholder_dummy.py +3 -2
  51. infrahub/core/migrations/shared.py +52 -19
  52. infrahub/core/node/__init__.py +158 -51
  53. infrahub/core/node/constraints/attribute_uniqueness.py +3 -1
  54. infrahub/core/node/create.py +46 -63
  55. infrahub/core/node/lock_utils.py +70 -44
  56. infrahub/core/node/node_property_attribute.py +230 -0
  57. infrahub/core/node/resource_manager/ip_address_pool.py +2 -1
  58. infrahub/core/node/resource_manager/ip_prefix_pool.py +2 -1
  59. infrahub/core/node/resource_manager/number_pool.py +2 -1
  60. infrahub/core/node/standard.py +1 -1
  61. infrahub/core/protocols.py +7 -1
  62. infrahub/core/query/attribute.py +55 -0
  63. infrahub/core/query/ipam.py +1 -0
  64. infrahub/core/query/node.py +23 -4
  65. infrahub/core/query/relationship.py +1 -0
  66. infrahub/core/registry.py +2 -2
  67. infrahub/core/relationship/constraints/count.py +1 -1
  68. infrahub/core/relationship/model.py +1 -1
  69. infrahub/core/schema/__init__.py +56 -0
  70. infrahub/core/schema/attribute_schema.py +4 -0
  71. infrahub/core/schema/basenode_schema.py +42 -2
  72. infrahub/core/schema/definitions/core/__init__.py +2 -0
  73. infrahub/core/schema/definitions/core/generator.py +2 -0
  74. infrahub/core/schema/definitions/core/group.py +16 -2
  75. infrahub/core/schema/definitions/internal.py +16 -3
  76. infrahub/core/schema/generated/attribute_schema.py +2 -2
  77. infrahub/core/schema/generated/base_node_schema.py +6 -1
  78. infrahub/core/schema/manager.py +22 -1
  79. infrahub/core/schema/node_schema.py +5 -2
  80. infrahub/core/schema/schema_branch.py +300 -8
  81. infrahub/core/schema/schema_branch_display.py +123 -0
  82. infrahub/core/schema/schema_branch_hfid.py +114 -0
  83. infrahub/core/validators/aggregated_checker.py +1 -1
  84. infrahub/core/validators/determiner.py +12 -1
  85. infrahub/core/validators/relationship/peer.py +1 -1
  86. infrahub/core/validators/tasks.py +1 -1
  87. infrahub/database/graph.py +21 -0
  88. infrahub/display_labels/__init__.py +0 -0
  89. infrahub/display_labels/gather.py +48 -0
  90. infrahub/display_labels/models.py +240 -0
  91. infrahub/display_labels/tasks.py +192 -0
  92. infrahub/display_labels/triggers.py +22 -0
  93. infrahub/events/branch_action.py +27 -1
  94. infrahub/events/group_action.py +1 -1
  95. infrahub/events/node_action.py +1 -1
  96. infrahub/generators/constants.py +7 -0
  97. infrahub/generators/models.py +7 -0
  98. infrahub/generators/tasks.py +34 -22
  99. infrahub/git/base.py +4 -1
  100. infrahub/git/integrator.py +23 -15
  101. infrahub/git/models.py +2 -1
  102. infrahub/git/repository.py +22 -5
  103. infrahub/git/tasks.py +66 -10
  104. infrahub/git/utils.py +123 -1
  105. infrahub/graphql/analyzer.py +1 -1
  106. infrahub/graphql/api/endpoints.py +14 -4
  107. infrahub/graphql/manager.py +4 -9
  108. infrahub/graphql/mutations/convert_object_type.py +11 -1
  109. infrahub/graphql/mutations/display_label.py +118 -0
  110. infrahub/graphql/mutations/generator.py +25 -7
  111. infrahub/graphql/mutations/hfid.py +125 -0
  112. infrahub/graphql/mutations/ipam.py +54 -35
  113. infrahub/graphql/mutations/main.py +27 -28
  114. infrahub/graphql/mutations/relationship.py +2 -2
  115. infrahub/graphql/mutations/resource_manager.py +2 -2
  116. infrahub/graphql/mutations/schema.py +5 -5
  117. infrahub/graphql/queries/resource_manager.py +1 -1
  118. infrahub/graphql/resolvers/resolver.py +2 -0
  119. infrahub/graphql/schema.py +4 -0
  120. infrahub/graphql/schema_sort.py +170 -0
  121. infrahub/graphql/types/branch.py +4 -1
  122. infrahub/graphql/types/enums.py +3 -0
  123. infrahub/groups/tasks.py +1 -1
  124. infrahub/hfid/__init__.py +0 -0
  125. infrahub/hfid/gather.py +48 -0
  126. infrahub/hfid/models.py +240 -0
  127. infrahub/hfid/tasks.py +191 -0
  128. infrahub/hfid/triggers.py +22 -0
  129. infrahub/lock.py +67 -16
  130. infrahub/message_bus/types.py +2 -1
  131. infrahub/middleware.py +26 -1
  132. infrahub/permissions/constants.py +2 -0
  133. infrahub/proposed_change/tasks.py +35 -17
  134. infrahub/server.py +21 -4
  135. infrahub/services/__init__.py +8 -5
  136. infrahub/services/adapters/http/__init__.py +5 -0
  137. infrahub/services/adapters/workflow/worker.py +14 -3
  138. infrahub/task_manager/event.py +5 -0
  139. infrahub/task_manager/models.py +7 -0
  140. infrahub/task_manager/task.py +73 -0
  141. infrahub/trigger/catalogue.py +4 -0
  142. infrahub/trigger/models.py +2 -0
  143. infrahub/trigger/setup.py +13 -4
  144. infrahub/trigger/tasks.py +6 -0
  145. infrahub/workers/dependencies.py +10 -1
  146. infrahub/workers/infrahub_async.py +10 -2
  147. infrahub/workflows/catalogue.py +80 -0
  148. infrahub/workflows/initialization.py +21 -0
  149. infrahub/workflows/utils.py +2 -1
  150. infrahub_sdk/checks.py +1 -1
  151. infrahub_sdk/client.py +13 -10
  152. infrahub_sdk/config.py +29 -2
  153. infrahub_sdk/ctl/cli_commands.py +2 -0
  154. infrahub_sdk/ctl/generator.py +4 -0
  155. infrahub_sdk/ctl/graphql.py +184 -0
  156. infrahub_sdk/ctl/schema.py +28 -9
  157. infrahub_sdk/generator.py +7 -1
  158. infrahub_sdk/graphql/__init__.py +12 -0
  159. infrahub_sdk/graphql/constants.py +1 -0
  160. infrahub_sdk/graphql/plugin.py +85 -0
  161. infrahub_sdk/graphql/query.py +77 -0
  162. infrahub_sdk/{graphql.py → graphql/renderers.py} +81 -73
  163. infrahub_sdk/graphql/utils.py +40 -0
  164. infrahub_sdk/protocols.py +14 -0
  165. infrahub_sdk/schema/__init__.py +70 -4
  166. infrahub_sdk/schema/repository.py +8 -0
  167. infrahub_sdk/spec/models.py +7 -0
  168. infrahub_sdk/spec/object.py +53 -44
  169. infrahub_sdk/spec/processors/__init__.py +0 -0
  170. infrahub_sdk/spec/processors/data_processor.py +10 -0
  171. infrahub_sdk/spec/processors/factory.py +34 -0
  172. infrahub_sdk/spec/processors/range_expand_processor.py +56 -0
  173. infrahub_sdk/spec/range_expansion.py +1 -1
  174. infrahub_sdk/transforms.py +1 -1
  175. {infrahub_server-1.5.0b0.dist-info → infrahub_server-1.5.0b2.dist-info}/METADATA +7 -4
  176. {infrahub_server-1.5.0b0.dist-info → infrahub_server-1.5.0b2.dist-info}/RECORD +182 -143
  177. infrahub_testcontainers/container.py +115 -3
  178. infrahub_testcontainers/docker-compose-cluster.test.yml +6 -1
  179. infrahub_testcontainers/docker-compose.test.yml +6 -1
  180. infrahub/core/migrations/graph/m040_profile_attrs_in_db.py +0 -166
  181. {infrahub_server-1.5.0b0.dist-info → infrahub_server-1.5.0b2.dist-info}/LICENSE.txt +0 -0
  182. {infrahub_server-1.5.0b0.dist-info → infrahub_server-1.5.0b2.dist-info}/WHEEL +0 -0
  183. {infrahub_server-1.5.0b0.dist-info → infrahub_server-1.5.0b2.dist-info}/entry_points.txt +0 -0
infrahub/cli/tasks.py CHANGED
@@ -3,9 +3,11 @@ import logging
3
3
  import typer
4
4
  from infrahub_sdk.async_typer import AsyncTyper
5
5
  from prefect.client.orchestration import get_client
6
+ from prefect.client.schemas.objects import StateType
6
7
 
7
8
  from infrahub import config
8
9
  from infrahub.services.adapters.workflow.worker import WorkflowWorkerExecution
10
+ from infrahub.task_manager.task import PrefectTask
9
11
  from infrahub.tasks.dummy import DUMMY_FLOW, DummyInput
10
12
  from infrahub.workflows.initialization import setup_task_manager
11
13
  from infrahub.workflows.models import WorkerPoolDefinition
@@ -50,3 +52,47 @@ async def execute(
50
52
  workflow=DUMMY_FLOW, parameters={"data": DummyInput(firstname="John", lastname="Doe")}
51
53
  ) # type: ignore[var-annotated]
52
54
  print(result)
55
+
56
+
57
+ flush_app = AsyncTyper()
58
+
59
+ app.add_typer(flush_app, name="flush")
60
+
61
+
62
+ @flush_app.command()
63
+ async def flow_runs(
64
+ ctx: typer.Context, # noqa: ARG001
65
+ config_file: str = typer.Argument("infrahub.toml", envvar="INFRAHUB_CONFIG"),
66
+ days_to_keep: int = 30,
67
+ batch_size: int = 100,
68
+ ) -> None:
69
+ """Flush old task runs"""
70
+ logging.getLogger("infrahub").setLevel(logging.WARNING)
71
+ logging.getLogger("neo4j").setLevel(logging.ERROR)
72
+ logging.getLogger("prefect").setLevel(logging.ERROR)
73
+
74
+ config.load_and_exit(config_file_name=config_file)
75
+
76
+ await PrefectTask.delete_flow_runs(
77
+ days_to_keep=days_to_keep,
78
+ batch_size=batch_size,
79
+ )
80
+
81
+
82
+ @flush_app.command()
83
+ async def stale_runs(
84
+ ctx: typer.Context, # noqa: ARG001
85
+ config_file: str = typer.Argument("infrahub.toml", envvar="INFRAHUB_CONFIG"),
86
+ days_to_keep: int = 2,
87
+ batch_size: int = 100,
88
+ ) -> None:
89
+ """Flush stale task runs"""
90
+ logging.getLogger("infrahub").setLevel(logging.WARNING)
91
+ logging.getLogger("neo4j").setLevel(logging.ERROR)
92
+ logging.getLogger("prefect").setLevel(logging.ERROR)
93
+
94
+ config.load_and_exit(config_file_name=config_file)
95
+
96
+ await PrefectTask.delete_flow_runs(
97
+ states=[StateType.RUNNING], delete=False, days_to_keep=days_to_keep, batch_size=batch_size
98
+ )
infrahub/cli/upgrade.py CHANGED
@@ -11,10 +11,16 @@ from prefect.client.orchestration import get_client
11
11
  from rich import print as rprint
12
12
 
13
13
  from infrahub import config
14
- from infrahub.core.initialization import create_anonymous_role, create_default_account_groups, initialize_registry
14
+ from infrahub.core.initialization import (
15
+ create_anonymous_role,
16
+ create_default_account_groups,
17
+ get_root_node,
18
+ initialize_registry,
19
+ )
15
20
  from infrahub.core.manager import NodeManager
16
21
  from infrahub.core.protocols import CoreAccount, CoreObjectPermission
17
22
  from infrahub.dependencies.registry import build_component_registry
23
+ from infrahub.lock import initialize_lock
18
24
  from infrahub.menu.menu import default_menu
19
25
  from infrahub.menu.models import MenuDict
20
26
  from infrahub.menu.repository import MenuRepository
@@ -26,7 +32,13 @@ from infrahub.workflows.initialization import (
26
32
  setup_worker_pools,
27
33
  )
28
34
 
29
- from .db import initialize_internal_schema, migrate_database, update_core_schema
35
+ from .db import (
36
+ detect_migration_to_run,
37
+ initialize_internal_schema,
38
+ migrate_database,
39
+ trigger_rebase_branches,
40
+ update_core_schema,
41
+ )
30
42
 
31
43
  if TYPE_CHECKING:
32
44
  from infrahub.cli.context import CliContext
@@ -40,6 +52,7 @@ async def upgrade_cmd(
40
52
  ctx: typer.Context,
41
53
  config_file: str = typer.Argument("infrahub.toml", envvar="INFRAHUB_CONFIG"),
42
54
  check: bool = typer.Option(False, help="Check the state of the system without upgrading."),
55
+ rebase_branches: bool = typer.Option(False, help="Rebase and apply migrations to branches if required."),
43
56
  ) -> None:
44
57
  """Upgrade Infrahub to the latest version."""
45
58
 
@@ -54,9 +67,12 @@ async def upgrade_cmd(
54
67
  dbdriver = await context.init_db(retry=1)
55
68
 
56
69
  await initialize_registry(db=dbdriver)
70
+ initialize_lock()
57
71
 
58
72
  build_component_registry()
59
73
 
74
+ root_node = await get_root_node(db=dbdriver)
75
+
60
76
  # NOTE add step to validate if the database and the task manager are reachable
61
77
 
62
78
  # -------------------------------------------
@@ -67,7 +83,12 @@ async def upgrade_cmd(
67
83
  # Upgrade Infrahub Database and Schema
68
84
  # -------------------------------------------
69
85
 
70
- if not await migrate_database(db=dbdriver, initialize=False, check=check):
86
+ migrations = await detect_migration_to_run(current_graph_version=root_node.graph_version)
87
+ if check:
88
+ await dbdriver.close()
89
+ return
90
+
91
+ if not await migrate_database(db=dbdriver, initialize=False, migrations=migrations):
71
92
  # A migration failed, stop the upgrade process
72
93
  rprint("Upgrade cancelled due to migration failure.")
73
94
  await dbdriver.close()
@@ -91,6 +112,12 @@ async def upgrade_cmd(
91
112
  await setup_deployments(client=client)
92
113
  await trigger_configure_all()
93
114
 
115
+ # -------------------------------------------
116
+ # Perform branch rebase and apply migrations to them
117
+ # -------------------------------------------
118
+ if rebase_branches:
119
+ await trigger_rebase_branches(db=dbdriver)
120
+
94
121
  await dbdriver.close()
95
122
 
96
123
 
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING
4
4
 
5
+ from infrahub_sdk.exceptions import URLNotFoundError
5
6
  from infrahub_sdk.protocols import CoreTransformPython
6
7
  from infrahub_sdk.template import Jinja2Template
7
8
  from prefect import flow
@@ -104,7 +105,7 @@ async def process_transform(
104
105
  ) # type: ignore[misc]
105
106
 
106
107
  data = await client.query_gql_query(
107
- name=transform.query.peer.name.value,
108
+ name=transform.query.id,
108
109
  branch_name=branch_name,
109
110
  variables={"id": object_id},
110
111
  update_group=True,
@@ -177,12 +178,17 @@ async def computed_attribute_jinja2_update_value(
177
178
  log.debug(f"Ignoring to update {obj} with existing value on {attribute_name}={value}")
178
179
  return
179
180
 
180
- await client.execute_graphql(
181
- query=UPDATE_ATTRIBUTE,
182
- variables={"id": obj.node_id, "kind": node_kind, "attribute": attribute_name, "value": value},
183
- branch_name=branch_name,
184
- )
185
- log.info(f"Updating computed attribute {node_kind}.{attribute_name}='{value}' ({obj.node_id})")
181
+ try:
182
+ await client.execute_graphql(
183
+ query=UPDATE_ATTRIBUTE,
184
+ variables={"id": obj.node_id, "kind": node_kind, "attribute": attribute_name, "value": value},
185
+ branch_name=branch_name,
186
+ )
187
+ log.info(f"Updating computed attribute {node_kind}.{attribute_name}='{value}' ({obj.node_id})")
188
+ except URLNotFoundError:
189
+ log.warning(
190
+ f"Update of computed attribute {node_kind}.{attribute_name} failed for branch {branch_name} (not found)"
191
+ )
186
192
 
187
193
 
188
194
  @flow(
@@ -229,7 +235,13 @@ async def process_jinja2(
229
235
 
230
236
  for id_filter in computed_macro.node_filters:
231
237
  query = attribute_graphql.render_graphql_query(query_filter=id_filter, filter_id=object_id)
232
- response = await client.execute_graphql(query=query, branch_name=branch_name)
238
+ try:
239
+ response = await client.execute_graphql(query=query, branch_name=branch_name)
240
+ except URLNotFoundError:
241
+ log.warning(
242
+ f"Process computed attributes for {computed_attribute_kind}.{computed_attribute_name} failed for branch {branch_name} (not found)"
243
+ )
244
+ return
233
245
  output = attribute_graphql.parse_response(response=response)
234
246
  found.extend(output)
235
247
 
@@ -18,6 +18,7 @@ from infrahub.core.changelog.models import AttributeChangelog
18
18
  from infrahub.core.constants import NULL_VALUE, AttributeDBNodeType, BranchSupportType, RelationshipStatus
19
19
  from infrahub.core.property import FlagPropertyMixin, NodePropertyData, NodePropertyMixin
20
20
  from infrahub.core.query.attribute import (
21
+ AttributeClearNodePropertyQuery,
21
22
  AttributeGetQuery,
22
23
  AttributeUpdateFlagQuery,
23
24
  AttributeUpdateNodePropertyQuery,
@@ -36,7 +37,7 @@ from .schema.attribute_parameters import NumberAttributeParameters
36
37
  if TYPE_CHECKING:
37
38
  from infrahub.core.branch import Branch
38
39
  from infrahub.core.node import Node
39
- from infrahub.core.schema import AttributeSchema
40
+ from infrahub.core.schema import AttributeSchema, MainSchemaTypes
40
41
  from infrahub.database import InfrahubDatabase
41
42
 
42
43
 
@@ -491,6 +492,12 @@ class BaseAttribute(FlagPropertyMixin, NodePropertyMixin):
491
492
  )
492
493
  await query.execute(db=db)
493
494
 
495
+ if needs_clear:
496
+ query = await AttributeClearNodePropertyQuery.init(
497
+ db=db, attr=self, at=update_at, prop_name=prop_name, prop_id=database_prop_id
498
+ )
499
+ await query.execute(db=db)
500
+
494
501
  # set the to time on the previously active edge
495
502
  rel = current_attr_result.get(f"rel_{prop_name}")
496
503
  if rel and rel.get("branch") == branch.name:
@@ -581,7 +588,7 @@ class BaseAttribute(FlagPropertyMixin, NodePropertyMixin):
581
588
 
582
589
  return value
583
590
 
584
- async def from_graphql(self, data: dict, db: InfrahubDatabase) -> bool:
591
+ async def from_graphql(self, data: dict, db: InfrahubDatabase, process_pools: bool = True) -> bool:
585
592
  """Update attr from GraphQL payload"""
586
593
 
587
594
  changed = False
@@ -595,7 +602,8 @@ class BaseAttribute(FlagPropertyMixin, NodePropertyMixin):
595
602
  changed = True
596
603
  elif "from_pool" in data:
597
604
  self.from_pool = data["from_pool"]
598
- await self.node.handle_pool(db=db, attribute=self, errors=[])
605
+ if process_pools:
606
+ await self.node.handle_pool(db=db, attribute=self, errors=[])
599
607
  changed = True
600
608
 
601
609
  if changed and self.is_from_profile:
@@ -630,7 +638,7 @@ class BaseAttribute(FlagPropertyMixin, NodePropertyMixin):
630
638
  return AttributeDBNodeType.DEFAULT
631
639
  return AttributeDBNodeType.INDEXED
632
640
 
633
- def get_create_data(self) -> AttributeCreateData:
641
+ def get_create_data(self, node_schema: MainSchemaTypes) -> AttributeCreateData:
634
642
  branch = self.branch
635
643
  hierarchy_level = branch.hierarchy_level
636
644
  if self.schema.branch == BranchSupportType.AGNOSTIC:
@@ -645,7 +653,7 @@ class BaseAttribute(FlagPropertyMixin, NodePropertyMixin):
645
653
  branch=branch.name,
646
654
  status="active",
647
655
  branch_level=hierarchy_level,
648
- branch_support=self.schema.branch.value,
656
+ branch_support=self.schema.branch.value if self.schema.branch is not None else node_schema.branch,
649
657
  content=self.to_db(),
650
658
  is_default=self.is_default,
651
659
  is_protected=self.is_protected,
@@ -4,5 +4,5 @@ from infrahub.utils import InfrahubStringEnum
4
4
  class BranchStatus(InfrahubStringEnum):
5
5
  OPEN = "OPEN"
6
6
  NEED_REBASE = "NEED_REBASE"
7
- CLOSED = "CLOSED"
7
+ NEED_UPGRADE_REBASE = "NEED_UPGRADE_REBASE"
8
8
  DELETING = "DELETING"
@@ -6,9 +6,8 @@ from typing import TYPE_CHECKING, Any, Optional, Self, Union
6
6
  from pydantic import Field, field_validator
7
7
 
8
8
  from infrahub.core.branch.enums import BranchStatus
9
- from infrahub.core.constants import (
10
- GLOBAL_BRANCH_NAME,
11
- )
9
+ from infrahub.core.constants import GLOBAL_BRANCH_NAME
10
+ from infrahub.core.graph import GRAPH_VERSION
12
11
  from infrahub.core.models import SchemaBranchHash # noqa: TC001
13
12
  from infrahub.core.node.standard import StandardNode
14
13
  from infrahub.core.query import QueryType
@@ -46,6 +45,7 @@ class Branch(StandardNode):
46
45
  is_isolated: bool = True
47
46
  schema_changed_at: Optional[str] = None
48
47
  schema_hash: Optional[SchemaBranchHash] = None
48
+ graph_version: int | None = None
49
49
 
50
50
  _exclude_attrs: list[str] = ["id", "uuid", "owner"]
51
51
 
@@ -261,6 +261,10 @@ class Branch(StandardNode):
261
261
 
262
262
  return start, end
263
263
 
264
+ async def create(self, db: InfrahubDatabase) -> bool:
265
+ self.graph_version = GRAPH_VERSION
266
+ return await super().create(db=db)
267
+
264
268
  async def delete(self, db: InfrahubDatabase) -> None:
265
269
  if self.is_default:
266
270
  raise ValidationError(f"Unable to delete {self.name} it is the default branch.")
@@ -12,6 +12,7 @@ from infrahub import lock
12
12
  from infrahub.context import InfrahubContext # noqa: TC001 needed for prefect flow
13
13
  from infrahub.core import registry
14
14
  from infrahub.core.branch import Branch
15
+ from infrahub.core.branch.enums import BranchStatus
15
16
  from infrahub.core.changelog.diff import DiffChangelogCollector, MigrationTracker
16
17
  from infrahub.core.constants import MutationAction
17
18
  from infrahub.core.diff.coordinator import DiffCoordinator
@@ -21,7 +22,10 @@ from infrahub.core.diff.merger.merger import DiffMerger
21
22
  from infrahub.core.diff.model.path import BranchTrackingId, EnrichedDiffRoot, EnrichedDiffRootMetadata
22
23
  from infrahub.core.diff.models import RequestDiffUpdate
23
24
  from infrahub.core.diff.repository.repository import DiffRepository
25
+ from infrahub.core.graph import GRAPH_VERSION
24
26
  from infrahub.core.merge import BranchMerger
27
+ from infrahub.core.migrations.exceptions import MigrationFailureError
28
+ from infrahub.core.migrations.runner import MigrationRunner
25
29
  from infrahub.core.migrations.schema.models import SchemaApplyMigrationData
26
30
  from infrahub.core.migrations.schema.tasks import schema_apply_migrations
27
31
  from infrahub.core.timestamp import Timestamp
@@ -29,10 +33,17 @@ from infrahub.core.validators.determiner import ConstraintValidatorDeterminer
29
33
  from infrahub.core.validators.models.validate_migration import SchemaValidateMigrationData
30
34
  from infrahub.core.validators.tasks import schema_validate_migrations
31
35
  from infrahub.dependencies.registry import get_component_registry
32
- from infrahub.events.branch_action import BranchCreatedEvent, BranchDeletedEvent, BranchMergedEvent, BranchRebasedEvent
36
+ from infrahub.events.branch_action import (
37
+ BranchCreatedEvent,
38
+ BranchDeletedEvent,
39
+ BranchMergedEvent,
40
+ BranchMigratedEvent,
41
+ BranchRebasedEvent,
42
+ )
33
43
  from infrahub.events.models import EventMeta, InfrahubEvent
34
44
  from infrahub.events.node_action import get_node_event
35
45
  from infrahub.exceptions import BranchNotFoundError, ValidationError
46
+ from infrahub.generators.constants import GeneratorDefinitionRunSource
36
47
  from infrahub.graphql.mutations.models import BranchCreateModel # noqa: TC001
37
48
  from infrahub.workers.dependencies import get_component, get_database, get_event_service, get_workflow
38
49
  from infrahub.workflows.catalogue import (
@@ -48,8 +59,57 @@ from infrahub.workflows.catalogue import (
48
59
  from infrahub.workflows.utils import add_tags
49
60
 
50
61
 
62
+ @flow(name="branch-migrate", flow_run_name="Apply migrations to branch {branch}")
63
+ async def migrate_branch(branch: str, context: InfrahubContext, send_events: bool = True) -> None:
64
+ await add_tags(branches=[branch])
65
+
66
+ database = await get_database()
67
+ async with database.start_session() as db:
68
+ log = get_run_logger()
69
+
70
+ obj = await Branch.get_by_name(db=db, name=branch)
71
+
72
+ if obj.graph_version == GRAPH_VERSION:
73
+ log.info(f"Branch '{obj.name}' has graph version {obj.graph_version}, no migrations to apply")
74
+ return
75
+
76
+ migration_runner = MigrationRunner(branch=obj)
77
+ if not migration_runner.has_migrations():
78
+ log.info(f"No migrations detected for branch '{obj.name}'")
79
+ obj.graph_version = GRAPH_VERSION
80
+ await obj.save(db=db)
81
+ return
82
+
83
+ # Branch status will remain as so if the migration process fails
84
+ # This will help user to know that a branch is in an invalid state to be used properly and that actions need to be taken
85
+ if obj.status != BranchStatus.NEED_UPGRADE_REBASE:
86
+ obj.status = BranchStatus.NEED_UPGRADE_REBASE
87
+ await obj.save(db=db)
88
+
89
+ try:
90
+ log.info(f"Running migrations for branch '{obj.name}'")
91
+ await migration_runner.run(db=db)
92
+ except MigrationFailureError as exc:
93
+ log.error(f"Failed to run migrations for branch '{obj.name}': {exc.errors}")
94
+ raise
95
+
96
+ if obj.status == BranchStatus.NEED_UPGRADE_REBASE:
97
+ obj.status = BranchStatus.OPEN
98
+ obj.graph_version = GRAPH_VERSION
99
+ await obj.save(db=db)
100
+
101
+ if send_events:
102
+ event_service = await get_event_service()
103
+ await event_service.send(
104
+ BranchMigratedEvent(
105
+ branch_name=obj.name, branch_id=str(obj.uuid), meta=EventMeta(branch=obj, context=context)
106
+ )
107
+ )
108
+
109
+
51
110
  @flow(name="branch-rebase", flow_run_name="Rebase branch {branch}")
52
- async def rebase_branch(branch: str, context: InfrahubContext) -> None: # noqa: PLR0915
111
+ async def rebase_branch(branch: str, context: InfrahubContext, send_events: bool = True) -> None: # noqa: PLR0915
112
+ workflow = get_workflow()
53
113
  database = await get_database()
54
114
  async with database.start_session() as db:
55
115
  log = get_run_logger()
@@ -68,7 +128,7 @@ async def rebase_branch(branch: str, context: InfrahubContext) -> None: # noqa:
68
128
  diff_repository=diff_repository,
69
129
  source_branch=obj,
70
130
  diff_locker=DiffLocker(),
71
- workflow=get_workflow(),
131
+ workflow=workflow,
72
132
  )
73
133
 
74
134
  enriched_diff_metadata = await diff_coordinator.update_branch_diff(base_branch=base_branch, diff_branch=obj)
@@ -155,15 +215,17 @@ async def rebase_branch(branch: str, context: InfrahubContext) -> None: # noqa:
155
215
  target_branch_name=registry.default_branch,
156
216
  )
157
217
  if ipam_node_details:
158
- await get_workflow().submit_workflow(
218
+ await workflow.submit_workflow(
159
219
  workflow=IPAM_RECONCILIATION,
160
220
  context=context,
161
221
  parameters={"branch": obj.name, "ipam_node_details": ipam_node_details},
162
222
  )
163
223
 
164
- await get_workflow().submit_workflow(
165
- workflow=DIFF_REFRESH_ALL, context=context, parameters={"branch_name": obj.name}
166
- )
224
+ await migrate_branch(branch=branch, context=context, send_events=send_events)
225
+ await workflow.submit_workflow(workflow=DIFF_REFRESH_ALL, context=context, parameters={"branch_name": obj.name})
226
+
227
+ if not send_events:
228
+ return
167
229
 
168
230
  # -------------------------------------------------------------
169
231
  # Generate an event to indicate that a branch has been rebased
@@ -437,7 +499,7 @@ async def post_process_branch_merge(source_branch: str, target_branch: str, cont
437
499
  await get_workflow().submit_workflow(
438
500
  workflow=TRIGGER_GENERATOR_DEFINITION_RUN,
439
501
  context=context,
440
- parameters={"branch": target_branch},
502
+ parameters={"branch": target_branch, "source": GeneratorDefinitionRunSource.MERGE},
441
503
  )
442
504
 
443
505
  for diff_root in branch_diff_roots:
@@ -560,7 +560,7 @@ class RelationshipChangelogGetter:
560
560
 
561
561
  for peer in relationship.peers:
562
562
  if peer.peer_status == DiffAction.ADDED:
563
- peer_schema = schema_branch.get(name=peer.peer_kind)
563
+ peer_schema = schema_branch.get(name=peer.peer_kind, duplicate=False)
564
564
  secondaries.extend(
565
565
  self._process_added_peers(
566
566
  peer_id=peer.peer_id,
@@ -572,7 +572,7 @@ class RelationshipChangelogGetter:
572
572
  )
573
573
 
574
574
  elif peer.peer_status == DiffAction.REMOVED:
575
- peer_schema = schema_branch.get(name=peer.peer_kind)
575
+ peer_schema = schema_branch.get(name=peer.peer_kind, duplicate=False)
576
576
  secondaries.extend(
577
577
  self._process_removed_peers(
578
578
  peer_id=peer.peer_id,
@@ -596,11 +596,7 @@ class RelationshipChangelogGetter:
596
596
  secondaries: list[NodeChangelog] = []
597
597
  peer_relation = peer_schema.get_relationship_by_identifier(id=str(rel_schema.identifier), raise_on_error=False)
598
598
  if peer_relation:
599
- node_changelog = NodeChangelog(
600
- node_id=peer_id,
601
- node_kind=peer_kind,
602
- display_label="n/a",
603
- )
599
+ node_changelog = NodeChangelog(node_id=peer_id, node_kind=peer_kind, display_label="n/a")
604
600
  if peer_relation.cardinality == RelationshipCardinality.ONE:
605
601
  node_changelog.relationships[peer_relation.name] = RelationshipCardinalityOneChangelog(
606
602
  name=peer_relation.name,
@@ -634,11 +630,7 @@ class RelationshipChangelogGetter:
634
630
  secondaries: list[NodeChangelog] = []
635
631
  peer_relation = peer_schema.get_relationship_by_identifier(id=str(rel_schema.identifier), raise_on_error=False)
636
632
  if peer_relation:
637
- node_changelog = NodeChangelog(
638
- node_id=peer_id,
639
- node_kind=peer_kind,
640
- display_label="n/a",
641
- )
633
+ node_changelog = NodeChangelog(node_id=peer_id, node_kind=peer_kind, display_label="n/a")
642
634
  if peer_relation.cardinality == RelationshipCardinality.ONE:
643
635
  node_changelog.relationships[peer_relation.name] = RelationshipCardinalityOneChangelog(
644
636
  name=peer_relation.name,
@@ -37,6 +37,7 @@ RESERVED_ATTR_REL_NAMES = [
37
37
  "rels",
38
38
  "save",
39
39
  "hfid",
40
+ "process_pools",
40
41
  ]
41
42
 
42
43
  RESERVED_ATTR_GEN_NAMES = ["type"]
@@ -50,6 +51,7 @@ class EventType(InfrahubStringEnum):
50
51
  BRANCH_CREATED = f"{EVENT_NAMESPACE}.branch.created"
51
52
  BRANCH_DELETED = f"{EVENT_NAMESPACE}.branch.deleted"
52
53
  BRANCH_MERGED = f"{EVENT_NAMESPACE}.branch.merged"
54
+ BRANCH_MIGRATED = f"{EVENT_NAMESPACE}.branch.migrated"
53
55
  BRANCH_REBASED = f"{EVENT_NAMESPACE}.branch.rebased"
54
56
 
55
57
  SCHEMA_UPDATED = f"{EVENT_NAMESPACE}.schema.updated"
@@ -99,6 +101,7 @@ class GlobalPermissions(InfrahubStringEnum):
99
101
  MANAGE_PERMISSIONS = "manage_permissions"
100
102
  MANAGE_REPOSITORIES = "manage_repositories"
101
103
  OVERRIDE_CONTEXT = "override_context"
104
+ UPDATE_OBJECT_HFID_DISPLAY_LABEL = "update_object_hfid_display_label"
102
105
 
103
106
 
104
107
  class PermissionAction(InfrahubStringEnum):
@@ -28,6 +28,7 @@ GENERATORDEFINITION = "CoreGeneratorDefinition"
28
28
  GENERATORINSTANCE = "CoreGeneratorInstance"
29
29
  GENERATORVALIDATOR = "CoreGeneratorValidator"
30
30
  GENERATORGROUP = "CoreGeneratorGroup"
31
+ GENERATORAWAREGROUP = "CoreGeneratorAwareGroup"
31
32
  GENERICGROUP = "CoreGroup"
32
33
  GLOBALPERMISSION = "CoreGlobalPermission"
33
34
  GRAPHQLQUERY = "CoreGraphQLQuery"
@@ -335,6 +335,10 @@ class EnrichedDiffNode(BaseSummary):
335
335
  def kind(self) -> str:
336
336
  return self.identifier.kind
337
337
 
338
+ @property
339
+ def is_schema_node(self) -> bool:
340
+ return self.identifier.kind.startswith("Schema")
341
+
338
342
  @property
339
343
  def num_properties(self) -> int:
340
344
  return sum(a.num_properties for a in self.attributes) + sum(r.num_properties for r in self.relationships)
@@ -36,7 +36,7 @@ async def get_display_labels_per_kind(
36
36
  break
37
37
  node_map = await NodeManager.get_many(ids=limited_ids, fields=fields, db=db, branch=branch)
38
38
  for node_id, node in node_map.items():
39
- display_label_map[node_id] = await node.render_display_label(db=db)
39
+ display_label_map[node_id] = await node.get_display_label(db=db)
40
40
  offset += limit
41
41
  return display_label_map
42
42
 
@@ -201,4 +201,5 @@ CALL (target_node, definition_node){
201
201
  "target_checksum",
202
202
  "target_storage_id",
203
203
  ]
204
+ self.order_by = ["source_artifact.uuid", "target_node.uuid", "definition_node.uuid"]
204
205
  self.add_to_query(query=query)
@@ -62,6 +62,7 @@ class EnrichedDiffNodeFieldSummaryQuery(Query):
62
62
  }
63
63
  """
64
64
  self.add_to_query(query=query)
65
+ self.order_by = ["kind"]
65
66
  self.return_labels = ["kind", "attr_names", "rel_names"]
66
67
 
67
68
  async def get_field_summaries(self) -> list[NodeDiffFieldSummary]:
@@ -1 +1 @@
1
- GRAPH_VERSION = 40
1
+ GRAPH_VERSION = 43
@@ -50,7 +50,7 @@ async def get_root_node(db: InfrahubDatabase, initialize: bool = False) -> Root:
50
50
  roots = await Root.get_list(db=db)
51
51
  if len(roots) == 0 and not initialize:
52
52
  raise DatabaseError(
53
- "The Database hasn't been initialized for Infrahub, please run 'infrahub db init' or 'infrahub server start' to initialize the database."
53
+ "The Database hasn't been initialized for Infrahub, please 'infrahub server start' to initialize the database."
54
54
  )
55
55
 
56
56
  if len(roots) == 0:
@@ -137,7 +137,8 @@ async def add_indexes(db: InfrahubDatabase) -> None:
137
137
  await index_manager.add()
138
138
 
139
139
 
140
- async def initialization(db: InfrahubDatabase, add_database_indexes: bool = False) -> None:
140
+ async def initialization(db: InfrahubDatabase, add_database_indexes: bool = False) -> bool:
141
+ """Run initialization and setup, returns a boolean to indicate if it's the initial setup."""
141
142
  if config.SETTINGS.database.db_type == config.DatabaseType.MEMGRAPH:
142
143
  session = await db.session()
143
144
  await session.run(query="SET DATABASE SETTING 'log.level' TO 'INFO'")
@@ -148,6 +149,7 @@ async def initialization(db: InfrahubDatabase, add_database_indexes: bool = Fals
148
149
  # Initialize the database and Load the Root node
149
150
  # ---------------------------------------------------
150
151
  async with lock.registry.initialization():
152
+ first_time_initialization = len(await Root.get_list(db=db)) == 0
151
153
  log.debug("Checking Root Node")
152
154
  await initialize_registry(db=db, initialize=True)
153
155
 
@@ -210,6 +212,7 @@ async def initialization(db: InfrahubDatabase, add_database_indexes: bool = Fals
210
212
  ip_namespace = await get_default_ipnamespace(db=db)
211
213
  if ip_namespace:
212
214
  registry.default_ipnamespace = ip_namespace.id
215
+ return first_time_initialization
213
216
 
214
217
 
215
218
  async def create_root_node(db: InfrahubDatabase) -> Root:
@@ -152,4 +152,4 @@ class PrefixUtilizationGetter:
152
152
  grand_total_space += prefix_total_space
153
153
  if grand_total_space == 0:
154
154
  return 0.0
155
- return (grand_total_used / grand_total_space) * 100
155
+ return min((grand_total_used / grand_total_space) * 100, 100)
infrahub/core/manager.py CHANGED
@@ -60,12 +60,15 @@ def identify_node_class(node: NodeToProcess) -> type[Node]:
60
60
 
61
61
 
62
62
  def get_schema(
63
- db: InfrahubDatabase, branch: Branch, node_schema: type[SchemaProtocol] | MainSchemaTypes | str
63
+ db: InfrahubDatabase,
64
+ branch: Branch,
65
+ node_schema: type[SchemaProtocol] | MainSchemaTypes | str,
66
+ duplicate: bool = False,
64
67
  ) -> MainSchemaTypes:
65
68
  if isinstance(node_schema, str):
66
- return db.schema.get(name=node_schema, branch=branch.name)
69
+ return db.schema.get(name=node_schema, branch=branch.name, duplicate=duplicate)
67
70
  if hasattr(node_schema, "_is_runtime_protocol") and node_schema._is_runtime_protocol:
68
- return db.schema.get(name=node_schema.__name__, branch=branch.name)
71
+ return db.schema.get(name=node_schema.__name__, branch=branch.name, duplicate=duplicate)
69
72
  if not isinstance(node_schema, (MainSchemaTypes)):
70
73
  raise ValueError(f"Invalid schema provided {node_schema}")
71
74
 
@@ -1,5 +1,6 @@
1
1
  from .schema.attribute_kind_update import AttributeKindUpdateMigration
2
2
  from .schema.attribute_name_update import AttributeNameUpdateMigration
3
+ from .schema.attribute_supports_profile import AttributeSupportsProfileUpdateMigration
3
4
  from .schema.node_attribute_add import NodeAttributeAddMigration
4
5
  from .schema.node_attribute_remove import NodeAttributeRemoveMigration
5
6
  from .schema.node_kind_update import NodeKindUpdateMigration
@@ -19,6 +20,8 @@ MIGRATION_MAP: dict[str, type[SchemaMigration] | None] = {
19
20
  "attribute.name.update": AttributeNameUpdateMigration,
20
21
  "attribute.branch.update": None,
21
22
  "attribute.kind.update": AttributeKindUpdateMigration,
23
+ "attribute.optional.update": AttributeSupportsProfileUpdateMigration,
24
+ "attribute.read_only.update": AttributeSupportsProfileUpdateMigration,
22
25
  "relationship.branch.update": None,
23
26
  "relationship.direction.update": None,
24
27
  "relationship.identifier.update": PlaceholderDummyMigration,
@@ -0,0 +1,4 @@
1
+ class MigrationFailureError(Exception):
2
+ def __init__(self, errors: list[str]) -> None:
3
+ super().__init__()
4
+ self.errors = errors