Skip to content
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

NAS-132505 / 25.04 / Remove accepts style decorator from catalog plugin #14951

Merged
merged 1 commit into from
Nov 20, 2024
Merged
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
2 changes: 2 additions & 0 deletions src/middlewared/middlewared/api/v25_04_0/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
from .alert import * # noqa
from .alertservice import * # noqa
from .api_key import * # noqa
from .app import * # noqa
from .auth import * # noqa
from .boot_environments import * # noqa
from .catalog import * # noqa
from .cloud_sync import * # noqa
from .common import * # noqa
from .core import * # noqa
Expand Down
31 changes: 31 additions & 0 deletions src/middlewared/middlewared/api/v25_04_0/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from middlewared.api.base import BaseModel, NonEmptyString

from .catalog import CatalogAppInfo


__all__ = [
'AppCategoriesArgs', 'AppCategoriesResult', 'AppSimilarArgs', 'AppSimilarResult', 'AppAvailableResponse',
]


class AppAvailableResponse(CatalogAppInfo):
catalog: NonEmptyString
installed: bool
train: NonEmptyString


class AppCategoriesArgs(BaseModel):
pass


class AppCategoriesResult(BaseModel):
result: list[NonEmptyString]


class AppSimilarArgs(BaseModel):
app_name: NonEmptyString
train: NonEmptyString


class AppSimilarResult(BaseModel):
result: list[AppAvailableResponse]
123 changes: 123 additions & 0 deletions src/middlewared/middlewared/api/v25_04_0/catalog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
from datetime import datetime

from pydantic import ConfigDict, Field, RootModel

from middlewared.api.base import BaseModel, ForUpdateMetaclass, NonEmptyString, single_argument_args, LongString


__all__ = [
'CatalogEntry', 'CatalogUpdateArgs', 'CatalogUpdateResult', 'CatalogTrainsArgs', 'CatalogTrainsResult',
'CatalogSyncArgs', 'CatalogSyncResult', 'CatalogAppInfo', 'CatalogAppsArgs', 'CatalogAppsResult',
'CatalogAppDetailsArgs', 'CatalogAppDetailsResult',
]


class CatalogEntry(BaseModel):
id: NonEmptyString
label: NonEmptyString = Field(pattern=r'^\w+[\w.-]*$')
preferred_trains: list[NonEmptyString]


@single_argument_args('catalog_update')
class CatalogUpdateArgs(BaseModel, metaclass=ForUpdateMetaclass):
preferred_trains: list[NonEmptyString]


class CatalogUpdateResult(BaseModel):
result: CatalogEntry


class CatalogTrainsArgs(BaseModel):
pass


class CatalogTrainsResult(BaseModel):
result: list[NonEmptyString]


class CatalogSyncArgs(BaseModel):
pass


class CatalogSyncResult(BaseModel):
result: None


class Maintainer(BaseModel):
name: str
email: str
url: str | None


class CatalogAppInfo(BaseModel):
app_readme: LongString | None
'''HTML content of the app README.'''
categories: list[str]
'''List of categories for the app.'''
description: str
'''Short description of the app.'''
healthy: bool
'''Health status of the app.'''
healthy_error: str | None = None
'''Error if app is not healthy.'''
home: str
'''Homepage URL of the app.'''
location: str
'''Local path to the app's location.'''
latest_version: str | None
'''Latest available app version.'''
latest_app_version: str | None
'''Latest available app version in repository.'''
latest_human_version: str | None
'''Human-readable version of the app.'''
last_update: datetime | None
'''Timestamp of the last update in ISO format.'''
name: str
'''Name of the app.'''
recommended: bool
'''Indicates if the app is recommended.'''
title: str
'''Title of the app.'''
maintainers: list[Maintainer]
'''List of app maintainers.'''
tags: list[str]
'''Tags associated with the app.'''
screenshots: list[str]
'''List of screenshot URLs.'''
sources: list[str]
'''List of source URLs.'''
icon_url: str | None = None
'''URL of the app icon'''

# We do this because if we change anything in catalog.json, even older releases will
# get this new field and different roles will start breaking due to this
model_config = ConfigDict(extra='allow')


@single_argument_args('catalog_apps_options')
class CatalogAppsArgs(BaseModel):
cache: bool = True
cache_only: bool = False
retrieve_all_trains: bool = True
trains: list[NonEmptyString] = Field(default_factory=list)


class CatalogTrainInfo(RootModel[dict[str, CatalogAppInfo]]):
pass


class CatalogAppsResult(BaseModel):
result: dict[str, CatalogTrainInfo]


class CatalogAppVersionDetails(BaseModel):
train: NonEmptyString


class CatalogAppDetailsArgs(BaseModel):
app_name: NonEmptyString
app_version_details: CatalogAppVersionDetails


class CatalogAppDetailsResult(BaseModel):
result: CatalogAppInfo
35 changes: 3 additions & 32 deletions src/middlewared/middlewared/plugins/catalog/app_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

from catalog_reader.train_utils import get_train_path

from middlewared.schema import accepts, Bool, Dict, List, returns, Str
from middlewared.api import api_method
from middlewared.api.current import CatalogAppDetailsArgs, CatalogAppDetailsResult
from middlewared.service import CallError, Service

from .apps_util import get_app_details
Expand All @@ -15,37 +16,7 @@ class CatalogService(Service):
class Config:
cli_namespace = 'app.catalog'

@accepts(
Str('app_name'),
Dict(
'app_version_details',
Str('train', required=True),
),
roles=['CATALOG_READ'],
)
@returns(Dict(
# TODO: Make sure keys here are mapped appropriately
'app_details',
Str('name', required=True),
List('categories', items=[Str('category')], required=True),
List('maintainers', required=True),
List('tags', required=True),
List('screenshots', required=True, items=[Str('screenshot')]),
List('sources', required=True, items=[Str('source')]),
Str('app_readme', null=True, required=True),
Str('location', required=True),
Bool('healthy', required=True),
Bool('recommended', required=True),
Str('healthy_error', required=True, null=True),
Str('healthy_error', required=True, null=True),
Dict('versions', required=True, additional_attrs=True),
Str('latest_version', required=True, null=True),
Str('latest_app_version', required=True, null=True),
Str('latest_human_version', required=True, null=True),
Str('last_update', required=True, null=True),
Str('icon_url', required=True, null=True),
Str('home', required=True),
))
@api_method(CatalogAppDetailsArgs, CatalogAppDetailsResult, roles=['CATALOG_READ'])
def get_app_details(self, app_name, options):
"""
Retrieve information of `app_name` `app_version_details.catalog` catalog app.
Expand Down
52 changes: 11 additions & 41 deletions src/middlewared/middlewared/plugins/catalog/apps.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
from middlewared.schema import accepts, Bool, Datetime, Dict, List, Ref, returns, Str
from middlewared.service import filterable, filterable_returns, Service
from middlewared.api import api_method
from middlewared.api.current import (
AppCategoriesArgs, AppCategoriesResult, AppAvailableResponse,
)
from middlewared.api.v25_04_0 import AppSimilarArgs, AppSimilarResult
from middlewared.service import filterable_api_method, Service
from middlewared.utils import filter_list


Expand All @@ -8,8 +12,7 @@ class AppService(Service):
class Config:
cli_namespace = 'app'

@filterable(roles=['CATALOG_READ'])
@filterable_returns(Ref('available_apps'))
@filterable_api_method(item=AppAvailableResponse, roles=['CATALOG_READ'])
async def latest(self, filters, options):
"""
Retrieve latest updated apps.
Expand All @@ -22,38 +25,7 @@ async def latest(self, filters, options):
), filters, options
)

@filterable(roles=['CATALOG_READ'])
@filterable_returns(Dict(
'available_apps',
Bool('healthy', required=True),
Bool('installed', required=True),
Bool('recommended', required=True),
Datetime('last_update', required=True),
List('capabilities', required=True),
List('run_as_context', required=True),
List('categories', required=True),
List('maintainers', required=True),
List('tags', required=True),
List('screenshots', required=True, items=[Str('screenshot')]),
List('sources', required=True, items=[Str('source')]),
Str('name', required=True),
Str('title', required=True),
Str('description', required=True),
Str('app_readme', required=True),
Str('location', required=True),
Str('healthy_error', required=True, null=True),
Str('home', required=True),
Str('latest_version', required=True),
Str('latest_app_version', required=True),
Str('latest_human_version', required=True),
Str('icon_url', null=True, required=True),
Str('train', required=True),
Str('catalog', required=True),
register=True,
# We do this because if we change anything in catalog.json, even older releases will
# get this new field and different roles will start breaking due to this
additional_attrs=True,
))
@filterable_api_method(item=AppAvailableResponse, roles=['CATALOG_READ'])
def available(self, filters, options):
"""
Retrieve all available applications from all configured catalogs.
Expand All @@ -68,7 +40,7 @@ def available(self, filters, options):
]

catalog = self.middleware.call_sync('catalog.config')
for train, train_data in self.middleware.call_sync('catalog.apps').items():
for train, train_data in self.middleware.call_sync('catalog.apps', {}).items():
if train not in catalog['preferred_trains']:
continue

Expand All @@ -82,16 +54,14 @@ def available(self, filters, options):

return filter_list(results, filters, options)

@accepts(roles=['CATALOG_READ'])
@returns(List(items=[Str('category')]))
@api_method(AppCategoriesArgs, AppCategoriesResult, roles=['CATALOG_READ'])
async def categories(self):
"""
Retrieve list of valid categories which have associated applications.
"""
return sorted(list(await self.middleware.call('catalog.retrieve_mapped_categories')))

@accepts(Str('app_name'), Str('train'), roles=['CATALOG_READ'])
@returns(List(items=[Ref('available_apps')]))
@api_method(AppSimilarArgs, AppSimilarResult, roles=['CATALOG_READ'])
def similar(self, app_name, train):
"""
Retrieve applications which are similar to `app_name`.
Expand Down
45 changes: 3 additions & 42 deletions src/middlewared/middlewared/plugins/catalog/apps_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
from datetime import datetime
from jsonschema import validate as json_schema_validate, ValidationError as JsonValidationError

from middlewared.schema import accepts, Bool, Dict, List, returns, Str
from middlewared.api import api_method
from middlewared.api.current import CatalogAppsArgs, CatalogAppsResult
from middlewared.service import private, Service

from .apps_util import get_app_version_details
Expand Down Expand Up @@ -45,47 +46,7 @@ def train_to_apps_version_mapping(self):
def cached(self, label):
return self.middleware.call_sync('cache.has_key', get_cache_key(label))

@accepts(
Dict(
'options',
Bool('cache', default=True),
Bool('cache_only', default=False),
Bool('retrieve_all_trains', default=True),
List('trains', items=[Str('train_name')]),
),
roles=['CATALOG_READ']
)
@returns(Dict(
'trains',
additional_attrs=True,
example={
'stable': {
'plex': {
'app_readme': '<h1>Plex</h1>',
'categories': ['media'],
'description': 'Plex is a media server that allows you to stream your media to any Plex client.',
'healthy': True,
'healthy_error': None,
'home': 'https://plex.tv',
'location': '/mnt/.ix-apps/truenas_catalog/stable/plex',
'latest_version': '1.0.0',
'latest_app_version': '1.40.2.8395',
'latest_human_version': '1.40.2.8395_1.0.0',
'last_update': '2024-07-30 13:40:47+00:00',
'name': 'plex',
'recommended': False,
'title': 'Plex',
'maintainers': [
{'email': '[email protected]', 'name': 'truenas', 'url': 'https://www.truenas.com/'},
],
'tags': ['plex', 'media', 'entertainment', 'movies', 'series', 'tv', 'streaming'],
'screenshots': ['https://media.sys.truenas.net/apps/plex/screenshots/screenshot2.png'],
'sources': ['https://plex.tv', 'https://hub.docker.com/r/plexinc/pms-docker'],
'icon_url': 'https://media.sys.truenas.net/apps/plex/icons/icon.png'
},
},
}
))
@api_method(CatalogAppsArgs, CatalogAppsResult, roles=['CATALOG_READ'])
def apps(self, options):
"""
Retrieve apps details for `label` catalog.
Expand Down
8 changes: 4 additions & 4 deletions src/middlewared/middlewared/plugins/catalog/sync.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from middlewared.schema import accepts
from middlewared.service import job, private, returns, Service
from middlewared.api import api_method
from middlewared.api.current import CatalogSyncArgs, CatalogSyncResult
from middlewared.service import job, private, Service

from .git_utils import pull_clone_repository
from .utils import OFFICIAL_LABEL, OFFICIAL_CATALOG_REPO, OFFICIAL_CATALOG_BRANCH
Expand All @@ -13,8 +14,7 @@ class CatalogService(Service):
async def synced(self):
return self.SYNCED

@accepts(roles=['CATALOG_WRITE'])
@returns()
@api_method(CatalogSyncArgs, CatalogSyncResult, roles=['CATALOG_WRITE'])
@job(lock='official_catalog_sync')
async def sync(self, job):
"""
Expand Down
Loading
Loading