Skip to content

Commit

Permalink
Merge pull request #376 from ynput/375-addons-auto-update-server-addons
Browse files Browse the repository at this point in the history
Addons: server addon auto-update
  • Loading branch information
martastain authored Oct 8, 2024
2 parents e98aa69 + 00a2eef commit b10cc16
Show file tree
Hide file tree
Showing 8 changed files with 596 additions and 297 deletions.
36 changes: 2 additions & 34 deletions api/addons/install.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import hashlib
from datetime import datetime
from typing import Literal

Expand All @@ -10,6 +9,7 @@
from ayon_server.constraints import Constraints
from ayon_server.events import dispatch_event, update_event
from ayon_server.exceptions import ForbiddenException
from ayon_server.helpers.download_addon import download_addon
from ayon_server.installer import background_installer
from ayon_server.installer.addons import get_addon_zip_info
from ayon_server.lib.postgres import Postgres
Expand Down Expand Up @@ -43,39 +43,7 @@ async def upload_addon_zip_file(
raise ForbiddenException("Only admins can install addons")

if url:
hash = hashlib.sha256(f"addon_install_{url}".encode()).hexdigest()

query = """
SELECT id FROM events
WHERE topic = 'addon.install_from_url'
AND hash = $1
"""

summary = {"url": url}
if addonName and addonVersion:
summary["name"] = addonName
summary["version"] = addonVersion

res = await Postgres.fetch(query, hash)
if res:
event_id = res[0]["id"]
await update_event(
event_id,
description="Reinstalling addon from URL",
summary=summary,
status="pending",
)
else:
event_id = await dispatch_event(
"addon.install_from_url",
hash=hash,
description="Installing addon from URL",
summary=summary,
user=user.name,
finished=False,
)

await background_installer.enqueue(event_id)
event_id = await download_addon(url, addonName, addonVersion)
return InstallAddonResponseModel(event_id=event_id)

# Store the zip file in a temporary location
Expand Down
265 changes: 2 additions & 263 deletions api/bundles/migration.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
__all__ = ["migrate_settings"]

from typing import Any

from nxtools import logging

from ayon_server.addons.addon import BaseServerAddon
from ayon_server.addons.library import AddonLibrary
from ayon_server.config import ayonconfig
from ayon_server.events import EventStream
from ayon_server.exceptions import NotFoundException
from ayon_server.helpers.project_list import get_project_list
from ayon_server.helpers.migrate_addon_settings import migrate_addon_settings
from ayon_server.lib.postgres import Connection, Postgres

AddonVersionsDict = dict[str, str]
Expand Down Expand Up @@ -51,263 +47,6 @@ async def _get_bundles_addons(
return source_addons, target_addons


async def _migrate_addon_settings(
source_addon: BaseServerAddon,
target_addon: BaseServerAddon,
source_variant: str,
target_variant: str,
with_projects: bool,
conn: Connection,
) -> list[dict[str, Any]]:
"""Migrate settings from source to target addon.
Returns a list of events that were created during migration.
"""

# Studio settings

# Load studio settings from source addon converted to the target version model
new_studio_overrides: dict[str, Any]
new_studio_overrides = await source_addon.get_studio_overrides(
variant=source_variant,
as_version=target_addon.version,
)

events: list[dict[str, Any]] = []
event_head = f"{target_addon.name} {target_addon.version} {target_variant}"

event_created = False
event_payload = {}

if new_studio_overrides:
# fetch the original studio settings
res = await conn.fetch(
"""
SELECT data FROM settings
WHERE addon_name = $1 AND addon_version = $2 AND variant = $3
""",
target_addon.name,
target_addon.version,
target_variant,
)

do_copy = False

if res:
original_data = res[0]["data"]
if original_data != new_studio_overrides:
do_copy = True
if ayonconfig.audit_trail:
event_payload["originalValue"] = original_data
event_payload["newValue"] = new_studio_overrides
else:
do_copy = True
if ayonconfig.audit_trail:
event_payload["originalValue"] = {}
event_payload["newValue"] = new_studio_overrides

if do_copy:
event_created = True
event_description = "studio overrides changed during migration"

await conn.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
""",
target_addon.name,
target_addon.version,
target_variant,
new_studio_overrides,
)
else:
res = await conn.fetch(
"""
DELETE FROM settings
WHERE addon_name = $1 AND addon_version = $2 AND variant = $3
RETURNING data
""",
target_addon.name,
target_addon.version,
target_variant,
)
if res:
event_created = True
event_description = "studio overrides removed during migration"
if ayonconfig.audit_trail:
event_payload = {"originalValue": res[0]["data"], "newValue": {}}

if event_created:
events.append(
{
"description": f"{event_head} {event_description}",
"summary": {
"addon_name": target_addon.name,
"addon_version": target_addon.version,
"variant": target_variant,
},
"payload": event_payload,
}
)

if not with_projects:
return events

# Project settings

project_names = [project.name for project in await get_project_list()]

for project_name in project_names:
event_created = False
event_payload = {}

# Load project settings from source addon converted to the target version model
new_project_overrides: dict[str, Any]
new_project_overrides = await source_addon.get_project_overrides(
project_name=project_name,
variant=source_variant,
as_version=target_addon.version,
)

if new_project_overrides:
# fetch the original project settings
res = await conn.fetch(
f"""
SELECT data
FROM project_{project_name}.settings
WHERE addon_name = $1 AND addon_version = $2 AND variant = $3
""",
target_addon.name,
target_addon.version,
target_variant,
)

do_copy = False

if res:
original_data = res[0]["data"]
if original_data != new_project_overrides:
do_copy = True
if ayonconfig.audit_trail:
event_payload["originalValue"] = original_data
event_payload["newValue"] = new_project_overrides
else:
do_copy = True
if ayonconfig.audit_trail:
event_payload["originalValue"] = {}
event_payload["newValue"] = new_project_overrides

if do_copy:
event_created = True
event_description = "project overrides changed during migration"

await conn.execute(
f"""
INSERT INTO project_{project_name}.settings
(addon_name, addon_version, variant, data)
VALUES ($1, $2, $3, $4)
ON CONFLICT (addon_name, addon_version, variant)
DO UPDATE SET data = $4
""",
target_addon.name,
target_addon.version,
target_variant,
new_project_overrides,
)
else:
res = await conn.fetch(
f"""
DELETE FROM project_{project_name}.settings
WHERE addon_name = $1 AND addon_version = $2 AND variant = $3
RETURNING data
""",
target_addon.name,
target_addon.version,
target_variant,
)

if res:
event_created = True
event_description = "project overrides removed during migration"
if ayonconfig.audit_trail:
event_payload = {"originalValue": res[0]["data"], "newValue": {}}

if event_created:
events.append(
{
"description": f"{event_head}: {event_description}",
"summary": {
"addon_name": target_addon.name,
"addon_version": target_addon.version,
"variant": target_variant,
},
"project": project_name,
"payload": event_payload,
}
)

# Project site settings

site_info = await conn.fetch(
f"""
SELECT site_id, user_name, data
FROM project_{project_name}.project_site_settings
WHERE addon_name = $1 AND addon_version = $2
""",
source_addon.name,
source_addon.version,
)
for row in site_info:
if not row["data"]:
continue
site_id, user_name = row["site_id"], row["user_name"]

# Load project site settings from source addon
# converted to the target version model

new_site_overrides: dict[str, Any]
new_site_overrides = await source_addon.get_project_site_overrides(
project_name=project_name,
site_id=site_id,
user_name=user_name,
as_version=target_addon.version,
)

if new_site_overrides:
await conn.execute(
f"""
INSERT INTO project_{project_name}.project_site_settings
(addon_name, addon_version, site_id, user_name, data)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (addon_name, addon_version, site_id, user_name)
DO UPDATE SET data = $5
""",
target_addon.name,
target_addon.version,
site_id,
user_name,
new_site_overrides,
)
else:
await conn.execute(
f"""
DELETE FROM project_{project_name}.project_site_settings
WHERE addon_name = $1
AND addon_version = $2
AND site_id = $3
AND user_name = $4
""",
target_addon.name,
target_addon.version,
site_id,
user_name,
)

return events


async def _migrate_settings_by_bundle(
source_bundle: str,
target_bundle: str,
Expand Down Expand Up @@ -353,7 +92,7 @@ async def _migrate_settings_by_bundle(

# perform migration of addon settings

events = await _migrate_addon_settings(
events = await migrate_addon_settings(
source_addon,
target_addon,
source_variant,
Expand Down
Loading

0 comments on commit b10cc16

Please sign in to comment.