-
Notifications
You must be signed in to change notification settings - Fork 16
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
RSTUF Interface refactoring (IServices) #389
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,13 @@ | ||
# SPDX-FileCopyrightText: 2022-2023 VMware Inc | ||
# | ||
# SPDX-License-Identifier: MIT | ||
|
||
|
||
import importlib | ||
import logging | ||
from abc import ABC, abstractmethod | ||
from dataclasses import dataclass | ||
from typing import Any, List, Optional | ||
from typing import Any, Dict, List, Optional | ||
|
||
from dynaconf import Dynaconf | ||
from securesystemslib.signer import Key, Signer | ||
from tuf.api.metadata import Metadata, T | ||
|
||
|
@@ -15,7 +16,7 @@ | |
class ServiceSettings: | ||
"""Dataclass for service settings.""" | ||
|
||
name: str | ||
names: List[str] | ||
argument: str | ||
required: bool | ||
default: Optional[Any] = None | ||
|
@@ -30,6 +31,13 @@ def configure(cls, settings) -> None: | |
""" | ||
pass # pragma: no cover | ||
|
||
@classmethod | ||
def from_dynaconf(cls, settings: Dynaconf) -> None: | ||
""" | ||
Run actions to test, configure using the settings. | ||
""" | ||
_setup_service_dynaconf(cls, settings.KEYVAULT_BACKEND, settings) | ||
|
||
@classmethod | ||
@abstractmethod | ||
def settings(cls) -> List[ServiceSettings]: | ||
|
@@ -47,18 +55,26 @@ def get(self, public_key: Key) -> Signer: | |
class IStorage(ABC): | ||
@classmethod | ||
@abstractmethod | ||
def configure(cls, settings: Any) -> None: | ||
def configure(cls, settings: Dynaconf) -> None: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does it make sense to annotate the settings arg at |
||
""" | ||
Run actions to test, configure using the settings. | ||
""" | ||
raise NotImplementedError # pragma: no cover | ||
|
||
@classmethod | ||
def from_dynaconf(cls, settings: Dynaconf) -> None: | ||
""" | ||
Run actions to test and configure using the dynaconf settings. | ||
""" | ||
_setup_service_dynaconf(cls, settings.STORAGE_BACKEND, settings) | ||
|
||
@classmethod | ||
@abstractmethod | ||
def settings(cls) -> List[ServiceSettings]: | ||
""" | ||
Define all the ServiceSettings required in settings. | ||
""" | ||
|
||
raise NotImplementedError # pragma: no cover | ||
|
||
@abstractmethod | ||
|
@@ -79,3 +95,78 @@ def put( | |
Stores file bytes within a file with a specific filename. | ||
""" | ||
raise NotImplementedError # pragma: no cover | ||
|
||
|
||
def _setup_service_dynaconf(cls: Any, backend: Any, settings: Dynaconf): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: to be even more precise you can create a class called There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As we have new contributors, it would be good to have it as a Good First Issue for someone who wants to understand the RSTUF Worker IServices. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
""" | ||
Setup a Interface Service (IService) from settings Dynaconf (environment | ||
variables) | ||
""" | ||
# the 'service import is used to retrieve sublcasses (Implemented Services) | ||
from repository_service_tuf_worker import services # noqa | ||
|
||
service_backends = [i.__name__.upper() for i in cls.__subclasses__()] | ||
backend_name = f"RSTUF_{cls.__name__.replace('I', '').upper()}_BACKEND" | ||
|
||
if type(backend) is not str and issubclass( | ||
backend, tuple(cls.__subclasses__()) | ||
): | ||
logging.debug(f"{backend_name} is defined as {backend}") | ||
|
||
elif backend.upper() not in service_backends: | ||
raise ValueError( | ||
f"Invalid {backend_name} {backend}. " | ||
f"Supported {backend_name} {', '.join(service_backends)}" | ||
) | ||
else: | ||
backend = getattr( | ||
importlib.import_module("repository_service_tuf_worker.services"), | ||
backend, | ||
) | ||
# look all required settings | ||
if missing_settings := [ | ||
s.names | ||
for s in backend.settings() | ||
if s.required and all(n not in settings for n in s.names) | ||
]: | ||
# add the prefix `RSTUF_` to attributes including as dynaconf | ||
# removes it. It makes the message more clear to the user. | ||
missing_stg: List = [] | ||
for missing in missing_settings: | ||
missing_stg.append("RSTUF_" + " or RSTUF_".join(missing)) | ||
|
||
raise AttributeError( | ||
"'Settings' object has no attribute(s) (environment variables)" | ||
f": {', '.join(missing_stg)}" | ||
) | ||
|
||
# parse and define the keyargs from dynaconf | ||
kwargs: Dict[str, Any] = {} | ||
for s_var in backend.settings(): | ||
if all( | ||
[ | ||
settings.store.get(var_name) is None | ||
for var_name in s_var.names | ||
] | ||
): | ||
for var_name in s_var.names: | ||
settings.store[var_name] = s_var.default | ||
kwargs[s_var.argument] = settings.store[s_var.names[0]] | ||
else: | ||
for var_name in s_var.names: | ||
if settings.store.get(var_name) is not None: | ||
kwargs[s_var.argument] = settings.store[var_name] | ||
break | ||
|
||
if cls.__name__ == "IStorage": | ||
settings.STORAGE_BACKEND = backend | ||
settings.STORAGE_BACKEND.configure(settings) | ||
settings.STORAGE = settings.STORAGE_BACKEND(**kwargs) | ||
|
||
elif cls.__name__ == "IKeyVault": | ||
settings.KEYVAULT_BACKEND = backend | ||
settings.KEYVAULT_BACKEND.configure(settings) | ||
settings.KEYVAULT = settings.KEYVAULT_BACKEND(**kwargs) | ||
|
||
else: | ||
raise ValueError(f"Invalid Interface {cls.__name__}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.