From d58bac6e4effcecd653388d132a9ff786308d636 Mon Sep 17 00:00:00 2001 From: Jongmin Kim Date: Wed, 20 Nov 2024 23:17:27 +0900 Subject: [PATCH] feat: implement package APIs Signed-off-by: Jongmin Kim --- .../identity/error/error_workspace.py | 6 + .../identity/interface/grpc/__init__.py | 5 +- .../identity/interface/grpc/package.py | 56 ++++ .../identity/interface/grpc/workspace.py | 12 + .../identity/manager/package_manager.py | 54 ++++ .../identity/manager/user_group_manager.py | 2 +- .../identity/model/package/__init__.py | 0 .../identity/model/package/database.py | 29 ++ .../identity/model/package/request.py | 61 +++++ .../identity/model/package/response.py | 29 ++ .../identity/model/workspace/database.py | 3 + .../identity/model/workspace/request.py | 14 + .../identity/model/workspace/response.py | 1 + .../identity/service/package_service.py | 259 ++++++++++++++++++ .../identity/service/user_group_service.py | 13 +- .../identity/service/workspace_service.py | 91 +++++- 16 files changed, 615 insertions(+), 20 deletions(-) create mode 100644 src/spaceone/identity/interface/grpc/package.py create mode 100644 src/spaceone/identity/manager/package_manager.py create mode 100644 src/spaceone/identity/model/package/__init__.py create mode 100644 src/spaceone/identity/model/package/database.py create mode 100644 src/spaceone/identity/model/package/request.py create mode 100644 src/spaceone/identity/model/package/response.py create mode 100644 src/spaceone/identity/service/package_service.py diff --git a/src/spaceone/identity/error/error_workspace.py b/src/spaceone/identity/error/error_workspace.py index de010133..c3a2ff9c 100644 --- a/src/spaceone/identity/error/error_workspace.py +++ b/src/spaceone/identity/error/error_workspace.py @@ -3,3 +3,9 @@ class ERROR_WORKSPACE_STATE(ERROR_INVALID_ARGUMENT): _message = "Workspace is disabled. (workspace_id = {workspace_id})" + + +class ERROR_DEFAULT_PACKAGE_NOT_ALLOWED(ERROR_INVALID_ARGUMENT): + _message = ( + "Default package can not be added to workspace. (package_id = {package_id})" + ) diff --git a/src/spaceone/identity/interface/grpc/__init__.py b/src/spaceone/identity/interface/grpc/__init__.py index 8f3db7e9..8d8f8782 100644 --- a/src/spaceone/identity/interface/grpc/__init__.py +++ b/src/spaceone/identity/interface/grpc/__init__.py @@ -9,6 +9,7 @@ from spaceone.identity.interface.grpc.project import Project from spaceone.identity.interface.grpc.project_group import ProjectGroup from spaceone.identity.interface.grpc.provider import Provider +from spaceone.identity.interface.grpc.package import Package from spaceone.identity.interface.grpc.role import Role from spaceone.identity.interface.grpc.role_binding import RoleBinding from spaceone.identity.interface.grpc.schema import Schema @@ -21,8 +22,7 @@ from spaceone.identity.interface.grpc.user_profile import UserProfile from spaceone.identity.interface.grpc.workspace import Workspace from spaceone.identity.interface.grpc.workspace_group import WorkspaceGroup -from spaceone.identity.interface.grpc.workspace_group_user import \ - WorkspaceGroupUser +from spaceone.identity.interface.grpc.workspace_group_user import WorkspaceGroupUser from spaceone.identity.interface.grpc.workspace_user import WorkspaceUser _all_ = ["app"] @@ -36,6 +36,7 @@ app.add_service(ProjectGroup) app.add_service(Project) app.add_service(Provider) +app.add_service(Package) app.add_service(Schema) app.add_service(TrustedAccount) app.add_service(ServiceAccount) diff --git a/src/spaceone/identity/interface/grpc/package.py b/src/spaceone/identity/interface/grpc/package.py new file mode 100644 index 00000000..d9727bd2 --- /dev/null +++ b/src/spaceone/identity/interface/grpc/package.py @@ -0,0 +1,56 @@ +from spaceone.core.pygrpc import BaseAPI +from spaceone.api.identity.v2 import package_pb2, package_pb2_grpc +from spaceone.identity.service.package_service import PackageService + + +class Package(BaseAPI, package_pb2_grpc.PackageServicer): + pb2 = package_pb2 + pb2_grpc = package_pb2_grpc + + def create(self, request, context): + params, metadata = self.parse_request(request, context) + package_svc = PackageService(metadata) + response: dict = package_svc.create(params) + return self.dict_to_message(response) + + def update(self, request, context): + params, metadata = self.parse_request(request, context) + package_svc = PackageService(metadata) + response: dict = package_svc.update(params) + return self.dict_to_message(response) + + def delete(self, request, context): + params, metadata = self.parse_request(request, context) + package_svc = PackageService(metadata) + package_svc.delete(params) + return self.empty() + + def set_default(self, request, context): + params, metadata = self.parse_request(request, context) + package_svc = PackageService(metadata) + response: dict = package_svc.set_default(params) + return self.dict_to_message(response) + + def change_order(self, request, context): + params, metadata = self.parse_request(request, context) + package_svc = PackageService(metadata) + response: dict = package_svc.change_order(params) + return self.dict_to_message(response) + + def get(self, request, context): + params, metadata = self.parse_request(request, context) + package_svc = PackageService(metadata) + response: dict = package_svc.get(params) + return self.dict_to_message(response) + + def list(self, request, context): + params, metadata = self.parse_request(request, context) + package_svc = PackageService(metadata) + response: dict = package_svc.list(params) + return self.dict_to_message(response) + + def stat(self, request, context): + params, metadata = self.parse_request(request, context) + package_svc = PackageService(metadata) + response: dict = package_svc.stat(params) + return self.dict_to_message(response) diff --git a/src/spaceone/identity/interface/grpc/workspace.py b/src/spaceone/identity/interface/grpc/workspace.py index 988d1ece..4f45f7b9 100644 --- a/src/spaceone/identity/interface/grpc/workspace.py +++ b/src/spaceone/identity/interface/grpc/workspace.py @@ -44,6 +44,18 @@ def disable(self, request, context): response: dict = workspace_svc.disable(params) return self.dict_to_message(response) + def add_package(self, request, context): + params, metadata = self.parse_request(request, context) + workspace_svc = WorkspaceService(metadata) + response: dict = workspace_svc.add_package(params) + return self.dict_to_message(response) + + def remove_package(self, request, context): + params, metadata = self.parse_request(request, context) + workspace_svc = WorkspaceService(metadata) + response: dict = workspace_svc.remove_package(params) + return self.dict_to_message(response) + def get(self, request, context): params, metadata = self.parse_request(request, context) workspace_svc = WorkspaceService(metadata) diff --git a/src/spaceone/identity/manager/package_manager.py b/src/spaceone/identity/manager/package_manager.py new file mode 100644 index 00000000..79d98645 --- /dev/null +++ b/src/spaceone/identity/manager/package_manager.py @@ -0,0 +1,54 @@ +import logging +from typing import Tuple + +from mongoengine import QuerySet +from spaceone.core.manager import BaseManager + +from spaceone.identity.model.package.database import Package + +_LOGGER = logging.getLogger(__name__) + + +class PackageManager(BaseManager): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.package_model = Package + + def create_package(self, params: dict) -> Package: + def _rollback(vo: Package): + _LOGGER.info( + f"[create_package._rollback] Delete package: {vo.package_id} ({vo.name})" + ) + vo.delete() + + package_vo = self.package_model.create(params) + + self.transaction.add_rollback(_rollback, package_vo) + + return package_vo + + def update_package_by_vo(self, params: dict, package_vo: Package) -> Package: + def _rollback(old_data): + _LOGGER.info( + f'[update_package_by_vo._rollback] Revert Data: {old_data["package_id"]}' + ) + package_vo.update(old_data) + + self.transaction.add_rollback(_rollback, package_vo.to_dict()) + return package_vo.update(params) + + @staticmethod + def delete_package_by_vo(package_vo: Package) -> None: + package_vo.delete() + + def get_package(self, package_id: str, domain_id: str) -> Package: + return self.package_model.get(package_id=package_id, domain_id=domain_id) + + def filter_packages(self, **conditions) -> QuerySet: + return self.package_model.filter(**conditions) + + def list_packages(self, query: dict) -> Tuple[QuerySet, int]: + return self.package_model.query(**query) + + def stat_package(self, query: dict) -> dict: + return self.package_model.stat(**query) diff --git a/src/spaceone/identity/manager/user_group_manager.py b/src/spaceone/identity/manager/user_group_manager.py index e09f6164..85c56c81 100644 --- a/src/spaceone/identity/manager/user_group_manager.py +++ b/src/spaceone/identity/manager/user_group_manager.py @@ -45,7 +45,7 @@ def delete_user_group_by_vo(user_group_vo: UserGroup) -> None: def get_user_group( self, user_group_id: str, domain_id: str, workspace_id: str = None - ): + ) -> UserGroup: conditions = {"user_group_id": user_group_id, "domain_id": domain_id} if workspace_id: diff --git a/src/spaceone/identity/model/package/__init__.py b/src/spaceone/identity/model/package/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/spaceone/identity/model/package/database.py b/src/spaceone/identity/model/package/database.py new file mode 100644 index 00000000..4ac53743 --- /dev/null +++ b/src/spaceone/identity/model/package/database.py @@ -0,0 +1,29 @@ +from mongoengine import * +from spaceone.core.model.mongo_model import MongoModel + + +class Package(MongoModel): + package_id = StringField(max_length=40, generate_id="p", unique=True) + name = StringField(max_length=255, unique_with=["domain_id"]) + description = StringField(max_length=255, default=None, null=True) + order = IntField(required=True) + is_default = BooleanField(default=False) + tags = DictField(Default=None) + domain_id = StringField(max_length=40) + created_at = DateTimeField(auto_now_add=True) + updated_at = DateTimeField(auto_now=True) + + meta = { + "updatable_fields": ["name", "description", "order", "is_default", "tags"], + "minimal_fields": [ + "package_id", + "name", + "is_default", + "domain_id", + ], + "ordering": ["order"], + "indexes": [ + "name", + "domain_id", + ], + } diff --git a/src/spaceone/identity/model/package/request.py b/src/spaceone/identity/model/package/request.py new file mode 100644 index 00000000..17d5d5ab --- /dev/null +++ b/src/spaceone/identity/model/package/request.py @@ -0,0 +1,61 @@ +from typing import Union, List +from pydantic import BaseModel + +__all__ = [ + "PackageCreateRequest", + "PackageUpdateRequest", + "PackageDeleteRequest", + "PackageSetDefaultRequest", + "PackageChangeOrderRequest", + "PackageGetRequest", + "PackageSearchQueryRequest", + "PackageStatQueryRequest", +] + + +class PackageCreateRequest(BaseModel): + name: str + description: Union[str, None] = None + tags: Union[dict, None] = None + domain_id: str + + +class PackageUpdateRequest(BaseModel): + package_id: str + name: Union[str, None] = None + description: Union[str, None] = None + tags: Union[dict, None] = None + domain_id: str + + +class PackageDeleteRequest(BaseModel): + package_id: str + domain_id: str + + +class PackageSetDefaultRequest(BaseModel): + package_id: str + domain_id: str + + +class PackageChangeOrderRequest(BaseModel): + package_id: str + order: int + domain_id: str + + +class PackageGetRequest(BaseModel): + package_id: str + domain_id: str + + +class PackageSearchQueryRequest(BaseModel): + query: Union[dict, None] = None + package_id: Union[str, None] = None + name: Union[str, None] = None + domain_id: str + + +class PackageStatQueryRequest(BaseModel): + query: dict + domain_id: str diff --git a/src/spaceone/identity/model/package/response.py b/src/spaceone/identity/model/package/response.py new file mode 100644 index 00000000..06191443 --- /dev/null +++ b/src/spaceone/identity/model/package/response.py @@ -0,0 +1,29 @@ +from datetime import datetime +from typing import Union, List +from pydantic import BaseModel +from spaceone.core import utils + +__all__ = ["PackageResponse", "PackagesResponse"] + + +class PackageResponse(BaseModel): + package_id: Union[str, None] = None + name: Union[str, None] = None + description: Union[str, None] = None + order: Union[int, None] = None + is_default: Union[bool, None] = None + tags: Union[dict, 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["updated_at"]) + return data + + +class PackagesResponse(BaseModel): + results: List[PackageResponse] + total_count: int diff --git a/src/spaceone/identity/model/workspace/database.py b/src/spaceone/identity/model/workspace/database.py index 832c476b..43d7ca67 100644 --- a/src/spaceone/identity/model/workspace/database.py +++ b/src/spaceone/identity/model/workspace/database.py @@ -15,6 +15,7 @@ class Workspace(MongoModel): created_by = StringField(max_length=255) references = ListField(StringField(max_length=255), default=None, null=True) is_managed = BooleanField(default=False) + packages = ListField(StringField(max_length=40), default=[]) is_dormant = BooleanField(default=False) dormant_ttl = IntField(default=None, required=True) @@ -37,6 +38,7 @@ class Workspace(MongoModel): "state", "tags", "is_managed", + "packages", "is_dormant", "dormant_ttl", "service_account_count", @@ -66,6 +68,7 @@ class Workspace(MongoModel): "indexes": [ "name", "state", + "packages", "domain_id", "created_by", "is_managed", diff --git a/src/spaceone/identity/model/workspace/request.py b/src/spaceone/identity/model/workspace/request.py index faeecff8..2aea05b7 100644 --- a/src/spaceone/identity/model/workspace/request.py +++ b/src/spaceone/identity/model/workspace/request.py @@ -9,6 +9,8 @@ "WorkspaceDeleteRequest", "WorkspaceEnableRequest", "WorkspaceDisableRequest", + "WorkspaceAddPackageRequest", + "WorkspaceRemovePackageRequest", "WorkspaceGetRequest", "WorkspaceCheckRequest", "WorkspaceSearchQueryRequest", @@ -54,6 +56,18 @@ class WorkspaceDisableRequest(BaseModel): domain_id: str +class WorkspaceAddPackageRequest(BaseModel): + package_id: str + workspace_id: str + domain_id: str + + +class WorkspaceRemovePackageRequest(BaseModel): + package_id: str + workspace_id: str + domain_id: str + + class WorkspaceGetRequest(BaseModel): workspace_id: str domain_id: str diff --git a/src/spaceone/identity/model/workspace/response.py b/src/spaceone/identity/model/workspace/response.py index f65ec18f..571ff9f0 100644 --- a/src/spaceone/identity/model/workspace/response.py +++ b/src/spaceone/identity/model/workspace/response.py @@ -17,6 +17,7 @@ class WorkspaceResponse(BaseModel): created_by: Union[str, None] = None references: Union[list, None] = None is_managed: Union[bool, None] = None + packages: Union[list, None] = None is_dormant: Union[bool, None] = None dormant_ttl: Union[int, None] = None service_account_count: Union[int, None] = None diff --git a/src/spaceone/identity/service/package_service.py b/src/spaceone/identity/service/package_service.py new file mode 100644 index 00000000..e314ae2c --- /dev/null +++ b/src/spaceone/identity/service/package_service.py @@ -0,0 +1,259 @@ +import logging +from typing import Union + +from spaceone.core.service import * +from spaceone.core.service.utils import * +from spaceone.identity.error import * + +from spaceone.identity.manager.package_manager import PackageManager +from spaceone.identity.model.package.request import * +from spaceone.identity.model.package.response import * + +_LOGGER = logging.getLogger(__name__) + + +@authentication_handler +@authorization_handler +@mutation_handler +@event_handler +class PackageService(BaseService): + resource = "Package" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.package_mgr = PackageManager() + + @transaction(permission="identity:Package.write", role_types=["DOMAIN_ADMIN"]) + @convert_model + def create(self, params: PackageCreateRequest) -> Union[PackageResponse, dict]: + """Create package + Args: + params (dict): { + 'name': 'str', # required + 'description': 'str', + 'tags': 'dict', # required + 'domain_id': 'str' # injected from auth (required) + } + """ + + params_dict = params.dict() + params_dict["order"] = self._get_highest_order(params.domain_id) + 1 + + package_vo = self.package_mgr.create_package(params_dict) + return PackageResponse(**package_vo.to_dict()) + + @transaction(permission="identity:Package.write", role_types=["DOMAIN_ADMIN"]) + @convert_model + def update(self, params: PackageUpdateRequest) -> Union[PackageResponse, dict]: + """Update package + Args: + params (dict): { + 'package_id': 'str', # required + 'name': 'str', + 'description': 'str', + 'tags': 'dict', + 'domain_id': 'str' # injected from auth (required) + } + """ + package_vo = self.package_mgr.get_package( + params.package_id, + params.domain_id, + ) + + package_vo = self.package_mgr.update_package_by_vo( + params.dict(exclude_unset=True), package_vo + ) + + return PackageResponse(**package_vo.to_dict()) + + @transaction(permission="identity:Package.write", role_types=["DOMAIN_ADMIN"]) + @convert_model + def delete(self, params: PackageDeleteRequest) -> None: + """Delete package + Args: + params (dict): { + 'package_id': 'str', # required + 'domain_id': 'str' # injected from auth (required) + } + Returns: + None + """ + + package_vo = self.package_mgr.get_package( + params.package_id, + params.domain_id, + ) + self.package_mgr.delete_package_by_vo(package_vo) + + @transaction(permission="identity:Package.write", role_types=["DOMAIN_ADMIN"]) + @convert_model + def set_default( + self, params: PackageSetDefaultRequest + ) -> Union[PackageResponse, dict]: + """Set default package + Args: + params (dict): { + 'package_id': 'str', # required + 'domain_id': 'str' # injected from auth (required) + } + Returns: + PackageResponse: + """ + package_vos = self.package_mgr.filter_packages(domain_id=params.domain_id) + for package_vo in package_vos: + if package_vo.package_id == params.package_id: + self.package_mgr.update_package_by_vo({"is_default": True}, package_vo) + else: + self.package_mgr.update_package_by_vo({"is_default": False}, package_vo) + + package_vo = self.package_mgr.get_package( + params.package_id, + params.domain_id, + ) + + return PackageResponse(**package_vo.to_dict()) + + @transaction(permission="identity:Package.write", role_types=["DOMAIN_ADMIN"]) + @convert_model + def change_order( + self, params: PackageChangeOrderRequest + ) -> Union[PackageResponse, dict]: + """Change order of package + Args: + params (dict): { + 'package_id': 'str', # required + 'order': 'int', # required + 'domain_id': 'str' # injected from auth (required) + } + Returns: + PackageResponse: + """ + + package_vo = self.package_mgr.get_package( + params.package_id, + params.domain_id, + ) + + self._check_order(params.order, params.domain_id) + + new_order = params.order + old_order = package_vo.order + + if new_order > old_order: + package_vos = self.package_mgr.filter_packages(domain_id=params.domain_id) + for package_vo in package_vos: + if params.package_id == package_vo.package_id: + package_vo.update({"order": new_order}) + elif old_order < package_vo.order <= new_order: + package_vo.update({"order": package_vo.order - 1}) + + if new_order < old_order: + package_vos = self.package_mgr.filter_packages(domain_id=params.domain_id) + for package_vo in package_vos: + if params.package_id == package_vo.package_id: + package_vo.update({"order": new_order}) + elif new_order <= package_vo.order < old_order: + package_vo.update({"order": package_vo.order + 1}) + + package_vo = self.package_mgr.get_package( + params.package_id, + params.domain_id, + ) + + return PackageResponse(**package_vo.to_dict()) + + @transaction( + permission="identity:Package.read", + role_types=["DOMAIN_ADMIN", "WORKSPACE_OWNER", "WORKSPACE_MEMBER"], + ) + @convert_model + def get(self, params: PackageGetRequest) -> Union[PackageResponse, dict]: + """Get package + Args: + params (dict): { + 'package_id': 'str', # required + 'domain_id': 'str' # injected from auth (required) + } + Returns: + PackageResponse: + """ + + package_vo = self.package_mgr.get_package( + params.package_id, + params.domain_id, + ) + return PackageResponse(**package_vo.to_dict()) + + @transaction( + permission="identity:Package.read", + role_types=["DOMAIN_ADMIN", "WORKSPACE_OWNER", "WORKSPACE_MEMBER"], + ) + @append_query_filter( + [ + "package_id", + "name", + "domain_id", + ] + ) + @append_keyword_filter(["package_id", "name"]) + @convert_model + def list(self, params: PackageSearchQueryRequest) -> Union[PackagesResponse, dict]: + """List packages + Args: + params (dict): { + 'query': 'dict (spaceone.api.core.v1.Query)', + 'package_id': 'str', + 'name': 'str', + 'domain_id': 'str' # injected from auth (required) + } + Returns: + PackagesResponse: + """ + + query = params.query or {} + package_vos, total_count = self.package_mgr.list_packages(query) + + packages_info = [package_vo.to_dict() for package_vo in package_vos] + return PackagesResponse(results=packages_info, total_count=total_count) + + @transaction( + permission="identity:Package.read", + role_types=["DOMAIN_ADMIN", "WORKSPACE_OWNER", "WORKSPACE_MEMBER"], + ) + @append_query_filter(["domain_id"]) + @append_keyword_filter(["package_id", "name"]) + @convert_model + def stat(self, params: PackageStatQueryRequest) -> dict: + """Stat packages + Args: + params (dict): { + 'query': 'dict (spaceone.api.core.v1.StatisticsQuery)', + 'domain_id': 'str' # injected from auth (required) + } + Returns: + dict: { + 'results': 'list', + 'total_count': 'int' + } + """ + + query = params.query or {} + return self.package_mgr.stat_package(query) + + def _check_order(self, order: int, domain_id: str) -> None: + if order <= 0: + raise ERROR_INVALID_PARAMETER( + key="order", reason="The order must be greater than 0." + ) + + highest_order = self._get_highest_order(domain_id) + if order > highest_order: + raise ERROR_INVALID_PARAMETER( + key="order", + reason="The order is out of range.", + ) + + def _get_highest_order(self, domain_id: str): + package_vos = self.package_mgr.filter_packages(domain_id=domain_id) + + return package_vos.count() diff --git a/src/spaceone/identity/service/user_group_service.py b/src/spaceone/identity/service/user_group_service.py index d3d021cd..444da829 100644 --- a/src/spaceone/identity/service/user_group_service.py +++ b/src/spaceone/identity/service/user_group_service.py @@ -5,7 +5,7 @@ from spaceone.core.service.utils import * from spaceone.identity.error.error_user_group import * -from spaceone.identity.manager.user_manager import UserManager +from spaceone.identity.manager.role_binding_manager import RoleBindingManager from spaceone.identity.manager.user_group_manager import UserGroupManager from spaceone.identity.model.user_group.request import * from spaceone.identity.model.user_group.response import * @@ -109,16 +109,17 @@ def add_users( params.workspace_id, ) - user_mgr = UserManager() - users_vo = user_mgr.filter_users( + rb_mgr = RoleBindingManager() + rb_vos = rb_mgr.filter_role_bindings( user_id=params.users, + resource_group="WORKSPACE", domain_id=params.domain_id, workspace_id=params.workspace_id, ) - if users_vo.count() != len(params.users): + if rb_vos.count() != len(params.users): raise ERROR_USERS_NOT_FOUND( - users=list(set(params.users) - set(users_vo.values_list("user_id"))) + users=list(set(params.users) - set(rb_vos.values_list("user_id"))) ) params.users = list(set(user_group_vo.users + params.users)) @@ -194,7 +195,7 @@ def get(self, params: UserGroupGetRequest) -> Union[UserGroupResponse, dict]: "domain_id", ] ) - @append_keyword_filter(["trusted_account_id", "name"]) + @append_keyword_filter(["user_group_id", "name"]) @convert_model def list( self, params: UserGroupSearchQueryRequest diff --git a/src/spaceone/identity/service/workspace_service.py b/src/spaceone/identity/service/workspace_service.py index 05e4a1c3..990a3f41 100644 --- a/src/spaceone/identity/service/workspace_service.py +++ b/src/spaceone/identity/service/workspace_service.py @@ -2,7 +2,6 @@ from datetime import datetime from typing import Dict, List, Union -from spaceone.core.error import * from spaceone.core.service import * from spaceone.core.service.utils import * @@ -16,9 +15,11 @@ from spaceone.identity.manager.trusted_account_manager import TrustedAccountManager from spaceone.identity.manager.workspace_group_manager import WorkspaceGroupManager from spaceone.identity.manager.workspace_manager import WorkspaceManager +from spaceone.identity.manager.package_manager import PackageManager from spaceone.identity.model import Workspace from spaceone.identity.model.workspace.request import * from spaceone.identity.model.workspace.response import * +from spaceone.identity.error.error_workspace import * from spaceone.identity.service.role_binding_service import RoleBindingService _LOGGER = logging.getLogger(__name__) @@ -240,6 +241,72 @@ def disable( workspace_vo = self.workspace_mgr.disable_workspace(workspace_vo) return WorkspaceResponse(**workspace_vo.to_dict()) + @transaction(permission="identity:Workspace.write", role_types=["DOMAIN_ADMIN"]) + @convert_model + def add_package( + self, params: WorkspaceAddPackageRequest + ) -> Union[WorkspaceResponse, dict]: + """Add package to workspace + Args: + params (WorkspaceAddPackageRequest): { + 'package_id': 'str', # required + 'workspace_id': 'str', # required + 'domain_id': 'str' # injected from auth (required) + } + Returns: + WorkspaceResponse: + """ + + workspace_vo = self.workspace_mgr.get_workspace( + params.workspace_id, params.domain_id + ) + + package_mgr = PackageManager() + package_vo = package_mgr.get_package(params.package_id, params.domain_id) + + if package_vo.is_default: + raise ERROR_DEFAULT_PACKAGE_NOT_ALLOWED(package_id=params.package_id) + + packages = workspace_vo.packages or [] + if params.package_id not in packages: + packages.append(params.package_id) + + workspace_vo = self.workspace_mgr.update_workspace_by_vo( + {"packages": packages}, workspace_vo + ) + + return WorkspaceResponse(**workspace_vo.to_dict()) + + @transaction(permission="identity:Workspace.write", role_types=["DOMAIN_ADMIN"]) + @convert_model + def remove_package( + self, params: WorkspaceRemovePackageRequest + ) -> Union[WorkspaceResponse, dict]: + """Remove package from workspace + Args: + params (WorkspaceRemovePackageRequest): { + 'package_id': 'str', # required + 'workspace_id': 'str', # required + 'domain_id': 'str' # injected from auth (required) + } + Returns: + WorkspaceResponse: + """ + + workspace_vo = self.workspace_mgr.get_workspace( + params.workspace_id, params.domain_id + ) + + packages = workspace_vo.packages or [] + if params.package_id in packages: + packages.remove(params.package_id) + + workspace_vo = self.workspace_mgr.update_workspace_by_vo( + {"packages": packages}, workspace_vo + ) + + return WorkspaceResponse(**workspace_vo.to_dict()) + @transaction(permission="identity:Workspace.read", role_types=["DOMAIN_ADMIN"]) @convert_model def get(self, params: WorkspaceGetRequest) -> Union[WorkspaceResponse, dict]: @@ -315,11 +382,12 @@ def list( workspaces_info = [workspace_vo.to_dict() for workspace_vo in workspace_vos] else: - workspaces_info, total_count = ( - self.workspace_mgr.list_workspace_group_workspaces( - params.workspace_group_id, - params.domain_id, - ) + ( + workspaces_info, + total_count, + ) = self.workspace_mgr.list_workspace_group_workspaces( + params.workspace_group_id, + params.domain_id, ) return WorkspacesResponse(results=workspaces_info, total_count=total_count) @@ -427,11 +495,12 @@ def _add_workspace_to_group( domain_id, ) - workspaces_info, total_count = ( - self.workspace_mgr.list_workspace_group_workspaces( - old_workspace_group_id, - domain_id, - ) + ( + workspaces_info, + total_count, + ) = self.workspace_mgr.list_workspace_group_workspaces( + old_workspace_group_id, + domain_id, ) for workspace_info in workspaces_info: workspace_vo = self.workspace_mgr.get_workspace(