Skip to content

Commit a21d73a

Browse files
committed
adds icon inheritance
1 parent 724b8da commit a21d73a

File tree

3 files changed

+102
-47
lines changed

3 files changed

+102
-47
lines changed

services/catalog/src/simcore_service_catalog/core/background_tasks.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,22 +81,24 @@ def _by_version(t: tuple[ServiceKey, ServiceVersion]) -> Version:
8181
)
8282

8383
# AUTO-UPGRADE PATCH policy
84-
inherited_access_rights = await access_rights.evaluate_auto_upgrade_policy(
85-
service_metadata=service_metadata, services_repo=services_repo
84+
inherited_data = await access_rights.inherit_from_previous_release(
85+
service_metadata=service_metadata,
86+
services_repo=services_repo,
8687
)
8788

88-
service_access_rights += inherited_access_rights
89+
service_access_rights += inherited_data["access_rights"]
8990
service_access_rights = access_rights.reduce_access_rights(
9091
service_access_rights
9192
)
9293

93-
# TODO: if icon is not set, use the icon from previous version
94+
metadata_updates = {
95+
**service_metadata.model_dump(exclude_unset=True),
96+
**inherited_data["metadata_updates"],
97+
}
9498

9599
# set the service in the DB
96100
await services_repo.create_or_update_service(
97-
ServiceMetaDataDBCreate(
98-
**service_metadata.model_dump(exclude_unset=True), owner=owner_gid
99-
),
101+
ServiceMetaDataDBCreate(**metadata_updates, owner=owner_gid),
100102
service_access_rights,
101103
)
102104

services/catalog/src/simcore_service_catalog/service/access_rights.py

Lines changed: 75 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import operator
55
from collections.abc import Callable
66
from datetime import UTC, datetime
7-
from typing import cast
7+
from typing import Any, TypedDict, cast
88

99
import arrow
1010
from fastapi import FastAPI
@@ -16,7 +16,7 @@
1616
from sqlalchemy.ext.asyncio import AsyncEngine
1717

1818
from ..api._dependencies.director import get_director_client
19-
from ..models.services_db import ServiceAccessRightsDB
19+
from ..models.services_db import ServiceAccessRightsDB, ServiceMetaDataDBGet
2020
from ..repository.groups import GroupsRepository
2121
from ..repository.services import ServicesRepository
2222
from ..utils.versioning import as_version, is_patch_release
@@ -26,6 +26,11 @@
2626
_LEGACY_SERVICES_DATE: datetime = datetime(year=2020, month=8, day=19, tzinfo=UTC)
2727

2828

29+
class InheritedData(TypedDict):
30+
access_rights: list[ServiceAccessRightsDB]
31+
metadata_updates: dict[str, Any]
32+
33+
2934
def _is_frontend_service(service: ServiceMetaDataPublished) -> bool:
3035
return "/frontend/" in service.key
3136

@@ -117,71 +122,104 @@ async def evaluate_service_ownership_and_rights(
117122
return (owner_gid, default_access_rights)
118123

119124

120-
async def evaluate_auto_upgrade_policy(
125+
async def _find_previous_compatible_release(
121126
services_repo: ServicesRepository, *, service_metadata: ServiceMetaDataPublished
122-
) -> list[ServiceAccessRightsDB]:
127+
) -> ServiceMetaDataDBGet | None:
123128
"""
124-
Evaluates the access rights for a service based on the auto-upgrade patch policy.
125-
126-
The AUTO-UPGRADE PATCH policy ensures that:
127-
- Any new patch release of a service automatically inherits the access rights from the previous compatible version.
128-
- This policy does NOT apply to frontend services.
129+
Finds the previous compatible release for a service.
129130
130131
Args:
131132
services_repo: Instance of ServicesRepository for database access.
132133
service_metadata: Metadata of the service being evaluated.
133134
134135
Returns:
135-
A list of ServiceAccessRightsDB objects representing the inherited access rights for the new patch version.
136-
Returns an empty list if the service is a frontend service or if no previous compatible version is found.
137-
138-
Notes:
139-
- The policy is described in https://github.com/ITISFoundation/osparc-simcore/issues/2244
140-
- Inheritance is only for patch releases (i.e., same major and minor version).
141-
- Future improvement: Consider making this behavior configurable in the service publication contract.
142-
136+
The previous compatible release if found, None otherwise.
143137
"""
144138
if _is_frontend_service(service_metadata):
145-
return []
139+
return None
146140

147-
service_access_rights = []
148141
new_version: Version = as_version(service_metadata.version)
149142
latest_releases = await services_repo.list_service_releases(
150143
service_metadata.key,
151144
major=new_version.major,
152145
minor=new_version.minor,
153146
)
154147

155-
previous_release = None
148+
# latest_releases is sorted from newer to older
156149
for release in latest_releases:
157-
# latest_releases is sorted from newer to older
158-
# Find the previous version that is patched by new_version
150+
# COMPATIBILITY RULE:
151+
# - a patch release is compatible with the previous patch release
159152
if is_patch_release(new_version, release.version):
160-
previous_release = release
161-
break
153+
return release
154+
155+
return None
156+
157+
158+
async def inherit_from_previous_release(
159+
services_repo: ServicesRepository, *, service_metadata: ServiceMetaDataPublished
160+
) -> InheritedData:
161+
"""
162+
Inherits metadata and access rights from a previous compatible release.
163+
164+
This function applies inheritance policies:
165+
- AUTO-UPGRADE PATCH policy: new patch releases inherit access rights from previous compatible versions
166+
- Metadata inheritance: icon and other metadata fields are inherited if not specified in the new version
167+
168+
Args:
169+
services_repo: Instance of ServicesRepository for database access.
170+
service_metadata: Metadata of the service being evaluated.
171+
172+
Returns:
173+
An InheritedData object containing:
174+
- access_rights: List of ServiceAccessRightsDB objects inherited from the previous release
175+
- metadata_updates: Dict of metadata fields that should be updated in the new service
176+
177+
Notes:
178+
- The policy is described in https://github.com/ITISFoundation/osparc-simcore/issues/2244
179+
- Inheritance is only for patch releases (i.e., same major and minor version).
180+
"""
181+
inherited_data: InheritedData = {
182+
"access_rights": [],
183+
"metadata_updates": {},
184+
}
185+
186+
previous_release = await _find_previous_compatible_release(
187+
services_repo, service_metadata=service_metadata
188+
)
189+
190+
if not previous_release:
191+
return inherited_data
162192

163-
if previous_release:
164-
previous_access_rights = await services_repo.get_service_access_rights(
165-
previous_release.key, previous_release.version
193+
# 1. ACCESS-RIGHTS:
194+
# Inherit access rights
195+
previous_access_rights = await services_repo.get_service_access_rights(
196+
previous_release.key, previous_release.version
197+
)
198+
199+
inherited_data["access_rights"] = [
200+
access.model_copy(
201+
update={"version": service_metadata.version},
202+
deep=True,
166203
)
204+
for access in previous_access_rights
205+
]
167206

168-
service_access_rights = [
169-
access.model_copy(
170-
update={"version": service_metadata.version},
171-
deep=True,
172-
)
173-
for access in previous_access_rights
174-
]
175-
return service_access_rights
207+
# 2. METADATA:
208+
# Inherit icon if not specified in the new service
209+
if not service_metadata.icon and previous_release.icon:
210+
inherited_data["metadata_updates"]["icon"] = previous_release.icon
211+
212+
return inherited_data
176213

177214

178215
def reduce_access_rights(
179216
access_rights: list[ServiceAccessRightsDB],
180217
reduce_operation: Callable = operator.ior,
181218
) -> list[ServiceAccessRightsDB]:
182-
"""
183-
Reduces a list of access-rights per target
219+
"""Reduces a list of access-rights per target
220+
184221
By default, the reduction is OR (i.e. preserves True flags)
222+
185223
"""
186224
# TODO: probably a lot of room to optimize
187225
# helper functions to simplify operation of access rights

services/catalog/tests/unit/with_dbs/test_service_access_rights.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from simcore_service_catalog.service.access_rights import (
1717
evaluate_auto_upgrade_policy,
1818
evaluate_service_ownership_and_rights,
19+
inherit_from_previous_release,
1920
reduce_access_rights,
2021
)
2122
from sqlalchemy.ext.asyncio import AsyncEngine
@@ -120,6 +121,7 @@ async def test_auto_upgrade_policy(
120121
ServiceMetaDataPublished.model_json_schema()["examples"][MOST_UPDATED_EXAMPLE]
121122
)
122123
new_service_metadata.version = TypeAdapter(ServiceVersion).validate_python("1.0.11")
124+
new_service_metadata.icon = None # Remove icon to test inheritance
123125

124126
# we have three versions of the service in the database for which the sorting matters: (1.0.11 should inherit from 1.0.10 not 1.0.9)
125127
await services_db_tables_injector(
@@ -146,6 +148,7 @@ async def test_auto_upgrade_policy(
146148
team_access="x",
147149
everyone_access=None,
148150
product=target_product,
151+
icon="previous_icon.svg",
149152
),
150153
create_fake_service_data(
151154
new_service_metadata.key,
@@ -182,18 +185,30 @@ async def test_auto_upgrade_policy(
182185
}
183186
assert service_access_rights[0].product_name == target_product
184187

185-
# AUTO-UPGRADE PATCH policy
186-
inherited_access_rights = await evaluate_auto_upgrade_policy(
187-
new_service_metadata, services_repo
188+
# Inheritance policy (both access rights and metadata)
189+
inherited_data = await inherit_from_previous_release(
190+
services_repo, service_metadata=new_service_metadata
188191
)
189192

193+
# Check access rights inheritance
194+
inherited_access_rights = inherited_data["access_rights"]
190195
assert len(inherited_access_rights) == 4
191196
assert {a.gid for a in inherited_access_rights} == {team_gid, owner_gid}
192197
assert {a.product_name for a in inherited_access_rights} == {
193198
target_product,
194199
other_product,
195200
}
196201

202+
# Check metadata inheritance
203+
assert "icon" in inherited_data["metadata_updates"]
204+
assert inherited_data["metadata_updates"]["icon"] == "previous_icon.svg"
205+
206+
# Test backward compatibility with evaluate_auto_upgrade_policy
207+
legacy_result = await evaluate_auto_upgrade_policy(
208+
services_repo, service_metadata=new_service_metadata
209+
)
210+
assert legacy_result["access_rights"] == inherited_data["access_rights"]
211+
197212
# ALL
198213
service_access_rights += inherited_access_rights
199214
service_access_rights = reduce_access_rights(service_access_rights)

0 commit comments

Comments
 (0)