Skip to content

Support converting repository type #6469

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions backend/infrahub/api/artifact.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
)
from infrahub.core import registry
from infrahub.core.account import ObjectPermission
from infrahub.core.branch.enums import BranchStatus
from infrahub.core.branch.needs_rebase_status import raise_needs_rebase_error
from infrahub.core.constants import GLOBAL_BRANCH_NAME, InfrahubKind, PermissionAction
from infrahub.core.protocols import CoreArtifactDefinition
from infrahub.database import InfrahubDatabase # noqa: TC001
Expand Down Expand Up @@ -74,6 +76,9 @@ async def generate_artifact(
permission_manager: PermissionManager = Depends(get_permission_manager),
context: InfrahubContext = Depends(get_context),
) -> None:
if branch_params.branch.status == BranchStatus.NEED_REBASE:
raise_needs_rebase_error(branch_name=branch_params.branch.name)

permission_decision = (
PermissionDecisionFlag.ALLOW_DEFAULT
if branch_params.branch.name in (GLOBAL_BRANCH_NAME, registry.default_branch)
Expand Down
2 changes: 2 additions & 0 deletions backend/infrahub/api/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
GRAPHQL_RESPONSE_SIZE_METRICS,
GRAPHQL_TOP_LEVEL_QUERIES_METRICS,
)
from infrahub.graphql.middleware import raise_on_mutation_on_branch_needing_rebase
from infrahub.graphql.utils import extract_data
from infrahub.groups.models import RequestGraphQLQueryGroupUpdate
from infrahub.log import get_logger
Expand Down Expand Up @@ -98,6 +99,7 @@ async def execute_query(
context_value=gql_params.context,
root_value=None,
variable_values=params,
middleware=[raise_on_mutation_on_branch_needing_rebase],
)

data = extract_data(query_name=gql_query.name.value, result=result)
Expand Down
5 changes: 5 additions & 0 deletions backend/infrahub/api/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
from infrahub.core import registry
from infrahub.core.account import GlobalPermission
from infrahub.core.branch import Branch # noqa: TC001
from infrahub.core.branch.enums import BranchStatus
from infrahub.core.branch.needs_rebase_status import raise_needs_rebase_error
from infrahub.core.constants import GLOBAL_BRANCH_NAME, GlobalPermissions, PermissionDecision
from infrahub.core.migrations.schema.models import SchemaApplyMigrationData
from infrahub.core.models import ( # noqa: TC001
Expand Down Expand Up @@ -286,6 +288,9 @@ async def load_schema(
permission_manager: PermissionManager = Depends(get_permission_manager),
context: InfrahubContext = Depends(get_context),
) -> SchemaUpdate:
if branch.status == BranchStatus.NEED_REBASE:
raise_needs_rebase_error(branch_name=branch.name)

permission_manager.raise_for_permission(
permission=GlobalPermission(
action=GlobalPermissions.MANAGE_SCHEMA.value,
Expand Down
7 changes: 7 additions & 0 deletions backend/infrahub/core/branch/enums.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from infrahub.utils import InfrahubStringEnum


class BranchStatus(InfrahubStringEnum):
ACTIVE = "ACTIVE"
NEED_REBASE = "NEED_REBASE"
CLOSED = "CLOSED"
2 changes: 1 addition & 1 deletion backend/infrahub/core/branch/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class Branch(StandardNode):
name: str = Field(
max_length=250, min_length=3, description="Name of the branch (git ref standard)", validate_default=True
)
status: str = "OPEN" # OPEN, CLOSED
status: str = "OPEN" # OPEN, CLOSED, NEED_REBASE
description: str = ""
origin_branch: str = "main"
branched_from: Optional[str] = Field(default=None, validate_default=True)
Expand Down
2 changes: 2 additions & 0 deletions backend/infrahub/core/branch/needs_rebase_status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def raise_needs_rebase_error(branch_name: str) -> None:
raise ValueError(f"Branch {branch_name} should be rebased before performing other operation on it")
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

from pydantic import BaseModel

from infrahub.core import registry
from infrahub.core.attribute import BaseAttribute
from infrahub.core.branch import Branch
from infrahub.core.constants import RelationshipCardinality
from infrahub.core.branch.enums import BranchStatus
from infrahub.core.constants import GLOBAL_BRANCH_NAME, BranchSupportType, RelationshipCardinality
from infrahub.core.manager import NodeManager
from infrahub.core.node import Node
from infrahub.core.node.create import create_node
Expand Down Expand Up @@ -87,8 +89,25 @@ async def get_unidirectional_rels_peers_ids(node: Node, branch: Branch, db: Infr
return query.get_peers_uuids()


async def _get_other_active_branches(db: InfrahubDatabase) -> list[Branch]:
branches = await Branch.get_list(db=db)
return [branch for branch in branches if branch.name not in [GLOBAL_BRANCH_NAME, registry.default_branch]]


def _has_pass_thru_aware_attributes(node_schema: NodeSchema, mapping: dict[str, InputForDestField]) -> bool:
aware_attributes = [attr for attr in node_schema.attributes if attr.branch != BranchSupportType.AGNOSTIC]
aware_attributes_pass_thru = [
attr.name for attr in aware_attributes if attr.name in mapping and mapping[attr.name].source_field is not None
]
return len(aware_attributes_pass_thru) > 0


async def convert_object_type(
node: Node, target_schema: NodeSchema, mapping: dict[str, InputForDestField], branch: Branch, db: InfrahubDatabase
node: Node,
target_schema: NodeSchema,
mapping: dict[str, InputForDestField],
branch: Branch,
db: InfrahubDatabase,
) -> Node:
"""Delete the node and return the new created one. If creation fails, the node is not deleted, and raise an error.
An extra check is performed on input node peers relationships to make sure they are still valid."""
Expand All @@ -108,6 +127,22 @@ async def convert_object_type(
raise ValueError(f"Deleted {len(deleted_nodes)} nodes instead of 1")

data_new_node = await build_data_new_node(dbt, mapping, node)

if node_schema.branch == BranchSupportType.AGNOSTIC and _has_pass_thru_aware_attributes(
node_schema=node_schema, mapping=mapping
):
if branch.name != "main":
raise ValueError(
f"Conversion of {node_schema.kind} is not allowed on branch {branch.name} because it is agnostic and has aware attributes"
)

# When converting an agnostic node with aware attributes, we need to put other branches in NEED_REBASE state
# as aware attributes do not exist in other branches after conversion
other_branches = await _get_other_active_branches(db=dbt)
for br in other_branches:
br.status = BranchStatus.NEED_REBASE
await br.save(db=dbt)

node_created = await create_node(
data=data_new_node,
db=dbt,
Expand All @@ -121,4 +156,4 @@ async def convert_object_type(
for peer in peers.values():
peer.validate_relationships()

return node_created
return node_created
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from infrahub import lock
from infrahub.core.branch import Branch
from infrahub.core.convert_object_type.object_conversion import InputForDestField, convert_object_type
from infrahub.core.manager import NodeManager
from infrahub.core.node import Node
from infrahub.core.repositories.create_repository import RepositoryFinalizer
from infrahub.core.schema import NodeSchema
from infrahub.database import InfrahubDatabase


async def convert_repository_type(
node: Node,
target_schema: NodeSchema,
mapping: dict[str, InputForDestField],
branch: Branch,
db: InfrahubDatabase,
repository_post_creator: RepositoryFinalizer,
) -> Node:
"""Delete the node and return the new created one. If creation fails, the node is not deleted, and raise an error.
An extra check is performed on input node peers relationships to make sure they are still valid."""

repo_name = node.name.value # type: ignore [attr-defined]
async with lock.registry.get(name=repo_name, namespace="repository"):
node_created = await convert_object_type(
node=node,
target_schema=target_schema,
mapping=mapping,
branch=branch,
db=db,
)

# We can't apply post creation steps within `convert_object_type` transaction as a sdk call tries to fetch the node
# created within the transaction from a different process, therefore that would not run within this transaction
# so the node ends up being not found
await repository_post_creator.post_create(
branch=branch,
obj=node_created, # type: ignore
db=db,
)

# Delete the RepositoryGroup associated with the old repository, as a new one was created for the new repository.
repository_groups = (await node.groups_objects.get_peers(db=db)).values() # type: ignore [attr-defined]
for repository_group in repository_groups:
await NodeManager.delete(db=db, branch=branch, nodes=[repository_group], cascade_delete=False)

return node_created
7 changes: 4 additions & 3 deletions backend/infrahub/core/initialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from infrahub.constants.database import DatabaseType
from infrahub.core import registry
from infrahub.core.branch import Branch
from infrahub.core.branch.enums import BranchStatus
from infrahub.core.constants import (
DEFAULT_IP_NAMESPACE,
GLOBAL_BRANCH_NAME,
Expand Down Expand Up @@ -224,7 +225,7 @@ async def create_root_node(db: InfrahubDatabase) -> Root:
async def create_default_branch(db: InfrahubDatabase) -> Branch:
branch = Branch(
name=registry.default_branch,
status="OPEN",
status=BranchStatus.ACTIVE.value,
description="Default Branch",
hierarchy_level=1,
is_default=True,
Expand All @@ -241,7 +242,7 @@ async def create_default_branch(db: InfrahubDatabase) -> Branch:
async def create_global_branch(db: InfrahubDatabase) -> Branch:
branch = Branch(
name=GLOBAL_BRANCH_NAME,
status="OPEN",
status=BranchStatus.ACTIVE.value,
description="Global Branch",
hierarchy_level=1,
is_global=True,
Expand All @@ -264,7 +265,7 @@ async def create_branch(
description = description or f"Branch {branch_name}"
branch = Branch(
name=branch_name,
status="OPEN",
status=BranchStatus.ACTIVE.value,
hierarchy_level=2,
description=description,
is_default=False,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from typing import TYPE_CHECKING, Any, Sequence

from infrahub.core.branch import Branch
from infrahub.core.branch.enums import BranchStatus
from infrahub.core.constants import GLOBAL_BRANCH_NAME, BranchSupportType, InfrahubKind
from infrahub.core.migrations.shared import MigrationResult
from infrahub.core.query import Query, QueryType
Expand All @@ -20,7 +21,7 @@

global_branch = Branch(
name=GLOBAL_BRANCH_NAME,
status="OPEN",
status=BranchStatus.ACTIVE.value,
description="Global Branch",
hierarchy_level=1,
is_global=True,
Expand All @@ -29,7 +30,7 @@

default_branch = Branch(
name="main",
status="OPEN",
status=BranchStatus.ACTIVE.value,
description="Default Branch",
hierarchy_level=1,
is_global=False,
Expand Down Expand Up @@ -105,7 +106,7 @@ def __init__(self, **kwargs: Any):

branch = Branch(
name=GLOBAL_BRANCH_NAME,
status="OPEN",
status=BranchStatus.ACTIVE.value,
description="Global Branch",
hierarchy_level=1,
is_global=True,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from typing import TYPE_CHECKING, Any, Sequence

from infrahub.core.branch import Branch
from infrahub.core.branch.enums import BranchStatus
from infrahub.core.constants import (
GLOBAL_BRANCH_NAME,
BranchSupportType,
Expand All @@ -23,7 +24,7 @@

default_branch = Branch(
name="main",
status="OPEN",
status=BranchStatus.ACTIVE.value,
description="Default Branch",
hierarchy_level=1,
is_global=False,
Expand All @@ -42,7 +43,7 @@ async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> No

global_branch = Branch(
name=GLOBAL_BRANCH_NAME,
status="OPEN",
status=BranchStatus.ACTIVE.value,
description="Global Branch",
hierarchy_level=1,
is_global=True,
Expand Down Expand Up @@ -176,7 +177,7 @@ async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> No

global_branch = Branch(
name=GLOBAL_BRANCH_NAME,
status="OPEN",
status=BranchStatus.ACTIVE.value,
description="Global Branch",
hierarchy_level=1,
is_global=True,
Expand Down
Loading
Loading