From 26979a4821f27d7e1f8841cfb68f5fbbc98e4d8e Mon Sep 17 00:00:00 2001 From: ImMin5 Date: Wed, 22 May 2024 17:33:25 +0900 Subject: [PATCH] feat: add new feature WorkspaceConfig resource --- .../config/interface/grpc/__init__.py | 2 + .../config/interface/grpc/workspace_config.py | 53 +++++ .../manager/workspace_config_manager.py | 56 ++++++ src/spaceone/config/model/__init__.py | 1 + .../config/model/workspace_config/__init__.py | 0 .../config/model/workspace_config/database.py | 24 +++ .../config/model/workspace_config/request.py | 61 ++++++ .../config/model/workspace_config/response.py | 28 +++ .../service/workspace_config_service.py | 184 ++++++++++++++++++ 9 files changed, 409 insertions(+) create mode 100644 src/spaceone/config/interface/grpc/workspace_config.py create mode 100644 src/spaceone/config/manager/workspace_config_manager.py create mode 100644 src/spaceone/config/model/workspace_config/__init__.py create mode 100644 src/spaceone/config/model/workspace_config/database.py create mode 100644 src/spaceone/config/model/workspace_config/request.py create mode 100644 src/spaceone/config/model/workspace_config/response.py create mode 100644 src/spaceone/config/service/workspace_config_service.py diff --git a/src/spaceone/config/interface/grpc/__init__.py b/src/spaceone/config/interface/grpc/__init__.py index 27b25f4..e23f1ee 100644 --- a/src/spaceone/config/interface/grpc/__init__.py +++ b/src/spaceone/config/interface/grpc/__init__.py @@ -1,9 +1,11 @@ from spaceone.core.pygrpc.server import GRPCServer from spaceone.config.interface.grpc.domain_config import DomainConfig +from spaceone.config.interface.grpc.workspace_config import WorkspaceConfig from spaceone.config.interface.grpc.user_config import UserConfig _all_ = ["app"] app = GRPCServer() app.add_service(DomainConfig) +app.add_service(WorkspaceConfig) app.add_service(UserConfig) diff --git a/src/spaceone/config/interface/grpc/workspace_config.py b/src/spaceone/config/interface/grpc/workspace_config.py new file mode 100644 index 0000000..9549dcc --- /dev/null +++ b/src/spaceone/config/interface/grpc/workspace_config.py @@ -0,0 +1,53 @@ +from spaceone.api.config.v1 import workspace_config_pb2, workspace_config_pb2_grpc +from spaceone.core.pygrpc import BaseAPI + +from spaceone.config.service.workspace_config_service import WorkspaceConfigService +from spaceone.config.info.domain_config_info import * +from spaceone.config.info.common_info import * + + +class WorkspaceConfig(BaseAPI, workspace_config_pb2_grpc.WorkspaceConfigServicer): + pb2 = workspace_config_pb2 + pb2_grpc = workspace_config_pb2_grpc + + def create(self, request, context): + params, metadata = self.parse_request(request, context) + workspace_config_svc = WorkspaceConfigService(metadata) + response: dict = workspace_config_svc.create(params) + return self.dict_to_message(response) + + def update(self, request, context): + params, metadata = self.parse_request(request, context) + workspace_config_svc = WorkspaceConfigService(metadata) + response: dict = workspace_config_svc.update(params) + return self.dict_to_message(response) + + def set(self, request, context): + params, metadata = self.parse_request(request, context) + workspace_config_svc = WorkspaceConfigService(metadata) + response: dict = workspace_config_svc.set(params) + return self.dict_to_message(response) + + def delete(self, request, context): + params, metadata = self.parse_request(request, context) + workspace_config_svc = WorkspaceConfigService(metadata) + workspace_config_svc.delete(params) + return self.empty() + + def get(self, request, context): + params, metadata = self.parse_request(request, context) + workspace_config_svc = WorkspaceConfigService(metadata) + response: dict = workspace_config_svc.get(params) + return self.dict_to_message(response) + + def list(self, request, context): + params, metadata = self.parse_request(request, context) + workspace_config_svc = WorkspaceConfigService(metadata) + response: dict = workspace_config_svc.list(params) + return self.dict_to_message(response) + + def stat(self, request, context): + params, metadata = self.parse_request(request, context) + workspace_config_svc = WorkspaceConfigService(metadata) + response: dict = workspace_config_svc.stat(params) + return self.dict_to_message(response) diff --git a/src/spaceone/config/manager/workspace_config_manager.py b/src/spaceone/config/manager/workspace_config_manager.py new file mode 100644 index 0000000..1eba90a --- /dev/null +++ b/src/spaceone/config/manager/workspace_config_manager.py @@ -0,0 +1,56 @@ +import logging +from typing import Tuple + +from mongoengine import QuerySet +from spaceone.core.manager import BaseManager + +from spaceone.config.model.workspace_config.database import WorkspaceConfig + +_LOGGER = logging.getLogger(__name__) + + +class WorkspaceConfigManager(BaseManager): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.workspace_config_model = WorkspaceConfig + + def create_workspace_config(self, params: dict) -> WorkspaceConfig: + def _rollback(vo: WorkspaceConfig) -> None: + _LOGGER.info( + f"[create_workspace_config._rollback] " f"Delete workspace config : {vo.name}" + ) + vo.delete() + + workspace_config_vo: WorkspaceConfig = self.workspace_config_model.create(params) + self.transaction.add_rollback(_rollback, workspace_config_vo) + + return workspace_config_vo + + def update_workspace_config_by_vo( + self, params: dict, workspace_config_vo: WorkspaceConfig + ) -> WorkspaceConfig: + def _rollback(old_data: dict): + _LOGGER.info( + f'[update_workspace_config_by_vo._rollback] Revert Data : {old_data["name"]}' + ) + workspace_config_vo.update(old_data) + + self.transaction.add_rollback(_rollback, workspace_config_vo.to_dict()) + + return workspace_config_vo.update(params) + + @staticmethod + def delete_workspace_config_by_vo(workspace_config_vo: WorkspaceConfig) -> None: + workspace_config_vo.delete() + + def get_workspace_config(self, name: str, workspace_id: str, domain_id: str) -> WorkspaceConfig: + return self.workspace_config_model.get(name=name, workspace_id=workspace_id, domain_id=domain_id) + + def filter_workspace_configs(self, **conditions): + return self.workspace_config_model.filter(**conditions) + + def list_workspace_configs(self, query: dict) -> Tuple[QuerySet, int]: + return self.workspace_config_model.query(**query) + + def stat_workspace_configs(self, query: dict) -> dict: + return self.workspace_config_model.stat(**query) diff --git a/src/spaceone/config/model/__init__.py b/src/spaceone/config/model/__init__.py index 8e4fcc4..a161c73 100644 --- a/src/spaceone/config/model/__init__.py +++ b/src/spaceone/config/model/__init__.py @@ -1,2 +1,3 @@ from spaceone.config.model.user_config_model import UserConfig +from spaceone.config.model.workspace_config.database import WorkspaceConfig from spaceone.config.model.domain_config_model import DomainConfig diff --git a/src/spaceone/config/model/workspace_config/__init__.py b/src/spaceone/config/model/workspace_config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/spaceone/config/model/workspace_config/database.py b/src/spaceone/config/model/workspace_config/database.py new file mode 100644 index 0000000..49496d0 --- /dev/null +++ b/src/spaceone/config/model/workspace_config/database.py @@ -0,0 +1,24 @@ +from mongoengine import * + +from spaceone.core.model.mongo_model import MongoModel + + +class WorkspaceConfig(MongoModel): + name = StringField(max_length=255, unique_with=["domain_id", "workspace_id"]) + data = DictField() + tags = DictField() + workspace_id = StringField(max_length=40) + domain_id = StringField(max_length=40) + created_at = DateTimeField(auto_now_add=True) + updated_at = DateTimeField(auto_now=True) + + meta = { + "updatable_fields": ["name", "data", "tags", "updated_at"], + "minimal_fields": ["name"], + "ordering": ["name"], + "indexes": [ + "name", + "workspace_id", + "domain_id", + ], + } diff --git a/src/spaceone/config/model/workspace_config/request.py b/src/spaceone/config/model/workspace_config/request.py new file mode 100644 index 0000000..beabd28 --- /dev/null +++ b/src/spaceone/config/model/workspace_config/request.py @@ -0,0 +1,61 @@ +from typing import Union +from pydantic import BaseModel + +__all__ = [ + "WorkspaceConfigCreateRequest", + "WorkspaceConfigUpdateRequest", + "WorkspaceConfigSetRequest", + "WorkspaceConfigDeleteRequest", + "WorkspaceConfigGetRequest", + "WorkspaceConfigSearchQueryRequest", + "WorkspaceConfigQueryRequest", +] + + +class WorkspaceConfigCreateRequest(BaseModel): + name: str + data: dict + tags: Union[dict, None] = None + workspace_id: str + domain_id: str + + +class WorkspaceConfigUpdateRequest(BaseModel): + name: str + data: Union[dict, None] = None + tags: Union[dict, None] = None + workspace_id: str + domain_id: str + + +class WorkspaceConfigSetRequest(BaseModel): + name: str + data: str + tags: Union[dict, None] = None + workspace_id: str + domain_id: str + + +class WorkspaceConfigDeleteRequest(BaseModel): + name: str + workspace_id: str + domain_id: str + + +class WorkspaceConfigGetRequest(BaseModel): + name: str + workspace_id: str + domain_id: str + + +class WorkspaceConfigSearchQueryRequest(BaseModel): + query: Union[dict, None] = None + name: Union[str, None] = None + workspace_id: str + domain_id: str + + +class WorkspaceConfigQueryRequest(BaseModel): + query: dict + workspace_id: str + domain_id: str diff --git a/src/spaceone/config/model/workspace_config/response.py b/src/spaceone/config/model/workspace_config/response.py new file mode 100644 index 0000000..8ae101e --- /dev/null +++ b/src/spaceone/config/model/workspace_config/response.py @@ -0,0 +1,28 @@ +from datetime import datetime +from typing import Union, List +from pydantic import BaseModel + +from spaceone.core import utils + +__all__ = ["WorkspaceConfigResponse", "WorkspaceConfigsResponse"] + + +class WorkspaceConfigResponse(BaseModel): + name: Union[str, None] = None + data: Union[dict, None] = None + tags: Union[dict, None] = None + workspace_id: Union[str, None] = None + domain_id: Union[str, None] = None + created_at: Union[datetime, None] = None + updated_at: Union[datetime, None] = None + + def dict(self, *args, **kwargs): + data = super().dict(*args, **kwargs) + data["created_at"] = utils.datetime_to_iso8601(data["created_at"]) + data["updated_at"] = utils.datetime_to_iso8601(data.get("updated_at")) + return data + + +class WorkspaceConfigsResponse(BaseModel): + results: List[WorkspaceConfigResponse] = [] + total_count: int diff --git a/src/spaceone/config/service/workspace_config_service.py b/src/spaceone/config/service/workspace_config_service.py new file mode 100644 index 0000000..db3b28d --- /dev/null +++ b/src/spaceone/config/service/workspace_config_service.py @@ -0,0 +1,184 @@ +import logging +from typing import Union + +from spaceone.core.service import * + +from spaceone.config.model.workspace_config.response import * +from spaceone.config.model.workspace_config.request import * +from spaceone.config.manager.workspace_config_manager import WorkspaceConfigManager + +_LOGGER = logging.getLogger(__name__) + + +@authentication_handler +@authorization_handler +@mutation_handler +@event_handler +class WorkspaceConfigService(BaseService): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.workspace_config_mgr = WorkspaceConfigManager() + + @transaction(permission="config:WorkspaceConfig.write", role_types=["WORKSPACE_OWNER"]) + @convert_model + def create(self, params: WorkspaceConfigCreateRequest) -> Union[WorkspaceConfigResponse, dict]: + """Create workspace config + + Args: + params (dict): { + 'name': 'str', # required + 'data': 'dict', # required + 'tags': 'dict', + 'workspace_id': 'str' # injected from auth + 'domain_id': 'str' # injected from auth + } + + Returns: + workspace_config_vo (object) + """ + + workspace_config_vo = self.workspace_config_mgr.create_workspace_config(params.dict()) + + return WorkspaceConfigResponse(**workspace_config_vo.to_dict()) + + @transaction(permission="config:WorkspaceConfig.write", role_types=["WORKSPACE_OWNER"]) + @convert_model + def update(self, params: WorkspaceConfigUpdateRequest) -> Union[WorkspaceConfigResponse, dict]: + """Update workspace config + + Args: + params (dict): { + 'name': 'str', # required + 'data': 'dict', + 'tags': 'dict', + 'workspace_id': 'str' # injected from auth + 'domain_id': 'str' # injected from auth + } + + Returns: + domain_config_vo (object) + """ + + workspace_config_vo = self.workspace_config_mgr.get_workspace_config(params.name, params.workspace_id, + params.domain_id) + + workspace_config_vo = self.workspace_config_mgr.update_workspace_config_by_vo(params.dict(exclude_unset=True), + workspace_config_vo) + + return WorkspaceConfigResponse(**workspace_config_vo.to_dict()) + + @transaction(permission="config:WorkspaceConfig.write", role_types=["WORKSPACE_OWNER"]) + @convert_model + def set(self, params: WorkspaceConfigSetRequest) -> Union[WorkspaceConfigResponse, dict]: + """Set domain config (create or update) + + Args: + params (dict): { + 'name': 'str', # required + 'data': 'dict', # required + 'tags': 'dict', + 'workspace_id': 'str' # injected from auth + 'domain_id': 'str' # injected from auth + } + + Returns: + workspace_config_vo (object) + """ + + workspace_config_vos = self.workspace_config_mgr.filter_workspace_configs( + name=params.name, workspace_id=params.workspace_id, domain_id=params.domain_id + ) + + if workspace_config_vos.count() == 0: + workspace_config_vo = self.workspace_config_mgr.create_workspace_config(params.dict()) + else: + workspace_config_vo = self.workspace_config_mgr.update_workspace_config_by_vo( + params.dict(exclude_unset=True), workspace_config_vos[0] + ) + return WorkspaceConfigResponse(**workspace_config_vo.to_dict()) + + @transaction(permission="config:WorkspaceConfig.write", role_types=["WORKSPACE_OWNER"]) + @convert_model + def delete(self, params: WorkspaceConfigDeleteRequest) -> None: + """Delete workspace config + + Args: + params (dict): { + 'name': 'str', # required + 'workspace_id': 'str' # injected from auth + 'domain_id': 'str' # injected from auth + } + + Returns: + None + """ + workspace_config_vo = self.workspace_config_mgr.get_workspace_config(params.name, params.workspace_id, + params.domain_id) + self.workspace_config_mgr.delete_workspace_config_by_vo(workspace_config_vo) + + @transaction(permission="config:WorkspaceConfig.read", role_types=["WORKSPACE_OWNER", "WORKSPACE_MEMBER"]) + @check_required(["name", "domain_id"]) + def get(self, params: WorkspaceConfigGetRequest) -> Union[WorkspaceConfigResponse, dict]: + """Get workspace config + + Args: + params (dict): { + 'name': 'str', # required + 'workspace_id': 'str' # injected from auth + 'domain_id': 'str' # injected from auth + } + + Returns: + workspace_config_vo (object) + """ + + workspace_config_vo = self.workspace_config_mgr.get_workspace_config(params.name, params.workspace_id, + params.domain_id) + + return WorkspaceConfigResponse(**workspace_config_vo.to_dict()) + + @transaction(permission="config:WorkspaceConfig.read", role_types=["WORKSPACE_OWNER", "WORKSPACE_MEMBER"]) + @append_query_filter(["name", "workspace_id", "domain_id"]) + @append_keyword_filter(["name"]) + @convert_model + def list(self, params: WorkspaceConfigSearchQueryRequest) -> Union[WorkspaceConfigsResponse, dict]: + """List workspace configs + + Args: + params (dict): { + 'query': 'dict (spaceone.api.core.v2.Query)' + 'name': 'str', + 'workspace_id': 'str', # injected from auth + 'domain_id': 'str', # injected from auth + } + + Returns: + workspace_configs_vos (objects) + total_count (int) + """ + + query = params.query or {} + workspace_config_vos, total_count = self.workspace_config_mgr.list_workspace_configs(query) + workspaces_info = [workspace_config_vo.to_dict() for workspace_config_vo in workspace_config_vos] + return WorkspaceConfigsResponse(results=workspaces_info, total_count=total_count) + + @transaction(permission="config:WorkspaceConfig.read", role_types=["WORKSPACE_OWNER", "WORKSPACE_MEMBER"]) + @append_query_filter(["workspace_id", "domain_id"]) + @append_keyword_filter(["name"]) + @convert_model + def stat(self, params: WorkspaceConfigQueryRequest) -> dict: + """ Stat workspace configs + Args: + params (dict): { + 'query': 'dict (spaceone.api.core.v1.StatisticsQuery)' # required + 'workspace_id': 'str', # injected from auth + 'domain_id': 'str', # required + } + + Returns: + values (list) : 'list of statistics data' + + """ + + query = params.query or {} + return self.workspace_config_mgr.stat_workspace_configs(query)