From 7663f6b9a129b4b59cd6783924c14587e44bd6f1 Mon Sep 17 00:00:00 2001 From: ImMin5 Date: Wed, 11 Dec 2024 00:08:01 +0900 Subject: [PATCH 1/3] feat: add metric service create api Signed-off-by: ImMin5 --- .../collector_plugin_v1_connector.py | 70 +++++++++++++------ .../inventory_v2/error/collect_data.py | 13 ++++ .../inventory_v2/interface/grpc/__init__.py | 2 + src/spaceone/inventory_v2/lib/rule_matcher.py | 12 ++-- .../inventory_v2/manager/asset_manager.py | 24 +++++-- .../manager/asset_type_manager.py | 4 +- .../manager/collecting_manager.py | 31 +++++--- .../manager/collector_rule_manager.py | 8 +-- .../inventory_v2/manager/identity_manager.py | 52 +++++++------- .../inventory_v2/manager/metric_manager.py | 32 +++------ .../inventory_v2/model/asset/database.py | 11 ++- .../inventory_v2/model/asset/request.py | 8 +-- .../inventory_v2/model/asset/response.py | 9 ++- .../inventory_v2/model/asset_type/database.py | 5 +- .../inventory_v2/model/metric/request.py | 2 +- .../inventory_v2/service/asset_service.py | 57 ++++++--------- .../service/asset_type_service.py | 21 ++---- .../inventory_v2/service/collector_service.py | 3 +- .../inventory_v2/service/metric_service.py | 1 + 19 files changed, 206 insertions(+), 159 deletions(-) create mode 100644 src/spaceone/inventory_v2/error/collect_data.py diff --git a/src/spaceone/inventory_v2/connector/collector_plugin_connector/collector_plugin_v1_connector.py b/src/spaceone/inventory_v2/connector/collector_plugin_connector/collector_plugin_v1_connector.py index 6e38391..8383c34 100644 --- a/src/spaceone/inventory_v2/connector/collector_plugin_connector/collector_plugin_v1_connector.py +++ b/src/spaceone/inventory_v2/connector/collector_plugin_connector/collector_plugin_v1_connector.py @@ -3,6 +3,7 @@ from typing import Generator from spaceone.core.connector.space_connector import SpaceConnector +from spaceone.core.error import ERROR_BASE from spaceone.inventory_v2.connector.collector_plugin_connector import ( BaseCollectorPluginConnector, @@ -48,12 +49,36 @@ def collect( yield self._convert_resource_data(resource_data) @staticmethod - def _convert_resource_data(resource_data: dict) -> dict: + def _convert_match_rule(resource_data: dict, resource_type: str) -> dict: + for rule_values in resource_data.get("match_rules", {}).values(): + for index, rule_value in enumerate(rule_values): + if rule_value == "cloud_service_id": + rule_values[index] = "asset_id" + elif rule_value == "cloud_service_type": + rule_values[index] = "asset_type_id" + elif rule_value == "cloud_service_group": + del rule_values[index] + elif rule_value == "reference.resource_id": + rule_values[index] = "resource_id" + elif rule_value == "group": + rule_values[index] = "asset_group_id" + elif rule_value == "name": + if resource_type == "inventory.AssetType": + rule_values[index] = "asset_type_id" + return resource_data + + def _convert_resource_data(self, resource_data: dict) -> dict: + + if "resource" in resource_data and "metadata" in resource_data["resource"]: + del resource_data["resource"]["metadata"] + _LOGGER.debug( f"[_convert_resource_data] before convert resource_data: {resource_data}" ) resource_type = resource_data.get("resource_type") + if resource_type == "inventory.Region": + pass if resource_type in ["inventory.CloudService", "inventory.CloudServiceType"]: if resource_type == "inventory.CloudService": @@ -70,31 +95,35 @@ def _convert_resource_data(resource_data: dict) -> dict: resource_type = resource_data.get("resource_type") # convert match rule - for rule_values in resource_data.get("match_rules", {}).values(): - for index, rule_value in enumerate(rule_values): - if rule_value == "cloud_service_id": - rule_values[index] = "asset_id" - elif rule_value == "cloud_service_type": - rule_values[index] = "asset_type_id" - elif rule_value == "cloud_service_group": - del rule_values[index] - elif rule_value == "reference.resource_id": - rule_values[index] = "resource_id" - elif rule_value == "group": - rule_values[index] = "asset_group_id" + resource_data = self._convert_match_rule(resource_data, resource_type) + # convert keywords [instance_size, instance_type, ] if _resource := resource_data.get("resource"): - _resource_v1 = {} + # del _resource["metadata"] + if "instance_size" in _resource: - _resource_v1["instance_size"] = _resource.pop("instance_size") + _resource["data"]["instance_size"] = _resource.pop("instance_size") if "instance_type" in _resource: - _resource_v1["instance_type"] = _resource.pop("instance_type") - del _resource["metadata"] + _resource["data"]["instance_type"] = _resource.pop("instance_type") + + if "region_code" in _resource: + _resource["region_id"] = ( + f"{_resource['provider']}-{_resource['region_code']}" + ) if resource_type == "inventory.Asset": - asset_type_id = f"{_resource['provider']}.{_resource['cloud_service_group']}.{_resource['cloud_service_type']}" + asset_type_id = f"{_resource['provider']}-{_resource['cloud_service_group']}-{_resource['cloud_service_type']}" _resource["asset_type_id"] = asset_type_id resource_data["asset_type_id"] = asset_type_id + + if "reference" in _resource: + _resource["resource_id"] = _resource["reference"].get( + "resource_id" + ) + _resource["external_link"] = _resource["reference"].get( + "external_link" + ) + elif resource_type == "inventory.AssetType": asset_type_id = f"at-{_resource['provider']}-{_resource['group']}-{_resource['name']}" @@ -105,9 +134,10 @@ def _convert_resource_data(resource_data: dict) -> dict: _resource["asset_type_id"] = asset_type_id resource_data["asset_type_id"] = asset_type_id resource_data["asset_groups"] = asset_groups - resource_data["icon"] = _resource.get("tags", {}).get("icon", "") - resource_data["resource"]["v1"] = _resource_v1 + resource_data["icon"] = resource_data.get("tags", {}).get( + "spaceone:icon", "" + ) _LOGGER.debug(f"[_convert_resource_data] resource_data: {resource_data}") diff --git a/src/spaceone/inventory_v2/error/collect_data.py b/src/spaceone/inventory_v2/error/collect_data.py new file mode 100644 index 0000000..ef740ee --- /dev/null +++ b/src/spaceone/inventory_v2/error/collect_data.py @@ -0,0 +1,13 @@ +from spaceone.core.error import * + + +class ERROR_NOT_ALLOW_PINNING_KEYS(ERROR_INVALID_ARGUMENT): + _message = "Pinning keys not allowed. (key={key})" + + +class ERROR_METADATA_DICT_TYPE(ERROR_INVALID_ARGUMENT): + _message = "The value of metadata's {key} must be a dict type." + + +class ERROR_METADATA_LIST_VALUE_TYPE(ERROR_INVALID_ARGUMENT): + _message = "The value of metadata's {key} must be a list type." diff --git a/src/spaceone/inventory_v2/interface/grpc/__init__.py b/src/spaceone/inventory_v2/interface/grpc/__init__.py index 45857a9..870175c 100644 --- a/src/spaceone/inventory_v2/interface/grpc/__init__.py +++ b/src/spaceone/inventory_v2/interface/grpc/__init__.py @@ -3,6 +3,7 @@ from .collector import Collector from .job import Job from .job_task import JobTask +from .metric import Metric _all_ = ["app"] @@ -11,3 +12,4 @@ app.add_service(Collector) app.add_service(Job) app.add_service(JobTask) +app.add_service(Metric) diff --git a/src/spaceone/inventory_v2/lib/rule_matcher.py b/src/spaceone/inventory_v2/lib/rule_matcher.py index 8d82cd5..b5dfd8a 100644 --- a/src/spaceone/inventory_v2/lib/rule_matcher.py +++ b/src/spaceone/inventory_v2/lib/rule_matcher.py @@ -36,10 +36,14 @@ def dict_key_int_parser(data: dict) -> dict: def make_query( key: str, rules: dict, resource: dict, domain_id: str, workspace_id: str ) -> dict: - _filter = [ - {"k": "domain_id", "v": domain_id, "o": "eq"}, - {"k": "workspace_id", "v": workspace_id, "o": "eq"}, - ] + resource_type = resource.get("resource_type") + _filter = [{"k": "domain_id", "v": domain_id, "o": "eq"}] + + if resource_type in ["inventory.Asset"]: + _filter.append({"k": "workspace_id", "v": workspace_id, "o": "eq"}) + else: + workspaces = list({workspace_id, "*"}) + _filter.append({"k": "workspace_id", "v": workspaces, "o": "in"}) for rule in rules[key]: value = find_data(resource, rule) diff --git a/src/spaceone/inventory_v2/manager/asset_manager.py b/src/spaceone/inventory_v2/manager/asset_manager.py index dc73da0..572920d 100644 --- a/src/spaceone/inventory_v2/manager/asset_manager.py +++ b/src/spaceone/inventory_v2/manager/asset_manager.py @@ -19,11 +19,9 @@ "name", "ip_addresses", "account", - "instance_type", - "instance_size", - "reference", - "region_code", - "ref_region", + "resource_id", + "external_link", + "region_id", "project_id", "data", ] @@ -116,6 +114,22 @@ def list_assets( **query, target=target, reference_filter=reference_filter ) + def analyze_assets( + self, + query: dict, + change_filter: bool = False, + domain_id: str = None, + reference_filter: dict = None, + ): + if change_filter: + query = self._change_filter_tags(query) + query = self._change_filter_project_group_id(query, domain_id) + + # Append Query for DELETED filter (Temporary Logic) + query = self._append_state_query(query) + + return self.asset_model.analyze(**query, reference_filter=reference_filter) + def _change_filter_tags(self, query: dict) -> dict: change_filter = [] diff --git a/src/spaceone/inventory_v2/manager/asset_type_manager.py b/src/spaceone/inventory_v2/manager/asset_type_manager.py index 9992124..87dd208 100644 --- a/src/spaceone/inventory_v2/manager/asset_type_manager.py +++ b/src/spaceone/inventory_v2/manager/asset_type_manager.py @@ -51,12 +51,12 @@ def delete_asset_type_by_vo(asset_type_vo: AssetType) -> None: def get_asset_type( self, - asset_Type_id: str, + asset_type_id: str, domain_id: str, workspace_id: str = None, ) -> AssetType: conditions = { - "asset_Type_id": asset_Type_id, + "asset_type_id": asset_type_id, "domain_id": domain_id, } diff --git a/src/spaceone/inventory_v2/manager/collecting_manager.py b/src/spaceone/inventory_v2/manager/collecting_manager.py index 5a6c5ef..c402e39 100644 --- a/src/spaceone/inventory_v2/manager/collecting_manager.py +++ b/src/spaceone/inventory_v2/manager/collecting_manager.py @@ -1,10 +1,13 @@ +import datetime import logging import time +from datetime import datetime from typing import Generator + from spaceone.core import utils from spaceone.core.manager import BaseManager + from spaceone.inventory_v2.lib.resource_manager import ResourceManager -from spaceone.inventory_v2.manager.asset_manager import AssetManager from spaceone.inventory_v2.manager.job_manager import JobManager from spaceone.inventory_v2.manager.job_task_manager import JobTaskManager from spaceone.inventory_v2.manager.collector_manager import CollectorManager @@ -13,8 +16,6 @@ CollectorPluginManager, ) -# from spaceone.inventory.manager.namespace_manager import NamespaceManager -# from spaceone.inventory.manager.metric_manager import MetricManager from spaceone.inventory_v2.model.job_task.database import JobTask from spaceone.inventory_v2.error import * from spaceone.inventory_v2.lib import rule_matcher @@ -50,9 +51,6 @@ def collecting_resources(self, params: dict) -> bool: 'token': 'str' } """ - from spaceone.core import model - - model.init_all(False) # set token to transaction meta token = params["token"] @@ -71,6 +69,8 @@ def collecting_resources(self, params: dict) -> bool: secret_data = params["secret_data"] plugin_info = params["plugin_info"] + print(secret_info) + if is_sub_task: _LOGGER.debug( f"[collecting_resources] start sub task: {job_task_id} " @@ -85,7 +85,7 @@ def collecting_resources(self, params: dict) -> bool: self.job_task_mgr.push_job_task(params) return True - job_task_vo = self.job_task_mgr.get(job_task_id, domain_id) + job_task_vo = self.job_task_mgr.get_job_task(job_task_id, domain_id) # add workspace_id to params from secret_info params["workspace_id"] = secret_info["workspace_id"] @@ -233,7 +233,11 @@ def _upsert_collecting_resources( total_count += 1 try: - if resource_type in ["inventory.Namespace", "inventory.Metric"]: + if resource_type in [ + "inventory.NamespaceGroup", + "inventory.Namespace", + "inventory.Metric", + ]: # self._upsert_metric_and_namespace(resource_data, params) # total_count -= 1 pass @@ -377,6 +381,7 @@ def _upsert_resource( request_data = resource_data.get("resource", {}) request_data["domain_id"] = domain_id request_data["workspace_id"] = workspace_id + request_data["last_collected_at"] = datetime.utcnow() service, manager = self._get_resource_map(resource_type) @@ -421,7 +426,11 @@ def _upsert_resource( try: match_resource, total_count = self._query_with_match_rules( - request_data, match_rules, domain_id, workspace_id, manager + request_data, + match_rules, + domain_id, + workspace_id, + manager, ) except ERROR_TOO_MANY_MATCH as e: @@ -474,6 +483,7 @@ def _upsert_resource( additional = self._set_error_addition_info( resource_type, total_count, request_data ) + # todo: refactoring job task error self.job_task_mgr.add_error( job_task_vo, e.error_code, e.message, additional ) @@ -590,8 +600,11 @@ def _query_with_match_rules( query = rule_matcher.make_query( order, match_rules, resource_data, domain_id, workspace_id ) + _LOGGER.debug(f"[_query_with_match_rules] query: {query}") match_resource, total_count = resource_manager.find_resources(query) + _LOGGER.debug(f"[_query_with_match_rules] match_resource: {match_resource}") + if total_count > 1: if data := resource_data.get("data"): raise ERROR_TOO_MANY_MATCH( diff --git a/src/spaceone/inventory_v2/manager/collector_rule_manager.py b/src/spaceone/inventory_v2/manager/collector_rule_manager.py index f45adcc..58f6e3c 100644 --- a/src/spaceone/inventory_v2/manager/collector_rule_manager.py +++ b/src/spaceone/inventory_v2/manager/collector_rule_manager.py @@ -83,15 +83,15 @@ def change_asset_data( custom_collector_rule_vos, ) = self._get_collector_rules(collector_id, domain_id) - cloud_service_data = self._apply_collector_rule_to_asset_data( + asset_data = self._apply_collector_rule_to_asset_data( asset_data, managed_collector_rule_vos, domain_id ) - cloud_service_data = self._apply_collector_rule_to_asset_data( - cloud_service_data, custom_collector_rule_vos, domain_id + asset_data = self._apply_collector_rule_to_asset_data( + asset_data, custom_collector_rule_vos, domain_id ) - return cloud_service_data + return asset_data def _apply_collector_rule_to_asset_data( self, asset_data: dict, collector_rule_vos: QuerySet, domain_id: str diff --git a/src/spaceone/inventory_v2/manager/identity_manager.py b/src/spaceone/inventory_v2/manager/identity_manager.py index 4f1c7f1..62e1d5a 100644 --- a/src/spaceone/inventory_v2/manager/identity_manager.py +++ b/src/spaceone/inventory_v2/manager/identity_manager.py @@ -21,28 +21,6 @@ def __init__(self, *args, **kwargs): token=token, ) - def get_user(self, domain_id: str, user_id: str) -> dict: - system_token = config.get_global("TOKEN") - response = self.identity_conn.dispatch( - "User.list", - {"user_id": user_id, "state": "ENABLED"}, - x_domain_id=domain_id, - token=system_token, - ) - users_info = response.get("results", []) - if users_info: - return users_info[0] - else: - return {} - - def get_domain_name(self, domain_id: str) -> str: - system_token = config.get_global("TOKEN") - - domain_info = self.identity_conn.dispatch( - "Domain.get", {"domain_id": domain_id}, token=system_token - ) - return domain_info["name"] - def list_domains(self, params: dict) -> dict: system_token = config.get_global("TOKEN") return self.identity_conn.dispatch("Domain.list", params, token=system_token) @@ -107,6 +85,24 @@ def get_service_account_name_map(self, domain_id: str, workspace_id: str) -> dic ) return service_account_name_map + @cache.cacheable( + key="inventory:service-account:{domain_id}:{service_account_id}", expire=300 + ) + def get_service_account(self, service_account_id: str, domain_id: str) -> dict: + token = self.transaction.get_meta("token") + token_type = JWTUtil.get_value_from_token(token, "typ") + + if token_type == "SYSTEM_TOKEN": + return self.identity_conn.dispatch( + "ServiceAccount.get", + {"service_account_id": service_account_id}, + x_domain_id=domain_id, + ) + else: + return self.identity_conn.dispatch( + "ServiceAccount.get", {"service_account": service_account_id} + ) + def list_service_accounts(self, query: dict, domain_id: str) -> dict: if self.token_type == "SYSTEM_TOKEN": return self.identity_conn.dispatch( @@ -115,10 +111,16 @@ def list_service_accounts(self, query: dict, domain_id: str) -> dict: else: return self.identity_conn.dispatch("ServiceAccount.list", {"query": query}) - def get_project(self, project_id: str, domain_id: str): - if self.token_type == "SYSTEM_TOKEN": + @cache.cacheable(key="inventory:project:{domain_id}:{project_id}", expire=3600) + def get_project(self, project_id, domain_id) -> dict: + token = self.transaction.get_meta("token") + token_type = JWTUtil.get_value_from_token(token, "typ") + + if token_type == "SYSTEM_TOKEN": return self.identity_conn.dispatch( - "Project.get", {"project_id": project_id}, x_domain_id=domain_id + "Project.get", + {"project_id": project_id}, + x_domain_id=domain_id, ) else: return self.identity_conn.dispatch( diff --git a/src/spaceone/inventory_v2/manager/metric_manager.py b/src/spaceone/inventory_v2/manager/metric_manager.py index 6555f9d..da70b46 100644 --- a/src/spaceone/inventory_v2/manager/metric_manager.py +++ b/src/spaceone/inventory_v2/manager/metric_manager.py @@ -231,13 +231,11 @@ def analyze_resource( ) try: - if metric_vo.resource_type == "inventory.CloudService": - return self._analyze_cloud_service(query, domain_id) - elif metric_vo.resource_type.startswith("inventory.CloudService:"): + if metric_vo.resource_type == "inventory.Asset": + return self._analyze_assets(query, domain_id) + elif metric_vo.resource_type.startswith("inventory.Asset:"): cloud_service_type_key = metric_vo.resource_type.split(":")[-1] - return self._analyze_cloud_service( - query, domain_id, cloud_service_type_key - ) + return self._analyze_assets(query, domain_id, cloud_service_type_key) else: raise ERROR_NOT_SUPPORT_RESOURCE_TYPE(resource_type=resource_type) except Exception as e: @@ -281,13 +279,13 @@ def _append_datetime_filter( return query @staticmethod - def _analyze_cloud_service( + def _analyze_assets( query: dict, domain_id: str, - cloud_service_type_key: str = None, + asset_type_id: str = None, ) -> list: default_group_by = [ - "collection_info.service_account_id", + "service_account_id", "project_id", "workspace_id", ] @@ -306,24 +304,16 @@ def _analyze_cloud_service( query["group_by"] = changed_group_by query["filter"] = query.get("filter", []) query["filter"].append({"k": "domain_id", "v": domain_id, "o": "eq"}) + print(query) - if cloud_service_type_key: + if asset_type_id: try: - ( - provider, - cloud_service_group, - cloud_service_type, - ) = cloud_service_type_key.split(".") - query["filter"].append({"k": f"provider", "v": provider, "o": "eq"}) - query["filter"].append( - {"k": f"cloud_service_group", "v": cloud_service_group, "o": "eq"} - ) query["filter"].append( - {"k": f"cloud_service_type", "v": cloud_service_type, "o": "eq"} + {"k": "asset_type_id", "v": asset_type_id, "o": "eq"} ) except Exception as e: raise ERROR_NOT_SUPPORT_RESOURCE_TYPE( - resource_type=f"inventory.CloudService:{cloud_service_type_key}" + resource_type=f"inventory.Asset:{asset_type_id}" ) if "select" in query: diff --git a/src/spaceone/inventory_v2/model/asset/database.py b/src/spaceone/inventory_v2/model/asset/database.py index d21f4a8..8accf3b 100644 --- a/src/spaceone/inventory_v2/model/asset/database.py +++ b/src/spaceone/inventory_v2/model/asset/database.py @@ -20,8 +20,10 @@ class Asset(MongoModel): provider = StringField(max_length=255) account = StringField(max_length=255, default=None, null=True) region_id = StringField(max_length=40) + secret_id = StringField(max_length=40, default=None, null=True) asset_type_id = StringField(max_length=255) - collector_id = StringField(max_length=40) + service_account_id = StringField(max_length=40, default=None, null=True) + collector_id = StringField(max_length=40, default=None, null=True) project_id = StringField(max_length=40) workspace_id = StringField(max_length=40) domain_id = StringField(max_length=40) @@ -35,12 +37,15 @@ class Asset(MongoModel): "name", "data", "state", - "account", "ip_addresses", "tags", - "project_id", + "account", "region_id", "asset_type_id", + "secret_id", + "service_account_id", + "collector_id", + "project_id", "updated_at", "last_collected_at", "deleted_at", diff --git a/src/spaceone/inventory_v2/model/asset/request.py b/src/spaceone/inventory_v2/model/asset/request.py index 57b05ad..b90e578 100644 --- a/src/spaceone/inventory_v2/model/asset/request.py +++ b/src/spaceone/inventory_v2/model/asset/request.py @@ -10,21 +10,21 @@ class AssetCreateRequest(BaseModel): - asset_code: Union[str, None] + asset_id: Union[str, None] name: Union[str, None] provider: str asset_type: str ipaddresses: Union[List[str], None] account: Union[str, None] - instance_type: Union[str, None] - instance_size: Union[float, None] data: dict metadata: Union[dict, None] tags: Union[dict, None] - region: Union[str, None] + region_id: Union[str, None] + service_account_id: Union[str, None] project_id: str workspace_id: str domain_id: str + users_projects: Union[List[str], None] class AssetUpdateRequest(BaseModel): diff --git a/src/spaceone/inventory_v2/model/asset/response.py b/src/spaceone/inventory_v2/model/asset/response.py index 7563421..db704f0 100644 --- a/src/spaceone/inventory_v2/model/asset/response.py +++ b/src/spaceone/inventory_v2/model/asset/response.py @@ -7,18 +7,17 @@ class AssetResponse(BaseModel): asset_id: Union[str, None] - asset_code: Union[str, None] name: Union[str, None] provider: Union[str, None] ipaddresses: Union[List[str], None] account: Union[str, None] - instance_type: Union[str, None] - instance_size: Union[float, None] data: Union[dict, None] metadata: Union[dict, None] tags: Union[dict, None] - region: Union[str, None] + region_id: Union[str, None] asset_type_id: Union[str, None] + service_account_id: Union[str, None] + collector_id: Union[str, None] project_id: Union[str, None] workspace_id: Union[str, None] domain_id: Union[str, None] @@ -28,5 +27,5 @@ class AssetResponse(BaseModel): 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"]) + data["updated_at"] = utils.datetime_to_iso8601(data.get("updated_at")) return data diff --git a/src/spaceone/inventory_v2/model/asset_type/database.py b/src/spaceone/inventory_v2/model/asset_type/database.py index 5118d8f..43c269b 100644 --- a/src/spaceone/inventory_v2/model/asset_type/database.py +++ b/src/spaceone/inventory_v2/model/asset_type/database.py @@ -5,10 +5,7 @@ class AssetType(MongoModel): asset_type_id = StringField(max_length=40, unique=True) - name = StringField( - max_length=255, - unique_with=["provider", "workspace_id", "domain_id"], - ) + name = StringField(max_length=255) description = StringField(max_length=255, default=None, null=True) icon = StringField(max_length=255, default=None, null=True) provider = StringField(max_length=255) diff --git a/src/spaceone/inventory_v2/model/metric/request.py b/src/spaceone/inventory_v2/model/metric/request.py index 3210c18..747cd22 100644 --- a/src/spaceone/inventory_v2/model/metric/request.py +++ b/src/spaceone/inventory_v2/model/metric/request.py @@ -83,5 +83,5 @@ class MetricSearchQueryRequest(BaseModel): class MetricStatQueryRequest(BaseModel): query: dict - workspace_id: Union[str, list, None] = None + workspace_id: Union[list, str, None] = None domain_id: str diff --git a/src/spaceone/inventory_v2/service/asset_service.py b/src/spaceone/inventory_v2/service/asset_service.py index f2dcd80..05402a5 100644 --- a/src/spaceone/inventory_v2/service/asset_service.py +++ b/src/spaceone/inventory_v2/service/asset_service.py @@ -22,9 +22,9 @@ "asset_id", "name", "ip_addresses", - "cloud_service_group", - "cloud_service_type", - "reference.resource_id", + "asset_type_id", + "region_id", + "resource_id", ] _LOGGER = logging.getLogger(__name__) @@ -58,18 +58,14 @@ def create(self, params: AssetCreateRequest) -> Union[AssetResponse, dict]: """ Args: params (dict): { - 'cloud_service_type': 'str', # required - 'cloud_service_group': 'str', # required + 'asset_id': 'str', # required 'provider': 'str', # required 'name': 'str', 'account': 'str', - 'instance_type': 'str', - 'instance_size': 'float', 'ip_addresses': 'list', 'data': 'dict', # required 'json_data': 'dict', 'metadata': 'dict', - 'reference': 'dict', 'tags': 'list or dict', 'region_code': 'str', 'project_id': 'str', # required @@ -110,7 +106,9 @@ def create_resource(self, params: dict) -> Asset: domain_id = params["domain_id"] workspace_id = params["workspace_id"] + secret_project_id = self.transaction.get_meta("secret.project_id") + provider = params["provider"] if instance_size := params.get("instance_size"): @@ -136,17 +134,12 @@ def create_resource(self, params: dict) -> Asset: elif secret_project_id: params["project_id"] = secret_project_id - # params["ref_cloud_service_type"] = self._make_cloud_service_type_key(params) - - if "region_id" in params: - params["ref_region"] = self._make_region_key( - domain_id, provider, params["region_code"] + if "service_account_id" in params: + self.identity_mgr.get_service_account( + params["service_account_id"], domain_id ) - if "metadata" in params: - params["metadata"] = self._convert_metadata(params["metadata"], provider) - - params["collection_info"] = self._get_collection_info() + params = self._set_metadata_info_from_transaction(params) asset_vo = self.asset_mgr.create_asset(params) @@ -226,10 +219,6 @@ def update_resource(self, params: dict) -> Asset: if "ip_addresses" in params and params["ip_addresses"] is None: del params["ip_addresses"] - if instance_size := params.get("instance_size"): - if not isinstance(instance_size, float): - raise ERROR_INVALID_PARAMETER_TYPE(key="instance_size", type="float") - if "tags" in params: params["tags"] = self._convert_tags_to_dict(params["tags"]) @@ -282,7 +271,7 @@ def update_resource(self, params: dict) -> Asset: else: del params["metadata"] - params["collection_info"] = self._get_collection_info() + params = self._set_metadata_info_from_transaction(params) params = self.asset_mgr.merge_data(params, old_asset_data) @@ -391,13 +380,6 @@ def list(self, params: AssetSearchQueryRequest): reference_filter=reference_filter, ) - @staticmethod - def _make_cloud_service_type_key(resource_data: dict) -> str: - return ( - f'{resource_data["domain_id"]}.{resource_data["workspace_id"]}.{resource_data["provider"]}.' - f'{resource_data["cloud_service_group"]}.{resource_data["cloud_service_type"]}' - ) - @staticmethod def _make_region_key(domain_id: str, provider: str, region_code: str) -> str: return f"{domain_id}.{provider}.{region_code}" @@ -406,17 +388,20 @@ def _make_region_key(domain_id: str, provider: str, region_code: str) -> str: def _convert_metadata(metadata: dict, provider: str) -> dict: return {provider: copy.deepcopy(metadata)} - def _get_collection_info(self) -> dict: + def _set_metadata_info_from_transaction(self, params: dict) -> dict: collector_id = self.transaction.get_meta("collector_id") secret_id = self.transaction.get_meta("secret.secret_id") service_account_id = self.transaction.get_meta("secret.service_account_id") - return { - "collector_id": collector_id, - "secret_id": secret_id, - "service_account_id": service_account_id, - "last_collected_at": datetime.utcnow(), - } + params.update( + { + "collector_id": collector_id, + "secret_id": secret_id, + "service_account_id": service_account_id, + } + ) + + return params @staticmethod def _convert_tags_to_dict(tags: Union[list, dict]) -> dict: diff --git a/src/spaceone/inventory_v2/service/asset_type_service.py b/src/spaceone/inventory_v2/service/asset_type_service.py index 23904c4..e5b2768 100644 --- a/src/spaceone/inventory_v2/service/asset_type_service.py +++ b/src/spaceone/inventory_v2/service/asset_type_service.py @@ -22,19 +22,10 @@ from spaceone.inventory_v2.model.asset_type.database import AssetType from spaceone.inventory_v2.model.asset_type.response import AssetTypeResponse -_KEYWORD_FILTER = [ - "asset_id", - "name", - "ip_addresses", - "cloud_service_group", - "cloud_service_type", - "reference.resource_id", -] +_KEYWORD_FILTER = ["asset_type_id", "name", "asset_group_id"] _LOGGER = logging.getLogger(__name__) -_KEYWORD_FILTER = ["asset_type_id", "name", "asset_group_id", "service_code"] - @authentication_handler @authorization_handler @@ -131,7 +122,7 @@ def update(self, params: dict) -> AssetType: return self.update_resource(params) - @check_required(["cloud_service_type_id", "workspace_id", "domain_id"]) + @check_required(["asset_type_id", "workspace_id", "domain_id"]) def update_resource(self, params: dict) -> AssetType: if json_metadata := params.get("json_metadata"): params["metadata"] = utils.load_json(json_metadata) @@ -149,11 +140,11 @@ def update_resource(self, params: dict) -> AssetType: params["updated_by"] = self.transaction.get_meta("collector_id") or "manual" domain_id = params["domain_id"] - cloud_svc_type_vo = self.asset_type_mgr.get_asset_type( + asset_type_vo = self.asset_type_mgr.get_asset_type( params["asset_type_id"], domain_id ) - return self.asset_type_mgr.update_asset_type_by_vo(params, cloud_svc_type_vo) + return self.asset_type_mgr.update_asset_type_by_vo(params, asset_type_vo) @transaction( permission="inventory:AssetType.write", @@ -173,10 +164,10 @@ def delete(self, params: dict) -> None: self.delete_resource(params) - @check_required(["cloud_service_type_id", "workspace_id", "domain_id"]) + @check_required(["asset_type_id", "workspace_id", "domain_id"]) def delete_resource(self, params: dict) -> None: asset_type_vo = self.asset_type_mgr.get_asset_type( - params["cloud_service_type_id"], params["domain_id"], params["workspace_id"] + params["asset_type_id"], params["domain_id"], params["workspace_id"] ) self.asset_type_mgr.delete_asset_type_by_vo(asset_type_vo) diff --git a/src/spaceone/inventory_v2/service/collector_service.py b/src/spaceone/inventory_v2/service/collector_service.py index 50befd2..b05f548 100644 --- a/src/spaceone/inventory_v2/service/collector_service.py +++ b/src/spaceone/inventory_v2/service/collector_service.py @@ -614,7 +614,7 @@ def _get_tasks( domain_id: str, collector_workspace_id: str = None, ) -> list: - secret_mgr: SecretManager = self.locator.get_manager(SecretManager) + secret_mgr = SecretManager() collector_plugin_mgr = CollectorPluginManager() tasks = [] @@ -629,6 +629,7 @@ def _get_tasks( for secret_id in secret_ids: secret_info = secret_mgr.get_secret(secret_id, domain_id) secret_data = secret_mgr.get_secret_data(secret_id, domain_id) + _task = { "plugin_info": plugin_info, "secret_info": secret_info, diff --git a/src/spaceone/inventory_v2/service/metric_service.py b/src/spaceone/inventory_v2/service/metric_service.py index 9fdeb9b..494ea10 100644 --- a/src/spaceone/inventory_v2/service/metric_service.py +++ b/src/spaceone/inventory_v2/service/metric_service.py @@ -283,6 +283,7 @@ def list(self, params: MetricSearchQueryRequest) -> Union[MetricsResponse, dict] permission="inventory-v2:Metric.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(["metric_id", "name"]) @convert_model From 894552c686361b03a3a8679a0c07cd1c86822ccd Mon Sep 17 00:00:00 2001 From: kang2453 Date: Tue, 10 Dec 2024 21:42:44 +0900 Subject: [PATCH 2/3] feat: add new services namespacegroup, namespace --- .../inventory_v2/error/error_namespace.py | 11 + .../error/error_namespace_group.py | 24 ++ .../inventory_v2/interface/grpc/__init__.py | 4 + .../inventory_v2/interface/grpc/namespace.py | 46 ++++ .../interface/grpc/namespace_group.py | 47 ++++ src/spaceone/inventory_v2/manager/__init__.py | 2 + .../manager/namespace_group_manager.py | 120 +++++++++ .../inventory_v2/manager/namespace_manager.py | 79 ++++++ src/spaceone/inventory_v2/model/__init__.py | 4 +- .../inventory_v2/model/namespace/__init__.py | 1 + .../inventory_v2/model/namespace/database.py | 31 +++ .../inventory_v2/model/namespace/request.py | 64 +++++ .../inventory_v2/model/namespace/response.py | 34 +++ .../model/namespace_group/__init__.py | 1 + .../model/namespace_group/database.py | 28 +++ .../model/namespace_group/request.py | 66 +++++ .../model/namespace_group/response.py | 33 +++ src/spaceone/inventory_v2/service/__init__.py | 4 + .../service/namespace_group_service.py | 234 +++++++++++++++++ .../inventory_v2/service/namespace_service.py | 235 ++++++++++++++++++ 20 files changed, 1067 insertions(+), 1 deletion(-) create mode 100644 src/spaceone/inventory_v2/error/error_namespace.py create mode 100644 src/spaceone/inventory_v2/error/error_namespace_group.py create mode 100644 src/spaceone/inventory_v2/interface/grpc/namespace.py create mode 100644 src/spaceone/inventory_v2/interface/grpc/namespace_group.py create mode 100644 src/spaceone/inventory_v2/manager/namespace_group_manager.py create mode 100644 src/spaceone/inventory_v2/manager/namespace_manager.py create mode 100644 src/spaceone/inventory_v2/model/namespace/__init__.py create mode 100644 src/spaceone/inventory_v2/model/namespace/database.py create mode 100644 src/spaceone/inventory_v2/model/namespace/request.py create mode 100644 src/spaceone/inventory_v2/model/namespace/response.py create mode 100644 src/spaceone/inventory_v2/model/namespace_group/__init__.py create mode 100644 src/spaceone/inventory_v2/model/namespace_group/database.py create mode 100644 src/spaceone/inventory_v2/model/namespace_group/request.py create mode 100644 src/spaceone/inventory_v2/model/namespace_group/response.py create mode 100644 src/spaceone/inventory_v2/service/namespace_group_service.py create mode 100644 src/spaceone/inventory_v2/service/namespace_service.py 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 870175c..209dac2 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 from .metric import Metric _all_ = ["app"] @@ -12,4 +14,6 @@ app.add_service(Collector) app.add_service(Job) app.add_service(JobTask) +app.add_service(NamespaceGroup) +app.add_service(Namespace) app.add_service(Metric) 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) + From 5d99de70104a469867fc113ee782cde85073e1b6 Mon Sep 17 00:00:00 2001 From: ImMin5 Date: Wed, 11 Dec 2024 00:09:32 +0900 Subject: [PATCH 3/3] feat: add metric service create api Signed-off-by: ImMin5 --- src/spaceone/inventory_v2/interface/grpc/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/spaceone/inventory_v2/interface/grpc/__init__.py b/src/spaceone/inventory_v2/interface/grpc/__init__.py index 209dac2..1c06bf9 100644 --- a/src/spaceone/inventory_v2/interface/grpc/__init__.py +++ b/src/spaceone/inventory_v2/interface/grpc/__init__.py @@ -3,6 +3,7 @@ from .collector import Collector from .job import Job from .job_task import JobTask +from .metric import Metric from .namespace_group import NamespaceGroup from .namespace import Namespace from .metric import Metric