diff --git a/src/spaceone/inventory_v2/error/error_namespace.py b/src/spaceone/inventory_v2/error/error_namespace.py new file mode 100644 index 0000000..29828de --- /dev/null +++ b/src/spaceone/inventory_v2/error/error_namespace.py @@ -0,0 +1,11 @@ +from spaceone.core.error import * + + +class ERROR_RELATED_NAMESPACE_GROUP(ERROR_INVALID_ARGUMENT): + _message = ( + "Related namespace_group is exist. (namespace_group_id = {namespace_group_id})" + ) + + +class ERROR_NOT_ALLOWED_ADD_USER_TO_PUBLIC_PROJECT(ERROR_INVALID_ARGUMENT): + _message = "Not allowed to add user to public project." \ No newline at end of file diff --git a/src/spaceone/inventory_v2/error/error_namespace_group.py b/src/spaceone/inventory_v2/error/error_namespace_group.py new file mode 100644 index 0000000..d88ba31 --- /dev/null +++ b/src/spaceone/inventory_v2/error/error_namespace_group.py @@ -0,0 +1,24 @@ + +from spaceone.core.error import * + + +class ERROR_REQUIRED_PARAMETER(ERROR_INVALID_ARGUMENT): + _message = "Required parameter. (key = {key})" + + +class ERROR_RELATED_NAMESPACE_EXIST(ERROR_INVALID_ARGUMENT): + _message = "Related namespace is exist. (namespace_id = {namespace_id})" + + +class ERROR_RELATED_NAMESPACE_GROUP_EXIST(ERROR_INVALID_ARGUMENT): + _message = "Related namespace group is exist. (namespace_group_id = {namespace_group_id})" + + +class ERROR_NOT_ALLOWED_TO_CHANGE_PARENT_GROUP_TO_SUB_PROJECT_GROUP(ERROR_INVALID_ARGUMENT): + _message = "Not allowed to change parent group to sub namespace group. (namespace_group_id = {namespace_group_id})" + + +class ERROR_USER_NOT_IN_PROJECT_GROUP(ERROR_PERMISSION_DENIED): + _message = "{namespace_id} is not in namespace group." + + \ No newline at end of file diff --git a/src/spaceone/inventory_v2/interface/grpc/__init__.py b/src/spaceone/inventory_v2/interface/grpc/__init__.py index 45857a9..7f77806 100644 --- a/src/spaceone/inventory_v2/interface/grpc/__init__.py +++ b/src/spaceone/inventory_v2/interface/grpc/__init__.py @@ -3,6 +3,8 @@ from .collector import Collector from .job import Job from .job_task import JobTask +from .namespace_group import NamespaceGroup +from .namespace import Namespace _all_ = ["app"] @@ -11,3 +13,5 @@ app.add_service(Collector) app.add_service(Job) app.add_service(JobTask) +app.add_service(NamespaceGroup) +app.add_service(Namespace) \ No newline at end of file diff --git a/src/spaceone/inventory_v2/interface/grpc/namespace.py b/src/spaceone/inventory_v2/interface/grpc/namespace.py new file mode 100644 index 0000000..0af4a9b --- /dev/null +++ b/src/spaceone/inventory_v2/interface/grpc/namespace.py @@ -0,0 +1,46 @@ +from google.protobuf.json_format import ParseDict +from spaceone.api.inventory_v2.v1 import namespace_pb2, namespace_pb2_grpc +from spaceone.core.pygrpc import BaseAPI + +from spaceone.inventory_v2.service.namespace_service import NamespaceService + +class Namespace(BaseAPI, namespace_pb2_grpc.NamespaceServicer): + + pb2 = namespace_pb2 + pb2_grpc = namespace_pb2_grpc + + def create(self, request, context): + params, metadata = self.parse_request(request, context) + namespace_svc = NamespaceService(metadata) + response: dict = namespace_svc.create(params) + return self.dict_to_message(response) + + def update(self, request, context): + params, metadata = self.parse_request(request, context) + namespace_svc = NamespaceService(metadata) + response: dict = namespace_svc.update(params) + return self.dict_to_message(response) + + def delete(self, request, context): + params, metadata = self.parse_request(request, context) + namespace_svc = NamespaceService(metadata) + response: dict = namespace_svc.delete(params) + return self.empty() + + def get(self, request, context): + params, metadata = self.parse_request(request, context) + namespace_svc = NamespaceService(metadata) + response: dict = namespace_svc.get(params) + return self.dict_to_message(response) + + def list(self, request, context): + params, metadata = self.parse_request(request, context) + namespace_svc = NamespaceService(metadata) + response: dict = namespace_svc.list(params) + return self.dict_to_message(response) + + def stat(self, request, context): + params, metadata = self.parse_request(request, context) + namespace_svc = NamespaceService(metadata) + response: dict = namespace_svc.stat(params) + return self.dict_to_message(response) \ No newline at end of file diff --git a/src/spaceone/inventory_v2/interface/grpc/namespace_group.py b/src/spaceone/inventory_v2/interface/grpc/namespace_group.py new file mode 100644 index 0000000..62e902c --- /dev/null +++ b/src/spaceone/inventory_v2/interface/grpc/namespace_group.py @@ -0,0 +1,47 @@ +from google.protobuf.json_format import ParseDict +from spaceone.api.inventory_v2.v1 import namespace_group_pb2, namespace_group_pb2_grpc +from spaceone.core.pygrpc import BaseAPI + +from spaceone.inventory_v2.service.namespace_group_service import NamespaceGroupService + + +class NamespaceGroup(BaseAPI, namespace_group_pb2_grpc.NamespaceGroupServicer): + + pb2 = namespace_group_pb2 + pb2_grpc = namespace_group_pb2_grpc + + def create(self, request, context): + params, metadata = self.parse_request(request, context) + namespacegroup_svc = NamespaceGroupService(metadata) + response: dict = namespacegroup_svc.create(params) + return self.dict_to_message(response) + + def update(self, request, context): + params, metadata = self.parse_request(request, context) + namespacegroup_svc = NamespaceGroupService(metadata) + response: dict = namespacegroup_svc.update(params) + return self.dict_to_message(response) + + def delete(self, request, context): + params, metadata = self.parse_request(request, context) + namespacegroup_svc = NamespaceGroupService(metadata) + response: dict = namespacegroup_svc.delete(params) + return self.empty() + + def get(self, request, context): + params, metadata = self.parse_request(request, context) + namespacegroup_svc = NamespaceGroupService(metadata) + response: dict = namespacegroup_svc.get(params) + return self.dict_to_message(response) + + def list(self, request, context): + params, metadata = self.parse_request(request, context) + namespacegroup_svc = NamespaceGroupService(metadata) + response: dict = namespacegroup_svc.list(params) + return self.dict_to_message(response) + + def stat(self, request, context): + params, metadata = self.parse_request(request, context) + namespacegroup_svc = NamespaceGroupService(metadata) + response: dict = namespacegroup_svc.stat(params) + return self.dict_to_message(response) \ No newline at end of file diff --git a/src/spaceone/inventory_v2/manager/__init__.py b/src/spaceone/inventory_v2/manager/__init__.py index ba0d6c9..783fdf6 100644 --- a/src/spaceone/inventory_v2/manager/__init__.py +++ b/src/spaceone/inventory_v2/manager/__init__.py @@ -3,3 +3,5 @@ from spaceone.inventory_v2.lib.resource_manager import ResourceManager from spaceone.inventory_v2.manager.region_manager import RegionManager from spaceone.inventory_v2.manager.collecting_manager import CollectingManager +from spaceone.inventory_v2.manager.namespace_group_manager import NamespaceGroupManager +from spaceone.inventory_v2.manager.namespace_manager import NamespaceManager diff --git a/src/spaceone/inventory_v2/manager/namespace_group_manager.py b/src/spaceone/inventory_v2/manager/namespace_group_manager.py new file mode 100644 index 0000000..114596e --- /dev/null +++ b/src/spaceone/inventory_v2/manager/namespace_group_manager.py @@ -0,0 +1,120 @@ +import logging +from operator import is_ +from re import U +from typing import Tuple, List, Optional, Union +from fastapi.background import P +from mongoengine import QuerySet +from spaceone.core import utils, cache + + +from spaceone.core.manager import BaseManager +from spaceone.inventory_v2.error.error_namespace_group import * +from spaceone.inventory_v2.manager.namespace_manager import NamespaceManager +from spaceone.inventory_v2.model.namespace_group.database import NamespaceGroup +from spaceone.inventory_v2.model.namespace.database import Namespace +from spaceone.inventory_v2.model.namespace.response import NamespaceResponse + +_LOGGER = logging.getLogger(__name__) + + +class NamespaceGroupManager(BaseManager): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.namespace_group_model = NamespaceGroup + self.namespace_model = Namespace + + def create_namespace_group(self, params: dict) -> NamespaceGroup: + def _rollback(vo: NamespaceGroup): + _LOGGER.info( + f"[ROLLBACK] Delete namespace_group : {vo.name} ({vo.namespace_group_id})" + ) + vo.delete() + + if "namespace_group_id" not in params: + params["namespace_group_id"] = utils.generate_id("nsg") + if "is_managed" not in params: + params["is_managed"] = False + + namespace_group_vo= self.namespace_group_model.create(params) + self.transaction.add_rollback(_rollback, namespace_group_vo) + + return namespace_group_vo + + def update_namespace_group_by_vo( + self, + params: dict, + namespace_group_vo: NamespaceGroup + ) -> NamespaceGroup: + def _rollback(old_data): + _LOGGER.info( + f'[ROLLBACK] Revert Data : {old_data["name"]} ({old_data["namespace_group_id"]})' + ) + namespace_group_vo.update(old_data) + + self.transaction.add_rollback(_rollback, namespace_group_vo.to_dict()) + return namespace_group_vo.update(params) + + # @staticmethod + def delete_namespace_group_by_vo( + self, + namespace_group_vo: NamespaceGroup + ) -> None: + + namespace_mgr = NamespaceManager() + namespace_vos = namespace_mgr.filter_namespaces( + namespace_group_id=namespace_group_vo.namespace_group_id, + domain_id = namespace_group_vo.domain_id, + ) + + for namespace_vo in namespace_vos: + raise ERROR_RELATED_NAMESPACE_EXIST(namespace_id=namespace_vo.namespace_id) + + namespace_group_vo.delete() + + def get_namespace_group( + self, + namespace_group_id: str, + domain_id: str, + workspace_id: Union[list, str, None] = None, + + ) -> NamespaceGroup: + conditions:dict = { + "namespace_group_id": namespace_group_id, + "domain_id": domain_id, + } + + if workspace_id: + conditions.update({"workspace_id": workspace_id}) + + return self.namespace_group_model.get(**conditions) + + + def filter_namespace_groups(self, **conditions) -> QuerySet: + return self.namespace_group_model.filter(**conditions) + + def list_namespace_groups(self, query: dict, domain_id:str) -> Tuple[QuerySet, int]: + return self.namespace_group_model.query(**query) + + def stat_namespace_groups(self, query: dict) -> dict: + result = self.namespace_group_model.stat(**query) + return result if result is not None else {} + + # def get_namespaces_in_namespace_group( + # self, + # namespace_group_id: str, + # ) -> NamespaceResponse: + + # namespace_vos = self.namespace_model.filter( + # namespace_group_id=namespace_group_id + # ) + # namespaces = [namespace_vo.namespace_id for namespace_vo in namespace_vos] + + # child_namespace_groups = self.namespace_group_model.filter( + # parent_group_id=namespace_group_id + # ) + # for child_namespace_group in child_namespace_groups: + # parent_group_id = child_namespace_group.namespace_group_id + # namespaces.extend( + # self.get_namespaces_in_namespace_group(parent_group_id) + # ) + # return list(set(namespaces)) diff --git a/src/spaceone/inventory_v2/manager/namespace_manager.py b/src/spaceone/inventory_v2/manager/namespace_manager.py new file mode 100644 index 0000000..152550f --- /dev/null +++ b/src/spaceone/inventory_v2/manager/namespace_manager.py @@ -0,0 +1,79 @@ +import logging +from typing import Tuple,List, Optional, Union +from anyio import Condition +from mongoengine import QuerySet + +from spaceone.core.manager import BaseManager +from spaceone.inventory_v2.error.error_namespace import * +from spaceone.inventory_v2.model.namespace.database import Namespace + +_LOGGER = logging.getLogger(__name__) + + +class NamespaceManager(BaseManager): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.namespace_model = Namespace + + + def create_namespace(self, params:dict) -> Namespace: + def _rollback(vo: Namespace): + _LOGGER.info(f"[ROLLBACK] Delete namespace : {vo.name} ({vo.namespace_id}) ({vo.namespace_group_id})") + vo.delete() + + if "namespace_group_id" not in params: + raise ERROR_REQUIRED_PARAMETER(key="namespace_group_id") + + namespace_vo: Namespace = self.namespace_model.create(params) + self.transaction.add_rollback(_rollback, namespace_vo) + + return namespace_vo + + def update_namespace_by_vo( + self, + params: dict, + namespace_vo: Namespace + ) -> Namespace: + def _rollback(old_data): + _LOGGER.info(f'[ROLLBACK] Revert Data : {old_data["name"]} ({old_data["namespace_id"]}) ({old_data["namespace_group_id"]})') + + namespace_vo.update(old_data) + + self.transaction.add_rollback(_rollback, namespace_vo.to_dict()) + + return namespace_vo.update(params) + + def delete_namespace_by_vo( + self, + namespace_vo: Namespace + ) -> None: + namespace_vo.delete() + + def get_namespace( + self, + namespace_id: str, + domain_id: str, + workspace_id: Union[list, str, None] = None, + ) -> Namespace: + + conditions: dict = { + "namespace_id": namespace_id, + "domain_id": domain_id + } + + if workspace_id: + conditions.update({"workspace_id": workspace_id}) + + return self.namespace_model.get(**conditions) + + def filter_namespaces(self, **conditions) -> QuerySet: + return self.namespace_model.filter(**conditions) + + def list_namespaces(self, query: dict) -> Tuple[QuerySet, int]: + return self.namespace_model.query(**query) + + def stat_namespaces(self, query: dict) -> dict: + return self.namespace_model.stat(**query) + + + diff --git a/src/spaceone/inventory_v2/model/__init__.py b/src/spaceone/inventory_v2/model/__init__.py index 3602513..6755fe4 100644 --- a/src/spaceone/inventory_v2/model/__init__.py +++ b/src/spaceone/inventory_v2/model/__init__.py @@ -4,8 +4,10 @@ from spaceone.inventory_v2.model.collector.database import Collector from spaceone.inventory_v2.model.collector_rule.database import CollectorRule from spaceone.inventory_v2.model.collection_state.database import CollectionState +from spaceone.inventory_v2.model.namespace.database import Namespace +from spaceone.inventory_v2.model.namespace_group.database import NamespaceGroup from spaceone.inventory_v2.model.metric.database import Metric from spaceone.inventory_v2.model.metric_data.database import MetricData from spaceone.inventory_v2.model.metric_example.database import MetricExample from spaceone.inventory_v2.model.job.database import Job -from spaceone.inventory_v2.model.job_task.database import JobTask, JobTaskDetail +from spaceone.inventory_v2.model.job_task.database import JobTask, JobTaskDetail \ No newline at end of file diff --git a/src/spaceone/inventory_v2/model/namespace/__init__.py b/src/spaceone/inventory_v2/model/namespace/__init__.py new file mode 100644 index 0000000..d939915 --- /dev/null +++ b/src/spaceone/inventory_v2/model/namespace/__init__.py @@ -0,0 +1 @@ +from spaceone.inventory_v2.model.namespace import * \ No newline at end of file diff --git a/src/spaceone/inventory_v2/model/namespace/database.py b/src/spaceone/inventory_v2/model/namespace/database.py new file mode 100644 index 0000000..3bc37cd --- /dev/null +++ b/src/spaceone/inventory_v2/model/namespace/database.py @@ -0,0 +1,31 @@ +from unicodedata import category +from mongoengine import * +from typing import Dict, List, Literal, Union +from spaceone.core.model.mongo_model import MongoModel + + +class Namespace(MongoModel): + namespace_id = StringField(max_length=40, unique_with="domain_id") + name = StringField(max_length=255) + category = StringField(max_length=40, default=None, null=True) + icon = StringField(default=None, null=True) + tag = DictField(default=None) + is_managed = BooleanField(null=True) + resource_group = StringField(max_length=40, choices=("DOMAIN", "WORKSPACE")) + namespace_group_id = StringField(max_length=40) + 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", "icon", "tag", "updated_at"], + "minimal_fields": ["namespace_group_id", "name", "workspace_id","domain_id"], + "ordering": ["domain_id","workspace_id","namespace_group_id", "name"], + "indexes": [ + "domain_id", + "workspace_id", + "namespace_group_id", + "name", + ], + } diff --git a/src/spaceone/inventory_v2/model/namespace/request.py b/src/spaceone/inventory_v2/model/namespace/request.py new file mode 100644 index 0000000..2ebadac --- /dev/null +++ b/src/spaceone/inventory_v2/model/namespace/request.py @@ -0,0 +1,64 @@ +from typing import Dict, List, Literal, Union +from unicodedata import category + +from pydantic import BaseModel +from spaceone.inventory_v2.model import namespace_group + +__all__ = [ + "NamespaceCreateRequest", + "NamespaceUpdateRequest", + "NamespaceDeleteRequest", + "NamespaceGetRequest", + "NamespaceSearchQueryRequest", + "NamespaceStatQueryRequest", + "ResourceGroup", +] + + +ResourceGroup = Literal["DOMAIN", "WORKSPACE"] + +class NamespaceCreateRequest(BaseModel): + namespace_id: Union[str, None] = None + name: str + category: str + icon: Union[str, None] = None + tags: Union[Dict, None] = None + resource_group: ResourceGroup + namespace_group_id: str + workspace_id: Union[str,None] = None + domain_id: str + + +class NamespaceUpdateRequest(BaseModel): + namespace_id: str + name: Union[str, None] = None + icon: Union[str, None] = None + tags: Union[Dict, None] = None + workspace_id: Union[list,str,None] = None + domain_id: str + + +class NamespaceDeleteRequest(BaseModel): + namespace_id: str + workspace_id:Union[list,str,None] = None + domain_id: str + + +class NamespaceGetRequest(BaseModel): + namespace_id: str + workspace_id:Union[list,str,None] = None + domain_id: str + + + +class NamespaceSearchQueryRequest(BaseModel): + query: Union[dict, None] = None + namespace_id: Union[str, None] = None + workspace_id:Union[list,str,None] = None + domain_id: str + + +class NamespaceStatQueryRequest(BaseModel): + query: dict + workspace_id:Union[list,str,None] = None + domain_id: str \ No newline at end of file diff --git a/src/spaceone/inventory_v2/model/namespace/response.py b/src/spaceone/inventory_v2/model/namespace/response.py new file mode 100644 index 0000000..445c084 --- /dev/null +++ b/src/spaceone/inventory_v2/model/namespace/response.py @@ -0,0 +1,34 @@ +from datetime import datetime +from typing import Union, List +from pydantic import BaseModel +from spaceone.core import utils +from spaceone.inventory_v2.model.namespace.request import ResourceGroup + + +__all__ = ["NamespaceResponse", "NamespacesResponse"] + + +class NamespaceResponse(BaseModel): + namespace_id: Union[str, None] = None + name: Union[str, None] = None + category: Union[str, None] = None + icon: Union[str, None] = None + tags: Union[dict, None] = None + is_managed: Union[bool, None] = None + resource_group: Union[ResourceGroup, None] = None + namespace_group_id: Union[str, 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["updated_at"]) + return data + + +class NamespacesResponse(BaseModel): + results: List[NamespaceResponse] = [] + total_count: int diff --git a/src/spaceone/inventory_v2/model/namespace_group/__init__.py b/src/spaceone/inventory_v2/model/namespace_group/__init__.py new file mode 100644 index 0000000..2006a3d --- /dev/null +++ b/src/spaceone/inventory_v2/model/namespace_group/__init__.py @@ -0,0 +1 @@ +from spaceone.inventory_v2.model.namespace_group import * \ No newline at end of file diff --git a/src/spaceone/inventory_v2/model/namespace_group/database.py b/src/spaceone/inventory_v2/model/namespace_group/database.py new file mode 100644 index 0000000..2d968d9 --- /dev/null +++ b/src/spaceone/inventory_v2/model/namespace_group/database.py @@ -0,0 +1,28 @@ +from email.policy import default +from mongoengine import * +from spaceone.core.model.mongo_model import MongoModel + + +class NamespaceGroup(MongoModel): + namespace_group_id = StringField(max_length=80, unique_with="domain_id") + name = StringField(max_length=255) + icon = StringField(default=None, null=True) + description = StringField(max_length=255, null=True, default=None) + tags = DictField(default=None) + is_managed = BooleanField(null=True) + resource_group = StringField(max_length=40, choices=("DOMAIN", "WORKSPACE")) + 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", "icon", "description", "tags", "updated_at"], + "minimal_fields": ["namespace_group_id", "name", "workspace_id", "domain_id"], + "ordering": ["domain_id","workspace_id","-created_at"], + "indexes": [ + "domain_id", + "workspace_id", + "name", + ], + } diff --git a/src/spaceone/inventory_v2/model/namespace_group/request.py b/src/spaceone/inventory_v2/model/namespace_group/request.py new file mode 100644 index 0000000..78220a0 --- /dev/null +++ b/src/spaceone/inventory_v2/model/namespace_group/request.py @@ -0,0 +1,66 @@ +from re import U +from typing import Dict, List, Literal, Union +from unicodedata import category + +from pydantic import BaseModel +from spaceone.inventory_v2.model import namespace_group + +__all__ = [ + "NamespaceGroupCreateRequest", + "NamespaceGroupUpdateRequest", + "NamespaceGroupDeleteRequest", + "NamespaceGroupGetRequest", + "NamespaceGroupSearchQueryRequest", + "NamespaceGroupStatQueryRequest", +] + +ResourceGroup = Literal["DOMAIN", "WORKSPACE"] + +class NamespaceGroupCreateRequest(BaseModel): + namespace_group_id: Union[str, None] = None + name: str + icon: str + description: Union[str, None] = None + tags: Union[dict, None] = {} + resource_group: ResourceGroup + workspace_id: Union[str,None] = None + domain_id:str + + +class NamespaceGroupUpdateRequest(BaseModel): + namespace_group_id: str + name: Union[str, None] = None + icon: Union[str, None] = None + description: Union[str, None] = None + tags: Union[dict, None] = None + workspace_id: Union[str,None] = None + domain_id:str + + +class NamespaceGroupDeleteRequest(BaseModel): + namespace_group_id: str + resource_group: ResourceGroup + workspace_id: Union[str,None] = None + domain_id:str + + +class NamespaceGroupGetRequest(BaseModel): + namespace_group_id: str + resource_group: ResourceGroup + workspace_id: Union[str,None] = None + domain_id:str + + + +class NamespaceGroupSearchQueryRequest(BaseModel): + query: Union[dict, None] = None + namespace_group_id: Union[str, None] = None + exists_only: Union[bool, None] = None + workspace_id:Union[list,str,None] = None + domain_id:str + + +class NamespaceGroupStatQueryRequest(BaseModel): + query: dict + workspace_id:Union[list,str,None] = None + domain_id: str \ No newline at end of file diff --git a/src/spaceone/inventory_v2/model/namespace_group/response.py b/src/spaceone/inventory_v2/model/namespace_group/response.py new file mode 100644 index 0000000..3b1008e --- /dev/null +++ b/src/spaceone/inventory_v2/model/namespace_group/response.py @@ -0,0 +1,33 @@ +from datetime import datetime +from typing import Union, List +from pydantic import BaseModel +from spaceone.core import utils + +from spaceone.inventory_v2.model.namespace_group.request import ResourceGroup + +__all__ = ["NamespaceGroupResponse", "NamespaceGroupsResponse"] + + +class NamespaceGroupResponse(BaseModel): + namespace_group_id: Union[str, None] = None + name: Union[str, None] = None + icon: Union[str, None] = None + description: Union[str, None] = None + tags: Union[dict, None] = None + resource_group: Union[ResourceGroup, None] = None + is_managed: Union[bool, 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["updated_at"]) + return data + + +class NamespaceGroupsResponse(BaseModel): + results: List[NamespaceGroupResponse] = [] + total_count: int diff --git a/src/spaceone/inventory_v2/service/__init__.py b/src/spaceone/inventory_v2/service/__init__.py index 607f444..f815016 100644 --- a/src/spaceone/inventory_v2/service/__init__.py +++ b/src/spaceone/inventory_v2/service/__init__.py @@ -1,3 +1,7 @@ from spaceone.inventory_v2.service.asset_service import AssetService from spaceone.inventory_v2.service.asset_type_service import AssetTypeService from spaceone.inventory_v2.service.region_service import RegionService +from spaceone.inventory_v2.service.namespace_group_service import NamespaceGroupService +from spaceone.inventory_v2.service.namespace_service import NamespaceService + + diff --git a/src/spaceone/inventory_v2/service/namespace_group_service.py b/src/spaceone/inventory_v2/service/namespace_group_service.py new file mode 100644 index 0000000..b6bad8b --- /dev/null +++ b/src/spaceone/inventory_v2/service/namespace_group_service.py @@ -0,0 +1,234 @@ +import logging +from typing import Union + +from spaceone.core.service import * +from spaceone.core.service.utils import * +from spaceone.core.error import * + +from spaceone.inventory_v2.manager.identity_manager import IdentityManager +from spaceone.inventory_v2.manager.namespace_group_manager import NamespaceGroupManager +from spaceone.inventory_v2.manager.namespace_manager import NamespaceManager +from spaceone.inventory_v2.model.namespace_group.request import * +from spaceone.inventory_v2.model.namespace_group.response import * + +from spaceone.inventory_v2.error.error_namespace_group import * + +_LOGGER = logging.getLogger(__name__) +_KEYWORD_FILTER = ["namepspace_group_id", "name"] + +@authentication_handler +@authorization_handler +@mutation_handler +@event_handler +class NamespaceGroupService(BaseService): + resource = "NamespaceGroup" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.namespace_group_mgr = NamespaceGroupManager() + self.namespace_mgr = NamespaceManager() + self.identity_mgr = IdentityManager() + + @transaction( + permission="inventory-v2:NamespaceGroup.write", + role_types=["DOMAIN_ADMIN", "WORKSPACE_OWNER"], + ) + @convert_model + def create(self, params: NamespaceGroupCreateRequest) -> Union[NamespaceGroupResponse, dict]: + """Create namespace Group + + Args: + params (dict): { + 'namespace_group_id': 'str', + 'name': 'str', # required + 'icon': 'str', # required + 'description': 'str', + 'tags': 'dict', + 'resource_group': 'str', # required + 'workspace_id': 'str', # injected from auth + 'domain_id': 'str', # injected from auth (required) + } + + Returns: + NamespaceGroupResponse: + """ + + domain_id = params.domain_id + workspace_id = params.workspace_id + resource_group = params.resource_group + + # Check permission by resource group + if resource_group == "WORKSPACE": + if workspace_id is None: + raise ERROR_REQUIRED_PARAMETER(key="workspace_id") + + self.identity_mgr.check_workspace(workspace_id, domain_id) + else: + params.workspace_id = "*" + + namespace_group_vo = self.namespace_group_mgr.create_namespace_group(params.dict()) + return NamespaceGroupResponse(**namespace_group_vo.to_dict()) + + @transaction( + permission="inventory-v2:NamespaceGroup.write", + role_types=["DOMAIN_ADMIN", "WORKSPACE_OWNER"], + ) + @convert_model + def update(self, params: NamespaceGroupUpdateRequest) -> Union[NamespaceGroupResponse, dict]: + """Update namespace Group + + Args: + params (dict): { + 'namespace_group_id': 'str', # required + 'name': 'str', + 'icon': 'str', + 'description': 'str', + 'tags': 'dict', + 'workspace_id': 'str', # injected from auth + 'domain_id': 'str', # injected from auth (required) + } + + Returns: + NamespaceGroupResponse: + """ + + namespace_group_vo = self.namespace_group_mgr.get_namespace_group( + params.namespace_group_id, + params.domain_id, + workspace_id=params.workspace_id, + ) + + if namespace_group_vo.is_managed: + raise ERROR_PERMISSION_DENIED() + + namespace_group_vo = self.namespace_group_mgr.update_namespace_group_by_vo( + params.dict(exclude_unset=True), namespace_group_vo + ) + + return NamespaceGroupResponse(**namespace_group_vo.to_dict()) + + @transaction( + permission="inventory-v2:NamespaceGroup.write", + role_types=["DOMAIN_ADMIN", "WORKSPACE_OWNER"], + ) + @convert_model + def delete(self, params: NamespaceGroupDeleteRequest) -> None: + """Delete namespace Group + + Args: + params (dict): { + 'namespace_id': 'str', # required + 'workspace_id': 'str', # injected from auth + 'domain_id': 'str', # injected from auth (required) + } + + Returns: + None + """ + + namespace_group_vo = self.namespace_group_mgr.get_namespace_group( + namespace_group_id=params.namespace_group_id, + domain_id=params.domain_id, + workspace_id=params.workspace_id, + ) + + if namespace_group_vo.is_managed: + raise ERROR_PERMISSION_DENIED() + + self.namespace_group_mgr.delete_namespace_group_by_vo(namespace_group_vo=namespace_group_vo) + + @transaction( + permission="inventory-v2:NamespaceGroup.read", + role_types=["DOMAIN_ADMIN", "WORKSPACE_OWNER", "WORKSPACE_MEMBER"], + ) + @change_value_by_rule("APPEND", "workspace_id","*") + @convert_model + def get(self, params: NamespaceGroupGetRequest) -> Union[NamespaceGroupResponse, dict]: + """Get namespace Group + + Args: + params (dict): { + 'namespace_group_id': 'str', # required + 'workspace_id': 'str', # injected from auth + 'domain_id': 'str', # injected from auth (required) + } + + Returns: + NamespaceGroupResponse: + """ + + namespace_vo = self.namespace_group_mgr.get_namespace_group( + namespace_group_id=params.namespace_group_id, + domain_id=params.domain_id, + workspace_id = params.workspace_id, + ) + + return NamespaceGroupResponse(**namespace_vo.to_dict()) + + + @transaction( + permission="inventory-v2:NamespaceGroup.read", + role_types=["DOMAIN_ADMIN", "WORKSPACE_OWNER", "WORKSPACE_MEMBER"], + ) + @append_query_filter( + [ + "namespace_group_id", + "is_managed", + "domain_id", + ] + ) + @append_keyword_filter(["namespace_group_id", "name"]) + @change_value_by_rule("APPEND", "workspace_id","*") + @convert_model + def list( + self, params: NamespaceGroupSearchQueryRequest + ) -> Union[NamespaceGroupsResponse, dict]: + """List namespaces group + + Args: + params (dict): { + 'query': 'dict (spaceone.api.core.v1.Query)', + 'namespace_group_id': 'str', + 'exists_only': 'bool', + 'workspace_id': 'str', # injected from auth + 'domain_id': 'str', # injected from auth (required) + } + + Returns: + NamespacesGroupResponse: + """ + + + query = params.query or {} + namespace_group_vos, total_count = self.namespace_group_mgr.list_namespace_groups( + query, params.domain_id, + ) + + namespaces_group_info = [namespace_group_vo.to_dict() for namespace_group_vo in namespace_group_vos] + return NamespaceGroupsResponse(results=namespaces_group_info, total_count=total_count) + + @transaction( + permission="inventory-v2:NamespaceGroup.read", + role_types=["DOMAIN_ADMIN", "WORKSPACE_OWNER", "WORKSPACE_MEMBER"], + ) + @append_query_filter(["domain_id"]) + @append_keyword_filter(["namespace_group_id", "name"]) + @convert_model + def stat(self, params: NamespaceGroupStatQueryRequest) -> dict: + """ + Args: + params (dict): { + 'query': 'dict (spaceone.api.core.v1.StatisticsQuery)', # required + 'workspace_id': 'list', # injected from auth + 'domain_id': 'str', # injected from auth (required) + } + + Returns: + dict: { + 'results': 'list', + 'total_count': 'int' + } + """ + + query = params.query or {} + return self.namespace_group_mgr.stat_namespace_groups(query) \ No newline at end of file diff --git a/src/spaceone/inventory_v2/service/namespace_service.py b/src/spaceone/inventory_v2/service/namespace_service.py new file mode 100644 index 0000000..ef133c3 --- /dev/null +++ b/src/spaceone/inventory_v2/service/namespace_service.py @@ -0,0 +1,235 @@ +import logging +from typing import Union + +from pkg_resources import working_set +from spaceone.core.service import * +from spaceone.core.service.utils import * +from spaceone.core.error import * + +from spaceone.inventory_v2.model.namespace.request import * +from spaceone.inventory_v2.model.namespace.response import * +from spaceone.inventory_v2.manager.namespace_manager import NamespaceManager +from spaceone.inventory_v2.manager.identity_manager import IdentityManager + +_LOGGER = logging.getLogger(__name__) + + +@authentication_handler +@authorization_handler +@mutation_handler +@event_handler +class NamespaceService(BaseService): + resource = "Namespace" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.namespace_mgr = NamespaceManager() + self.identity_mgr = IdentityManager() + + @transaction( + permission="inventory-v2:Namespace.write", + role_types=["DOMAIN_ADMIN", "WORKSPACE_OWNER"], + ) + @convert_model + def create(self, params: NamespaceCreateRequest) -> Union[NamespaceResponse, dict]: + """Create namespace + + Args: + params (dict): { + 'namespace_id': 'str', + 'name': 'str', # required + 'category': 'str', # required + 'icon': 'str', + 'tags': 'dict', + 'resource_group': 'str', # required + 'namespace_group_id': 'str', # required + 'workspace_id': 'str', # injected from auth + 'domain_id': 'str', # injected from auth (required) + } + + Returns: + NamespaceResponse: + """ + + domain_id = params.domain_id + workspace_id = params.workspace_id + resource_group = params.resource_group + + # Check permission by resource group + if resource_group == "WORKSPACE": + if workspace_id is None: + raise ERROR_REQUIRED_PARAMETER(key="workspace_id") + + self.identity_mgr.check_workspace(workspace_id, domain_id) + else: + params.workspace_id = "*" + + namespace_vo = self.namespace_mgr.create_namespace(params.dict()) + return NamespaceResponse(**namespace_vo.to_dict()) + + @transaction( + permission="inventory-v2:Namespace.write", + role_types=["DOMAIN_ADMIN", "workSPACE_OWNER"], + ) + @convert_model + def update(self, params: NamespaceUpdateRequest) -> Union[NamespaceResponse, dict]: + """Update namespace + + Args: + params (dict): { + 'namespace_id': 'str', # required + 'name': 'str', + 'icon': 'str', + 'tags': 'dict', + 'domain_id': 'str', # injected from auth (required) + } + + Returns: + NamespaceResponse: + """ + + namespace_vo = self.namespace_mgr.get_namespace( + params.namespace_id, + params.domain_id, + workspace_id=params.workspace_id + ) + + if namespace_vo.is_managed: + raise ERROR_PERMISSION_DENIED() + + namespace_vo = self.namespace_mgr.update_namespace_by_vo( + params.dict(exclude_unset=True), namespace_vo + ) + + return NamespaceResponse(**namespace_vo.to_dict()) + + @transaction( + permission="inventory-v2:Namespace.write", + role_types=["DOMAIN_ADMIN", "WORKSPACE_OWNER"], + ) + @convert_model + def delete(self, params: NamespaceDeleteRequest) -> None: + """Delete namespace + + Args: + params (dict): { + 'namespace_id': 'str', # required + 'workspace_id': 'str', # injected from auth + 'domain_id': 'str', # injected from auth (required) + } + + Returns: + None + """ + + namespace_vo = self.namespace_mgr.get_namespace( + params.namespace_id, + params.domain_id, + workspace_id=params.workspace_id, + ) + + if namespace_vo.is_managed: + raise ERROR_PERMISSION_DENIED() + + self.namespace_mgr.delete_namespace_by_vo(namespace_vo) + + @transaction( + permission="inventory-v2:Namespace.read", + role_types=["DOMAIN_ADMIN", "WORKSPACE_OWNER", "WORKSPACE_MEMBER"], + ) + @change_value_by_rule("APPEND", "workspace_id","*") + @convert_model + def get(self, params: NamespaceGetRequest) -> Union[NamespaceResponse, dict]: + """Get namespace + + Args: + params (dict): { + 'namespace_id': 'str', # required + 'workspace_id': 'str', # injected from auth + 'domain_id': 'str', # injected from auth (required) + } + + Returns: + NamespaceResponse: + """ + + namespace_vo = self.namespace_mgr.get_namespace( + params.namespace_id, + params.domain_id, + workspace_id=params.workspace_id, + ) + + return NamespaceResponse(**namespace_vo.to_dict()) + + @transaction( + permission="inventory-v2:Namespace.read", + role_types=["DOMAIN_ADMIN", "WORKSPACE_OWNER", "WORKSPACE_MEMBER"], + ) + @append_query_filter( + [ + "namespace_id", + "category", + "is_managed", + "resource_type", + "namespace_group_id", + "domain_id", + ] + ) + @append_keyword_filter(["namespace_id", "name"]) + @change_value_by_rule("APPEND", "workspace_id","*") + @convert_model + def list( + self, params: NamespaceSearchQueryRequest + ) -> Union[NamespacesResponse, dict]: + """List namespaces + + Args: + params (dict): { + 'query': 'dict (spaceone.api.core.v1.Query)', + 'namespace_id': 'str', + 'category': 'str', + 'resource_type': 'str', + 'namespace_group_id': 'str', + 'workspace_id': 'str', # injected from auth + 'domain_id': 'str', # injected from auth (required) + } + + Returns: + NamespacesResponse: + """ + + query = params.query or {} + namespace_vos, total_count = self.namespace_mgr.list_namespaces( + query + ) + + namespaces_info = [namespace_vo.to_dict() for namespace_vo in namespace_vos] + return NamespacesResponse(results=namespaces_info, total_count=total_count) + + @transaction( + permission="inventory-v2:Namespace.read", + role_types=["DOMAIN_ADMIN", "WORKSPACE_OWNER", "WORKSPACE_MEMBER"], + ) + @change_value_by_rule("APPEND", "workspace_id", "*") + @append_query_filter(["workspace_id", "domain_id"]) + @append_keyword_filter(["namespace_id", "name"]) + @convert_model + def stat(self, params: NamespaceStatQueryRequest) -> dict: + """ + Args: + params (dict): { + 'query': 'dict (spaceone.api.core.v1.StatisticsQuery)', # required + 'workspace_id': 'list', # injected from auth + 'domain_id': 'str', # injected from auth (required) + } + + Returns: + dict: { + 'results': 'list', + 'total_count': 'int' + } + """ + + query = params.query or {} + return self.namespace_mgr.stat_namespaces(query) +