Skip to content

Commit f6eb66e

Browse files
authoredJun 29, 2024
Merge pull request #250 from ImMin5/master
Add cost_data_keys filter at data_source 'get' and 'list' api
2 parents 4ce040b + d02a153 commit f6eb66e

File tree

5 files changed

+152
-49
lines changed

5 files changed

+152
-49
lines changed
 

‎src/spaceone/cost_analysis/interface/grpc/data_source.py

+6-18
Original file line numberDiff line numberDiff line change
@@ -106,27 +106,15 @@ def sync(self, request, context):
106106

107107
def get(self, request, context):
108108
params, metadata = self.parse_request(request, context)
109-
110-
with self.locator.get_service(
111-
"DataSourceService", metadata
112-
) as data_source_service:
113-
return self.locator.get_info(
114-
"DataSourceInfo", data_source_service.get(params)
115-
)
109+
data_source_svc = DataSourceService(metadata)
110+
response: dict = data_source_svc.get(params)
111+
return self.dict_to_message(response)
116112

117113
def list(self, request, context):
118114
params, metadata = self.parse_request(request, context)
119-
120-
with self.locator.get_service(
121-
"DataSourceService", metadata
122-
) as data_source_service:
123-
data_source_vos, total_count = data_source_service.list(params)
124-
return self.locator.get_info(
125-
"DataSourcesInfo",
126-
data_source_vos,
127-
total_count,
128-
minimal=self.get_minimal(params),
129-
)
115+
data_source_svc = DataSourceService(metadata)
116+
response: dict = data_source_svc.list(params)
117+
return self.dict_to_message(response)
130118

131119
def stat(self, request, context):
132120
params, metadata = self.parse_request(request, context)

‎src/spaceone/cost_analysis/manager/data_source_manager.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def deregister_data_source_by_vo(data_source_vo):
8080

8181
def get_data_source(
8282
self, data_source_id: str, domain_id: str, workspace_id: str = None
83-
):
83+
) -> DataSource:
8484
conditions = {"data_source_id": data_source_id, "domain_id": domain_id}
8585

8686
if workspace_id:
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,63 @@
11
from typing import Union, Literal
2-
from pydantic import BaseModel
2+
from pydantic import BaseModel, Field
33

44
__all__ = [
5+
"DataSourceRegisterRequest",
56
"DataSourceUpdatePermissionsRequest",
7+
"DataSourceGetRequest",
8+
"DataSourceSearchQueryRequest",
69
]
7-
State = Literal["ENABLED", "DISABLED"]
10+
811
DataSourceType = Literal["LOCAL", "EXTERNAL"]
12+
State = Literal["ENABLED", "DISABLED"]
913
SecretType = Literal["MANUAL", "USE_SERVICE_ACCOUNT_SECRET"]
1014
ResourceGroup = Literal["DOMAIN", "WORKSPACE"]
1115

1216

17+
class Plugin(BaseModel):
18+
plugin_id: str
19+
version: Union[str, None] = None
20+
options: Union[dict, None] = None
21+
metadata: Union[dict, None] = None
22+
secret_data: Union[dict, None] = None
23+
schema_id: Union[str, None] = Field(None, alias="schema")
24+
secret_id: Union[str, None] = None
25+
upgrade_mode: Union[str, None] = None
26+
27+
28+
class DataSourceRegisterRequest(BaseModel):
29+
name: str
30+
data_source_type: DataSourceType
31+
provider: Union[str, None] = None
32+
secret_type: Union[SecretType, str, None] = None
33+
secret_filter: Union[dict, None] = None
34+
template: Union[dict, None] = None
35+
plugin_info: Union[Plugin, None] = None
36+
tags: Union[dict, None] = None
37+
resource_group: Union[ResourceGroup, None] = None
38+
workspace_id: Union[str, None] = None
39+
domain_id: str
40+
41+
1342
class DataSourceUpdatePermissionsRequest(BaseModel):
1443
data_source_id: str
1544
permissions: dict
1645
domain_id: str
46+
47+
48+
class DataSourceGetRequest(BaseModel):
49+
data_source_id: str
50+
workspace_id: Union[list, str, None] = None
51+
domain_id: str
52+
53+
54+
class DataSourceSearchQueryRequest(BaseModel):
55+
query: Union[dict, None] = None
56+
data_source_id: Union[str, None] = None
57+
name: Union[str, None] = None
58+
state: Union[State, None] = None
59+
data_source_type: Union[DataSourceType, None] = None
60+
provider: Union[str, None] = None
61+
connected_workspace_id: Union[str, None] = None
62+
workspace_id: Union[list, str, None] = None
63+
domain_id: str

‎src/spaceone/cost_analysis/model/data_source/response.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from datetime import datetime
2-
from typing import Union, Literal
2+
from typing import Union, Literal, List
33
from pydantic import BaseModel
44

55
from spaceone.core import utils
@@ -13,6 +13,7 @@
1313

1414
__all__ = [
1515
"DataSourceResponse",
16+
"DataSourcesResponse",
1617
]
1718

1819

@@ -48,3 +49,8 @@ def dict(self, *args, **kwargs):
4849
data.get("last_synchronized_at")
4950
)
5051
return data
52+
53+
54+
class DataSourcesResponse(BaseModel):
55+
results: List[DataSourceResponse]
56+
total_count: int

‎src/spaceone/cost_analysis/service/data_source_service.py

+89-27
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,8 @@
55
from mongoengine import QuerySet
66
from spaceone.core.service import *
77
from spaceone.cost_analysis.error import *
8-
from spaceone.cost_analysis.model.data_source.request import (
9-
DataSourceUpdatePermissionsRequest,
10-
)
11-
from spaceone.cost_analysis.model.data_source.response import DataSourceResponse
8+
from spaceone.cost_analysis.model.data_source.request import *
9+
from spaceone.cost_analysis.model.data_source.response import *
1210
from spaceone.cost_analysis.service.job_service import JobService
1311
from spaceone.cost_analysis.manager.repository_manager import RepositoryManager
1412
from spaceone.cost_analysis.manager.secret_manager import SecretManager
@@ -48,8 +46,10 @@ def __init__(self, *args, **kwargs):
4846
permission="cost-analysis:DataSource.write",
4947
role_types=["DOMAIN_ADMIN", "WORKSPACE_OWNER"],
5048
)
51-
@check_required(["name", "data_source_type", "domain_id"])
52-
def register(self, params):
49+
@convert_model
50+
def register(
51+
self, params: DataSourceRegisterRequest
52+
) -> Union[DataSourceResponse, dict]:
5353
"""Register data source
5454
5555
Args:
@@ -70,6 +70,7 @@ def register(self, params):
7070
Returns:
7171
data_source_vo (object)
7272
"""
73+
params = params.dict(exclude_unset=True)
7374

7475
domain_id = params["domain_id"]
7576
data_source_type = params["data_source_type"]
@@ -177,7 +178,7 @@ def register(self, params):
177178
data_source_vo
178179
)
179180

180-
return data_source_vo
181+
return DataSourceResponse(**data_source_vo.to_dict())
181182

182183
@transaction(
183184
permission="cost-analysis:DataSource.write",
@@ -228,7 +229,7 @@ def update(self, params):
228229
)
229230
@convert_model
230231
def update_permissions(
231-
self, params: DataSourceUpdatePermissionsRequest
232+
self, params: DataSourceUpdatePermissionsRequest
232233
) -> Union[DataSourceResponse, dict]:
233234
"""Update data source permissions
234235
@@ -567,35 +568,45 @@ def sync(self, params):
567568
permission="cost-analysis:DataSource.read",
568569
role_types=["DOMAIN_ADMIN", "WORKSPACE_OWNER", "WORKSPACE_MEMBER"],
569570
)
570-
@check_required(["data_source_id", "domain_id"])
571-
def get(self, params):
571+
@change_value_by_rule("APPEND", "workspace_id", "*")
572+
@convert_model
573+
def get(self, params: DataSourceGetRequest) -> Union[DataSourceResponse, dict]:
572574
"""Get data source
573575
574576
Args:
575577
params (dict): {
576578
'data_source_id': 'str', # required
577-
'workspace_id': 'str'
579+
'workspace_id': 'list'
578580
'domain_id': 'str', # injected from auth
579581
}
580582
581583
Returns:
582584
data_source_vo (object)
583585
"""
584586

585-
data_source_id = params["data_source_id"]
586-
domain_id = params["domain_id"]
587-
workspace_id = params.get("workspace_id")
587+
data_source_id = params.data_source_id
588+
domain_id = params.domain_id
589+
workspace_id = params.workspace_id
588590

589-
return self.data_source_mgr.get_data_source(
591+
data_source_vo = self.data_source_mgr.get_data_source(
590592
data_source_id, domain_id, workspace_id
591593
)
592594

595+
# Check data fields permissions
596+
if self.transaction.get_meta("authorization.role_type") != "DOMAIN_ADMIN":
597+
data_source_vo = (
598+
self._filter_cost_data_keys_with_permissions_by_data_source_vo(
599+
data_source_vo
600+
)
601+
)
602+
603+
return DataSourceResponse(**data_source_vo.to_dict())
604+
593605
@transaction(
594606
permission="cost-analysis:DataSource.read",
595607
role_types=["DOMAIN_ADMIN", "WORKSPACE_OWNER", "WORKSPACE_MEMBER"],
596608
)
597609
@change_value_by_rule("APPEND", "workspace_id", "*")
598-
@check_required(["domain_id"])
599610
@append_query_filter(
600611
[
601612
"data_source_id",
@@ -609,11 +620,15 @@ def get(self, params):
609620
)
610621
@change_tag_filter("tags")
611622
@append_keyword_filter(["data_source_id", "name"])
612-
def list(self, params):
623+
@convert_model
624+
def list(
625+
self, params: DataSourceSearchQueryRequest
626+
) -> Union[DataSourcesResponse, dict]:
613627
"""List data sources
614628
615629
Args:
616630
params (dict): {
631+
'query': 'dict (spaceone.api.core.v1.Query)'
617632
'data_source_id': 'str',
618633
'name': 'str',
619634
'state': 'str',
@@ -622,16 +637,19 @@ def list(self, params):
622637
'connected_workspace_id': str,
623638
'workspace_id': 'list,
624639
'domain_id': 'str',
625-
'query': 'dict (spaceone.api.core.v1.Query)'
640+
626641
}
627642
628643
Returns:
629644
data_source_vos (object)
630645
total_count
631646
"""
632647

633-
query = params.get("query", {})
634-
connected_workspace_id = params.get("connected_workspace_id")
648+
query = params.query or {}
649+
connected_workspace_id = params.connected_workspace_id
650+
651+
if self.transaction.get_meta("authorization.role_type") != "DOMAIN_ADMIN":
652+
self._check_only_fields_for_permissions(query)
635653

636654
if connected_workspace_id:
637655
(
@@ -643,7 +661,9 @@ def list(self, params):
643661
else:
644662
data_source_vos, total_count = self.data_source_mgr.list_data_sources(query)
645663

646-
return data_source_vos, total_count
664+
data_sources_info = self._get_data_sources_info_by_role_type(data_source_vos)
665+
666+
return DataSourcesResponse(results=data_sources_info, total_count=total_count)
647667

648668
@transaction(
649669
permission="cost-analysis:DataSource.read",
@@ -741,21 +761,21 @@ def _get_secret_data(self, secret_id, domain_id):
741761
return secret_data
742762

743763
@staticmethod
744-
def _validate_plugin_info(plugin_info, secret_type):
764+
def _validate_plugin_info(plugin_info: dict, secret_type: str) -> None:
745765
if "plugin_id" not in plugin_info:
746766
raise ERROR_REQUIRED_PARAMETER(key="plugin_info.plugin_id")
747767

748768
if (
749-
plugin_info.get("upgrade_mode", "AUTO") == "MANUAL"
750-
and "version" not in plugin_info
769+
plugin_info.get("upgrade_mode", "AUTO") == "MANUAL"
770+
and "version" not in plugin_info
751771
):
752772
raise ERROR_REQUIRED_PARAMETER(key="plugin_info.version")
753773

754774
if secret_type == "MANUAL" and plugin_info.get("secret_data") is None:
755775
raise ERROR_REQUIRED_PARAMETER(key="plugin_info.secret_data")
756776

757777
def create_data_source_account_with_data_source_vo(
758-
self, accounts_info: dict, data_source_vo: DataSource
778+
self, accounts_info: dict, data_source_vo: DataSource
759779
) -> None:
760780
data_source_id = data_source_vo.data_source_id
761781
workspace_id = data_source_vo.workspace_id
@@ -813,7 +833,7 @@ def create_data_source_account_with_data_source_vo(
813833
)
814834

815835
def _get_data_source_account_vo_map(
816-
self, data_source_id: str, domain_id: str
836+
self, data_source_id: str, domain_id: str
817837
) -> dict:
818838
data_source_account_vo_map = {}
819839
data_source_account_vos = (
@@ -828,7 +848,7 @@ def _get_data_source_account_vo_map(
828848
return data_source_account_vo_map
829849

830850
def _change_filter_connected_workspace_data_source(
831-
self, query: dict, connected_workspace_id: str
851+
self, query: dict, connected_workspace_id: str
832852
) -> Tuple[Union[QuerySet, list], int]:
833853
connected_data_source_ids = []
834854
domain_id = self._get_domain_id_from_filter(query)
@@ -873,6 +893,22 @@ def _change_filter_connected_workspace_data_source(
873893

874894
return self.data_source_mgr.list_data_sources(query)
875895

896+
def _get_data_sources_info_by_role_type(self, data_source_vos: list) -> list:
897+
data_sources_info = []
898+
if self.transaction.get_meta("authorization.role_type") != "DOMAIN_ADMIN":
899+
for data_source_vo in data_source_vos:
900+
data_source_vo = (
901+
self._filter_cost_data_keys_with_permissions_by_data_source_vo(
902+
data_source_vo
903+
)
904+
)
905+
data_sources_info.append(data_source_vo.to_dict())
906+
else:
907+
for data_source_vo in data_source_vos:
908+
data_sources_info.append(data_source_vo.to_dict())
909+
910+
return data_sources_info
911+
876912
@staticmethod
877913
def _get_domain_id_from_filter(query: dict) -> str:
878914
for condition in query.get("filter", []):
@@ -903,3 +939,29 @@ def _get_copied_remove_only_filter_query(query: dict) -> dict:
903939
copied_query.pop("minimal", None)
904940
_LOGGER.debug(f"[_get_copied_remove_only_filter_query] query: {copied_query}")
905941
return copied_query
942+
943+
@staticmethod
944+
def _filter_cost_data_keys_with_permissions_by_data_source_vo(
945+
data_source_vo: DataSource,
946+
) -> DataSource:
947+
if data_source_vo.permissions:
948+
deny = data_source_vo.permissions.get("deny", [])
949+
cost_data_keys = data_source_vo.cost_data_keys or []
950+
for deny_key in deny:
951+
if deny_key.startswith("data."):
952+
split_denied_key = deny_key.split(".")[1]
953+
if split_denied_key in cost_data_keys:
954+
cost_data_keys.remove(split_denied_key)
955+
data_source_vo.cost_data_keys = cost_data_keys
956+
return data_source_vo
957+
958+
@staticmethod
959+
def _check_only_fields_for_permissions(query: dict) -> None:
960+
only_fields = query.get("only")
961+
for only_field in only_fields:
962+
if only_field == "cost_data_keys":
963+
if "permissions" not in only_fields:
964+
raise ERROR_INVALID_PARAMETER(
965+
key="permissions",
966+
reason="when you want to get 'cost_data_keys', you must include 'permissions' field in 'only' field.",
967+
)

0 commit comments

Comments
 (0)