196 lines
5.6 KiB
Python
196 lines
5.6 KiB
Python
import json
|
|
from importlib import import_module
|
|
from typing import Any, Callable, Dict, List, Optional, Tuple
|
|
|
|
from uaclient.api import AbstractProgress, errors
|
|
from uaclient.api.data_types import APIData, APIResponse, ErrorWarningObject
|
|
from uaclient.config import UAConfig
|
|
from uaclient.data_types import IncorrectFieldTypeError
|
|
from uaclient.messages import API_UNKNOWN_ARG, WARN_NEW_VERSION_AVAILABLE
|
|
from uaclient.version import check_for_new_version
|
|
|
|
VALID_ENDPOINTS = [
|
|
"u.pro.version.v1",
|
|
"u.pro.attach.auto.configure_retry_service.v1",
|
|
"u.pro.attach.auto.full_auto_attach.v1",
|
|
"u.pro.attach.auto.should_auto_attach.v1",
|
|
"u.pro.attach.guest.get_guest_token.v1",
|
|
"u.pro.attach.magic.initiate.v1",
|
|
"u.pro.attach.magic.revoke.v1",
|
|
"u.pro.attach.magic.wait.v1",
|
|
"u.pro.attach.token.full_token_attach.v1",
|
|
"u.pro.detach.v1",
|
|
"u.pro.packages.summary.v1",
|
|
"u.pro.packages.updates.v1",
|
|
"u.pro.security.cves.v1",
|
|
"u.pro.security.fix.cve.execute.v1",
|
|
"u.pro.security.fix.cve.plan.v1",
|
|
"u.pro.security.fix.usn.execute.v1",
|
|
"u.pro.security.fix.usn.plan.v1",
|
|
"u.pro.security.status.livepatch_cves.v1",
|
|
"u.pro.security.status.reboot_required.v1",
|
|
"u.pro.services.dependencies.v1",
|
|
"u.pro.services.disable.v1",
|
|
"u.pro.services.enable.v1",
|
|
"u.pro.status.enabled_services.v1",
|
|
"u.pro.status.is_attached.v1",
|
|
"u.apt_news.current_news.v1",
|
|
"u.security.package_manifest.v1",
|
|
"u.unattended_upgrades.status.v1",
|
|
]
|
|
|
|
|
|
def _process_options(
|
|
options: List[str], fields: List[str]
|
|
) -> Tuple[Dict[str, Any], List[ErrorWarningObject]]:
|
|
kwargs = {}
|
|
warnings = []
|
|
|
|
for option in options:
|
|
try:
|
|
k, v = option.split("=")
|
|
except ValueError:
|
|
raise errors.APIBadArgsFormat(arg=option)
|
|
|
|
if not k or not v:
|
|
raise errors.APIBadArgsFormat(arg=option)
|
|
|
|
if k not in fields:
|
|
warnings.append(
|
|
ErrorWarningObject(
|
|
title=API_UNKNOWN_ARG.format(arg=k).msg,
|
|
code=API_UNKNOWN_ARG.name,
|
|
meta={},
|
|
)
|
|
)
|
|
|
|
kwargs[k] = v
|
|
|
|
return kwargs, warnings
|
|
|
|
|
|
def _process_data(
|
|
data: str, fields: List[str]
|
|
) -> Tuple[Dict[str, Any], List[ErrorWarningObject]]:
|
|
kwargs = {}
|
|
warnings = []
|
|
|
|
try:
|
|
json_data = json.loads(data)
|
|
except json.decoder.JSONDecodeError:
|
|
raise errors.APIJSONDataFormatError(data=data)
|
|
|
|
for k, v in json_data.items():
|
|
if k not in fields:
|
|
warnings.append(
|
|
ErrorWarningObject(
|
|
title=API_UNKNOWN_ARG.format(arg=k).msg,
|
|
code=API_UNKNOWN_ARG.name,
|
|
meta={},
|
|
)
|
|
)
|
|
|
|
kwargs[k] = v
|
|
|
|
return kwargs, warnings
|
|
|
|
|
|
def call_api(
|
|
endpoint_path: str,
|
|
options: List[str],
|
|
data: str,
|
|
cfg: UAConfig,
|
|
progress_object: Optional[AbstractProgress] = None,
|
|
) -> APIResponse:
|
|
|
|
if endpoint_path not in VALID_ENDPOINTS:
|
|
return errors.error_out(
|
|
errors.APIInvalidEndpoint(endpoint=endpoint_path)
|
|
)
|
|
|
|
module = import_module("uaclient.api." + endpoint_path)
|
|
endpoint = module.endpoint
|
|
|
|
option_warnings = []
|
|
|
|
if endpoint.options_cls:
|
|
fields = [f.key for f in endpoint.options_cls.fields]
|
|
try:
|
|
if options:
|
|
kwargs, warnings = _process_options(options, fields)
|
|
elif data:
|
|
kwargs, warnings = _process_data(data, fields)
|
|
else:
|
|
kwargs, warnings = {}, []
|
|
option_warnings.extend(warnings)
|
|
except errors.APIError as e:
|
|
return errors.error_out(e)
|
|
|
|
try:
|
|
options = endpoint.options_cls.from_dict(kwargs)
|
|
except IncorrectFieldTypeError as e:
|
|
return errors.error_out(
|
|
errors.APIMissingArg(arg=e.key, endpoint=endpoint_path)
|
|
)
|
|
|
|
try:
|
|
if endpoint.supports_progress:
|
|
result = endpoint.fn(
|
|
options, cfg, progress_object=progress_object
|
|
)
|
|
else:
|
|
result = endpoint.fn(options, cfg)
|
|
except Exception as e:
|
|
return errors.error_out(e)
|
|
|
|
else:
|
|
if options or data:
|
|
return errors.error_out(
|
|
errors.APINoArgsForEndpoint(endpoint=endpoint_path)
|
|
)
|
|
try:
|
|
if endpoint.supports_progress:
|
|
result = endpoint.fn(cfg, progress_object=progress_object)
|
|
else:
|
|
result = endpoint.fn(cfg)
|
|
except Exception as e:
|
|
return errors.error_out(e)
|
|
|
|
new_version = check_for_new_version()
|
|
if new_version:
|
|
option_warnings.append(
|
|
ErrorWarningObject(
|
|
title=WARN_NEW_VERSION_AVAILABLE.format(
|
|
version=new_version
|
|
).msg,
|
|
code=WARN_NEW_VERSION_AVAILABLE.name,
|
|
meta={},
|
|
)
|
|
)
|
|
|
|
return APIResponse(
|
|
_schema_version=endpoint.version,
|
|
warnings=result.warnings + option_warnings,
|
|
data=APIData(
|
|
type=endpoint.name,
|
|
attributes=result,
|
|
meta=result.meta,
|
|
),
|
|
)
|
|
|
|
|
|
class APIEndpoint:
|
|
def __init__(
|
|
self,
|
|
version: str,
|
|
name: str,
|
|
fn: Callable,
|
|
options_cls,
|
|
supports_progress: bool = False,
|
|
):
|
|
self.version = version
|
|
self.name = name
|
|
self.fn = fn
|
|
self.options_cls = options_cls
|
|
self.supports_progress = supports_progress
|