Skip to content
Open
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
70 changes: 26 additions & 44 deletions api/addons/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@

from typing import Literal

import semver
from fastapi import Response

from ayon_server.addons.library import AddonLibrary
from ayon_server.api.dependencies import CurrentUser
from ayon_server.exceptions import (
AyonException,
BadRequestException,
ForbiddenException,
NotFoundException,
)
from ayon_server.helpers.migrate_addon_settings import migrate_addon_settings
from ayon_server.lib.postgres import Postgres
from ayon_server.types import Field, OPModel

Expand All @@ -28,7 +30,7 @@ async def copy_addon_variant(
addon_name: str,
copy_from: AddonEnvironment,
copy_to: AddonEnvironment,
project_name: str | None = None,
with_project_overrides: bool = False,
):
"""Copy addon settings from one variant to another."""

Expand All @@ -39,51 +41,25 @@ async def copy_addon_variant(
raise AyonException("Addon environment not found")

source_version = res[0][f"{copy_from}_version"]
target_version = res[0][f"{copy_to}_version"]

if not source_version:
raise AyonException("Source environment not set")

# Get the settings

source_settings = await Postgres.fetch(
"""
SELECT addon_version, data FROM settings
WHERE addon_name = $1 AND variant = $2
""",
addon_name,
copy_from,
)

if source_version not in [x["addon_version"] for x in source_settings]:
source_settings.append({"addon_version": source_version, "data": {}})

source_settings.sort(
key=lambda x: semver.VersionInfo.parse(x["addon_version"]),
reverse=True,
)

target_settings = {}

for settings in source_settings:
target_settings = settings["data"]
if settings["addon_version"] == source_version:
break

# store the settings

# TODO: Emit change event

await Postgres.execute(
"""
INSERT INTO settings (addon_name, addon_version, variant, data)
VALUES ($1, $2, $3, $4)
ON CONFLICT (addon_name, addon_version, variant) DO UPDATE
SET data = $4
""",
addon_name,
source_version,
copy_to,
target_settings,
if not target_version:
raise AyonException("Target environment not set")

try:
source_addon = AddonLibrary.addon(addon_name, source_version)
target_addon = AddonLibrary.addon(addon_name, target_version)
except NotFoundException as exc:
raise AyonException(str(exc)) from exc

await migrate_addon_settings(
source_addon,
target_addon,
source_variant=copy_from,
target_variant=copy_to,
with_projects=with_project_overrides,
)


Expand All @@ -99,6 +75,11 @@ class VariantCopyRequest(OPModel):
description="Destination variant",
example="staging",
)
with_project_overrides: bool = Field(
False,
description="Also copy project overrides for the addon",
example=False,
)


class AddonConfigRequest(OPModel):
Expand All @@ -118,6 +99,7 @@ async def configure_addons(
addon_name=payload.copy_variant.addon_name,
copy_from=payload.copy_variant.copy_from,
copy_to=payload.copy_variant.copy_to,
with_project_overrides=payload.copy_variant.with_project_overrides,
)
return Response(status_code=204)

Expand Down
107 changes: 107 additions & 0 deletions tests/copy_addon_variant_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import asyncio
import os
import sys

sys.path.append(os.path.join(os.path.dirname(__file__), ".."))

from api.addons import configuration


def test_copy_addon_variant_uses_target_version_and_skips_projects_by_default():
calls: list[tuple] = []

async def fake_fetch(query, addon_name):
assert addon_name == "example"
return [{"production_version": "1.0.0", "staging_version": "2.0.0"}]

def fake_addon(addon_name, addon_version):
return (addon_name, addon_version)

async def fake_migrate(
source_addon,
target_addon,
source_variant,
target_variant,
with_projects,
):
calls.append(
(
source_addon,
target_addon,
source_variant,
target_variant,
with_projects,
)
)
return []

original_fetch = configuration.Postgres.fetch
original_addon = configuration.AddonLibrary.addon
original_migrate = configuration.migrate_addon_settings
configuration.Postgres.fetch = fake_fetch
configuration.AddonLibrary.addon = fake_addon
configuration.migrate_addon_settings = fake_migrate
try:
asyncio.run(
configuration.copy_addon_variant(
addon_name="example",
copy_from="production",
copy_to="staging",
)
)
finally:
configuration.Postgres.fetch = original_fetch
configuration.AddonLibrary.addon = original_addon
configuration.migrate_addon_settings = original_migrate

assert calls == [
(
("example", "1.0.0"),
("example", "2.0.0"),
"production",
"staging",
False,
)
]


def test_copy_addon_variant_can_include_project_overrides():
calls: list[bool] = []

async def fake_fetch(query, addon_name):
return [{"production_version": "1.2.0", "staging_version": "1.3.0"}]

def fake_addon(addon_name, addon_version):
return {"name": addon_name, "version": addon_version}

async def fake_migrate(
source_addon,
target_addon,
source_variant,
target_variant,
with_projects,
):
calls.append(with_projects)
return []

original_fetch = configuration.Postgres.fetch
original_addon = configuration.AddonLibrary.addon
original_migrate = configuration.migrate_addon_settings
configuration.Postgres.fetch = fake_fetch
configuration.AddonLibrary.addon = fake_addon
configuration.migrate_addon_settings = fake_migrate
try:
asyncio.run(
configuration.copy_addon_variant(
addon_name="example",
copy_from="production",
copy_to="staging",
with_project_overrides=True,
)
)
finally:
configuration.Postgres.fetch = original_fetch
configuration.AddonLibrary.addon = original_addon
configuration.migrate_addon_settings = original_migrate

assert calls == [True]