Skip to content

Commit eff39e7

Browse files
author
Douglas Curtis
authored
COST-865: GCP Tags API (#2583)
Basic GCP tags API support, minus tag enablement.
1 parent 61b5cde commit eff39e7

16 files changed

+801
-12
lines changed

docs/source/specs/openapi.json

+127
Original file line numberDiff line numberDiff line change
@@ -2920,6 +2920,133 @@
29202920
}]
29212921
}
29222922
},
2923+
"/tags/gcp/": {
2924+
"get": {
2925+
"tags": [
2926+
"Tags"
2927+
],
2928+
"summary": "Query to obtain GCP tags",
2929+
"operationId": "getGCPTagData",
2930+
"parameters": [{
2931+
"$ref": "#/components/parameters/QueryFilter"
2932+
},
2933+
{
2934+
"$ref": "#/components/parameters/QueryKeyOnly"
2935+
},
2936+
{
2937+
"$ref": "#/components/parameters/QueryOffset"
2938+
},
2939+
{
2940+
"$ref": "#/components/parameters/ReportQueryLimit"
2941+
}
2942+
],
2943+
"responses": {
2944+
"200": {
2945+
"description": "A paginated report object",
2946+
"content": {
2947+
"application/json": {
2948+
"schema": {
2949+
"$ref": "#/components/schemas/Tags"
2950+
}
2951+
}
2952+
}
2953+
},
2954+
"401": {
2955+
"description": "Unauthorized"
2956+
},
2957+
"500": {
2958+
"description": "Unexpected Error",
2959+
"content": {
2960+
"application/json": {
2961+
"schema": {
2962+
"$ref": "#/components/schemas/Error"
2963+
}
2964+
}
2965+
}
2966+
}
2967+
},
2968+
"security": [{
2969+
"basic_auth": []
2970+
}]
2971+
}
2972+
},
2973+
"/tags/gcp/{key}": {
2974+
"get": {
2975+
"tags": [
2976+
"Tags"
2977+
],
2978+
"summary": "Query to obtain GCP tags",
2979+
"operationId": "getGCPTagKeyData",
2980+
"parameters": [{
2981+
"$ref": "#/components/parameters/QueryFilter"
2982+
},
2983+
{
2984+
"$ref": "#/components/parameters/QueryOffset"
2985+
},
2986+
{
2987+
"$ref": "#/components/parameters/ReportQueryLimit"
2988+
},
2989+
{
2990+
"name": "key",
2991+
"in": "path",
2992+
"description": "The tag key to get",
2993+
"required": true,
2994+
"schema": {
2995+
"type": "string",
2996+
"example": "production"
2997+
}
2998+
}
2999+
],
3000+
"responses": {
3001+
"200": {
3002+
"description": "A paginated report object",
3003+
"content": {
3004+
"application/json": {
3005+
"schema": {
3006+
"$ref": "#/components/schemas/Tags"
3007+
}
3008+
}
3009+
}
3010+
},
3011+
"400": {
3012+
"description": "Invalid query parameter",
3013+
"content": {
3014+
"application/json": {
3015+
"schema": {
3016+
"$ref": "#/components/schemas/Error"
3017+
}
3018+
}
3019+
}
3020+
},
3021+
"401": {
3022+
"description": "Unauthorized"
3023+
},
3024+
"404": {
3025+
"description": "Not Found",
3026+
"content": {
3027+
"application/json": {
3028+
"schema": {
3029+
"$ref": "#/components/schemas/Error"
3030+
}
3031+
}
3032+
}
3033+
},
3034+
"500": {
3035+
"description": "Unexpected Error",
3036+
"content": {
3037+
"application/json": {
3038+
"schema": {
3039+
"$ref": "#/components/schemas/Error"
3040+
}
3041+
}
3042+
}
3043+
}
3044+
},
3045+
"security": [{
3046+
"basic_auth": []
3047+
}]
3048+
}
3049+
},
29233050
"/tags/openshift/": {
29243051
"get": {
29253052
"tags": [

koku/api/tags/gcp/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# noqa

koku/api/tags/gcp/queries.py

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#
2+
# Copyright 2021 Red Hat, Inc.
3+
#
4+
# This program is free software: you can redistribute it and/or modify
5+
# it under the terms of the GNU Affero General Public License as
6+
# published by the Free Software Foundation, either version 3 of the
7+
# License, or (at your option) any later version.
8+
#
9+
# This program is distributed in the hope that it will be useful,
10+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
# GNU Affero General Public License for more details.
13+
#
14+
# You should have received a copy of the GNU Affero General Public License
15+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
16+
#
17+
"""GCP Tag Query Handling."""
18+
import logging
19+
from copy import deepcopy
20+
21+
from api.models import Provider
22+
from api.report.gcp.provider_map import GCPProviderMap
23+
from api.tags.queries import TagQueryHandler
24+
from reporting.models import GCPTagsSummary
25+
from reporting.provider.gcp.models import GCPTagsValues
26+
27+
28+
LOG = logging.getLogger(__name__)
29+
30+
31+
class GCPTagQueryHandler(TagQueryHandler):
32+
"""Handles tag queries and responses for GCP."""
33+
34+
provider = Provider.PROVIDER_GCP
35+
data_sources = [{"db_table": GCPTagsSummary, "db_column_period": "cost_entry_bill__billing_period"}]
36+
TAGS_VALUES_SOURCE = [{"db_table": GCPTagsValues, "fields": ["key"]}]
37+
SUPPORTED_FILTERS = TagQueryHandler.SUPPORTED_FILTERS + ["account", "project"]
38+
39+
def __init__(self, parameters):
40+
"""Establish GCP report query handler.
41+
42+
Args:
43+
parameters (QueryParameters): parameter object for query
44+
45+
"""
46+
self._parameters = parameters
47+
if not hasattr(self, "_mapper"):
48+
self._mapper = GCPProviderMap(provider=self.provider, report_type=parameters.report_type)
49+
# super() needs to be called after _mapper is set
50+
super().__init__(parameters)
51+
52+
@property
53+
def filter_map(self):
54+
"""Establish which filter map to use based on tag API."""
55+
filter_map = deepcopy(TagQueryHandler.FILTER_MAP)
56+
if self._parameters.get_filter("value"):
57+
filter_map.update(
58+
{
59+
"account": [
60+
{"field": "account_ids", "operation": "icontains", "composition_key": "account_filter"}
61+
],
62+
"project": [
63+
{"field": "project_ids", "operation": "icontains", "composition_key": "project_filter"},
64+
{"field": "project_names", "operation": "icontains", "composition_key": "project_filter"},
65+
],
66+
}
67+
)
68+
else:
69+
filter_map.update(
70+
{
71+
"account": [
72+
{"field": "account_id", "operation": "icontains", "composition_key": "account_filter"}
73+
],
74+
"project": [
75+
{"field": "project_id", "operation": "icontains", "composition_key": "project_filter"},
76+
{"field": "project_name", "operation": "icontains", "composition_key": "project_filter"},
77+
],
78+
}
79+
)
80+
return filter_map

koku/api/tags/gcp/view.py

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#
2+
# Copyright 2021 Red Hat, Inc.
3+
#
4+
# This program is free software: you can redistribute it and/or modify
5+
# it under the terms of the GNU Affero General Public License as
6+
# published by the Free Software Foundation, either version 3 of the
7+
# License, or (at your option) any later version.
8+
#
9+
# This program is distributed in the hope that it will be useful,
10+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
# GNU Affero General Public License for more details.
13+
#
14+
# You should have received a copy of the GNU Affero General Public License
15+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
16+
#
17+
"""View for GCP tags."""
18+
from api.common.permissions.gcp_access import GcpAccessPermission
19+
from api.tags.gcp.queries import GCPTagQueryHandler
20+
from api.tags.serializers import GCPTagsQueryParamSerializer
21+
from api.tags.view import TagView
22+
from reporting.provider.gcp.models import GCPTagsSummary
23+
24+
25+
class GCPTagView(TagView):
26+
"""Get GCP tags."""
27+
28+
provider = "gcp"
29+
serializer = GCPTagsQueryParamSerializer
30+
query_handler = GCPTagQueryHandler
31+
tag_handler = [GCPTagsSummary]
32+
permission_classes = [GcpAccessPermission]

koku/api/tags/queries.py

-1
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,6 @@ def get_tags(self):
322322
tag_keys_query = tag_keys_query.annotate(**annotations)
323323
for annotation_key in annotations.keys():
324324
vals.append(annotation_key)
325-
326325
exclusion = self._get_exclusions("key")
327326
tag_keys = list(tag_keys_query.filter(self.query_filter).exclude(exclusion).values_list(*vals).all())
328327
converted = self._convert_to_dict(tag_keys, vals)

koku/api/tags/serializers.py

+33
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
OCP_FILTER_OP_FIELDS = ["project", "enabled", "cluster"]
2626
AWS_FILTER_OP_FIELDS = ["account"]
2727
AZURE_FILTER_OP_FIELDS = ["subscription_guid"]
28+
GCP_FILTER_OP_FIELDS = ["account", "project"]
2829

2930

3031
class FilterSerializer(serializers.Serializer):
@@ -148,6 +149,18 @@ def __init__(self, *args, **kwargs):
148149
)
149150

150151

152+
class GCPFilterSerializer(FilterSerializer):
153+
"""Serializer for handling tag query parameter filter."""
154+
155+
account = StringOrListField(child=serializers.CharField(), required=False)
156+
project = StringOrListField(child=serializers.CharField(), required=False)
157+
158+
def __init__(self, *args, **kwargs):
159+
"""Initialize the GCPFilterSerializer."""
160+
super().__init__(*args, **kwargs)
161+
add_operator_specified_fields(self.fields, GCP_FILTER_OP_FIELDS)
162+
163+
151164
class TagsQueryParamSerializer(serializers.Serializer):
152165
"""Serializer for handling query parameters."""
153166

@@ -247,3 +260,23 @@ class OCPAzureTagsQueryParamSerializer(AzureTagsQueryParamSerializer, OCPTagsQue
247260
"""Serializer for handling OCP-on-Azure tag query parameters."""
248261

249262
filter = OCPAzureFilterSerializer(required=False)
263+
264+
265+
class GCPTagsQueryParamSerializer(TagsQueryParamSerializer):
266+
"""Serializer for handling GCP tag query parameters."""
267+
268+
filter = GCPFilterSerializer(required=False)
269+
270+
def validate_filter(self, value):
271+
"""Validate incoming filter data.
272+
273+
Args:
274+
data (Dict): data to be validated
275+
Returns:
276+
(Dict): Validated data
277+
Raises:
278+
(ValidationError): if filter field inputs are invalid
279+
280+
"""
281+
validate_field(self, "filter", GCPFilterSerializer, value)
282+
return value

koku/api/tags/test/gcp/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# noqa

0 commit comments

Comments
 (0)