From b745ef52794a40386658f20c1070e70ed8629b8d Mon Sep 17 00:00:00 2001 From: David N Date: Mon, 10 Jun 2024 10:32:58 -0400 Subject: [PATCH 1/5] [COST-5114] Add type ahead support for the new EC2 fields --- .../aws_ec2_compute_instances/__init__.py | 0 .../aws_ec2_compute_instances/view.py | 66 +++++++++++++++++++ .../aws_ec2_compute_os/__init__.py | 0 .../resource_types/aws_ec2_compute_os/view.py | 59 +++++++++++++++++ koku/api/resource_types/test/test_views.py | 10 ++- koku/api/urls.py | 12 ++++ koku/api/views.py | 2 + 7 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 koku/api/resource_types/aws_ec2_compute_instances/__init__.py create mode 100644 koku/api/resource_types/aws_ec2_compute_instances/view.py create mode 100644 koku/api/resource_types/aws_ec2_compute_os/__init__.py create mode 100644 koku/api/resource_types/aws_ec2_compute_os/view.py diff --git a/koku/api/resource_types/aws_ec2_compute_instances/__init__.py b/koku/api/resource_types/aws_ec2_compute_instances/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/koku/api/resource_types/aws_ec2_compute_instances/view.py b/koku/api/resource_types/aws_ec2_compute_instances/view.py new file mode 100644 index 0000000000..b5940fcc4d --- /dev/null +++ b/koku/api/resource_types/aws_ec2_compute_instances/view.py @@ -0,0 +1,66 @@ +# +# Copyright 2024 Red Hat Inc. +# SPDX-License-Identifier: Apache-2.0 +# +"""View for AWS accounts.""" +from django.conf import settings +from django.db.models import F +from django.db.models.functions import Coalesce +from django.utils.decorators import method_decorator +from django.views.decorators.vary import vary_on_headers +from rest_framework import filters +from rest_framework import generics +from rest_framework import status +from rest_framework.response import Response + +from api.common import CACHE_RH_IDENTITY_HEADER +from api.common.pagination import ResourceTypeViewPaginator +from api.common.permissions.aws_access import AwsAccessPermission +from api.resource_types.serializers import ResourceTypeSerializer +from reporting.provider.aws.models import AWSCostEntryLineItemSummaryByEC2Compute + + +class AWSEC2ComputeInstanceView(generics.ListAPIView): + """API GET list view for AWS EC2 instances.""" + + queryset = ( + AWSCostEntryLineItemSummaryByEC2Compute.objects.annotate( + **( + { + "value": F("resource_id"), + "alias": Coalesce(F("instance_name"), "resource_id"), + } + ) + ) + .values("value", "alias") + .distinct() + ) + + serializer_class = ResourceTypeSerializer + permission_classes = [AwsAccessPermission] + filter_backends = [filters.OrderingFilter, filters.SearchFilter] + ordering = ["value", "alias"] + search_fields = ["value", "alias"] + pagination_class = ResourceTypeViewPaginator + + @method_decorator(vary_on_headers(CACHE_RH_IDENTITY_HEADER)) + def list(self, request): + # Reads the aws ec2 compute instances info and displays values related to what the user has access to. + supported_query_params = ["search", "limit"] + user_access = [] + error_message = {} + # Test for only supported query_params + if self.request.query_params: + for key in self.request.query_params: + if key not in supported_query_params: + error_message[key] = [{"Unsupported parameter"}] + return Response(error_message, status=status.HTTP_400_BAD_REQUEST) + if settings.ENHANCED_ORG_ADMIN and request.user.admin: + return super().list(request) + elif request.user.access: + user_access = request.user.access.get("aws.account", {}).get("read", []) + if user_access and user_access[0] == "*": + return super().list(request) + self.queryset = self.queryset.filter(usage_account_id__in=user_access) + + return super().list(request) diff --git a/koku/api/resource_types/aws_ec2_compute_os/__init__.py b/koku/api/resource_types/aws_ec2_compute_os/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/koku/api/resource_types/aws_ec2_compute_os/view.py b/koku/api/resource_types/aws_ec2_compute_os/view.py new file mode 100644 index 0000000000..255c810a2b --- /dev/null +++ b/koku/api/resource_types/aws_ec2_compute_os/view.py @@ -0,0 +1,59 @@ +# +# Copyright 2024 Red Hat Inc. +# SPDX-License-Identifier: Apache-2.0 +# +"""View for AWS accounts.""" +from django.conf import settings +from django.db.models import F +from django.utils.decorators import method_decorator +from django.views.decorators.vary import vary_on_headers +from rest_framework import filters +from rest_framework import generics +from rest_framework import status +from rest_framework.response import Response + +from api.common import CACHE_RH_IDENTITY_HEADER +from api.common.pagination import ResourceTypeViewPaginator +from api.common.permissions.aws_access import AwsAccessPermission +from api.resource_types.serializers import ResourceTypeSerializer +from reporting.provider.aws.models import AWSCostEntryLineItemSummaryByEC2Compute + + +class AWSEC2ComputeOperatingSystemView(generics.ListAPIView): + """API GET list view for AWS EC2 instances.""" + + queryset = ( + AWSCostEntryLineItemSummaryByEC2Compute.objects.annotate(**{"value": F("operating_system")}) + .values("value") + .distinct() + .filter(operating_system__isnull=False) + ) + + serializer_class = ResourceTypeSerializer + permission_classes = [AwsAccessPermission] + filter_backends = [filters.OrderingFilter, filters.SearchFilter] + ordering = ["value"] + search_fields = ["value"] + pagination_class = ResourceTypeViewPaginator + + @method_decorator(vary_on_headers(CACHE_RH_IDENTITY_HEADER)) + def list(self, request): + # Reads the aws ec2 compute instance info and displays values related to what the user has access to. + supported_query_params = ["search", "limit"] + user_access = [] + error_message = {} + # Test for only supported query_params + if self.request.query_params: + for key in self.request.query_params: + if key not in supported_query_params: + error_message[key] = [{"Unsupported parameter"}] + return Response(error_message, status=status.HTTP_400_BAD_REQUEST) + if settings.ENHANCED_ORG_ADMIN and request.user.admin: + return super().list(request) + elif request.user.access: + user_access = request.user.access.get("aws.account", {}).get("read", []) + if user_access and user_access[0] == "*": + return super().list(request) + self.queryset = self.queryset.filter(usage_account_id__in=user_access) + + return super().list(request) diff --git a/koku/api/resource_types/test/test_views.py b/koku/api/resource_types/test/test_views.py index e3cf9db038..05346ec3f3 100644 --- a/koku/api/resource_types/test/test_views.py +++ b/koku/api/resource_types/test/test_views.py @@ -75,7 +75,15 @@ class ResourceTypesViewTest(IamTestCase): """Tests the resource types views.""" ENDPOINTS_RTYPE = ["resource-types"] - ENDPOINTS_AWS = ["aws-accounts", "aws-regions", "aws-services", "aws-organizational-units", "aws-categories"] + ENDPOINTS_AWS = [ + "aws-accounts", + "aws-regions", + "aws-services", + "aws-organizational-units", + "aws-categories", + "aws-ec2-compute-instances", + "aws-ec2-compute-os", + ] ENDPOINTS_GCP = ["gcp-accounts", "gcp-projects", "gcp-regions", "gcp-services"] ENDPOINTS_AZURE = ["azure-subscription-guids", "azure-services", "azure-regions"] ENDPOINTS_OPENSHIFT = ["openshift-clusters", "openshift-nodes", "openshift-projects"] diff --git a/koku/api/urls.py b/koku/api/urls.py index b1335b4c21..7cd66a05d4 100644 --- a/koku/api/urls.py +++ b/koku/api/urls.py @@ -15,6 +15,8 @@ from api.views import AWSCategoryView from api.views import AWSCostForecastView from api.views import AWSCostView +from api.views import AWSEC2ComputeInstanceView +from api.views import AWSEC2ComputeOperatingSystemView from api.views import AWSInstanceTypeView from api.views import AWSOrganizationalUnitView from api.views import AWSOrgView @@ -396,6 +398,16 @@ path("resource-types/", ResourceTypeView.as_view(), name="resource-types"), path("user-access/", UserAccessView.as_view(), name="user-access"), path("resource-types/aws-accounts/", AWSAccountView.as_view(), name="aws-accounts"), + path( + "resource-types/aws-ec2-compute-instances/", + AWSEC2ComputeInstanceView.as_view(), + name="aws-ec2-compute-instances", + ), + path( + "resource-types/aws-ec2-compute-os/", + AWSEC2ComputeOperatingSystemView.as_view(), + name="aws-ec2-compute-os", + ), path( "resource-types/aws-categories/", cache_page(timeout=settings.CACHE_MIDDLEWARE_SECONDS, key_prefix=AWS_CACHE_PREFIX)(AWSCategoryView.as_view()), diff --git a/koku/api/views.py b/koku/api/views.py index 43ecef9b0f..7b38f93cb4 100644 --- a/koku/api/views.py +++ b/koku/api/views.py @@ -53,6 +53,8 @@ from api.report.ocp.view import OCPVolumeView from api.resource_types.aws_accounts.view import AWSAccountView from api.resource_types.aws_category.view import AWSCategoryView +from api.resource_types.aws_ec2_compute_instances.view import AWSEC2ComputeInstanceView +from api.resource_types.aws_ec2_compute_os.view import AWSEC2ComputeOperatingSystemView from api.resource_types.aws_org_unit.view import AWSOrganizationalUnitView from api.resource_types.aws_regions.view import AWSAccountRegionView from api.resource_types.aws_services.view import AWSServiceView From 039c01c76a3ffcd28f1055fb985489e66f0e1f8a Mon Sep 17 00:00:00 2001 From: David N Date: Mon, 10 Jun 2024 10:42:19 -0400 Subject: [PATCH 2/5] clean up docstrings --- koku/api/resource_types/aws_ec2_compute_instances/view.py | 2 +- koku/api/resource_types/aws_ec2_compute_os/view.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/koku/api/resource_types/aws_ec2_compute_instances/view.py b/koku/api/resource_types/aws_ec2_compute_instances/view.py index b5940fcc4d..8f7f84f373 100644 --- a/koku/api/resource_types/aws_ec2_compute_instances/view.py +++ b/koku/api/resource_types/aws_ec2_compute_instances/view.py @@ -21,7 +21,7 @@ class AWSEC2ComputeInstanceView(generics.ListAPIView): - """API GET list view for AWS EC2 instances.""" + """API GET list view for AWS EC2 compute instances.""" queryset = ( AWSCostEntryLineItemSummaryByEC2Compute.objects.annotate( diff --git a/koku/api/resource_types/aws_ec2_compute_os/view.py b/koku/api/resource_types/aws_ec2_compute_os/view.py index 255c810a2b..84d33f288c 100644 --- a/koku/api/resource_types/aws_ec2_compute_os/view.py +++ b/koku/api/resource_types/aws_ec2_compute_os/view.py @@ -20,7 +20,7 @@ class AWSEC2ComputeOperatingSystemView(generics.ListAPIView): - """API GET list view for AWS EC2 instances.""" + """API GET list view for AWS EC2 compute operating systems.""" queryset = ( AWSCostEntryLineItemSummaryByEC2Compute.objects.annotate(**{"value": F("operating_system")}) From be70819f92cdafa65da2570d3f3acbe954b198d3 Mon Sep 17 00:00:00 2001 From: David N Date: Fri, 12 Jul 2024 08:18:32 -0400 Subject: [PATCH 3/5] add instance_name field to serializer address pr feedback --- .../aws_ec2_compute_instances/view.py | 16 +++++----------- .../resource_types/aws_ec2_compute_os/view.py | 2 +- koku/api/resource_types/serializers.py | 1 + 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/koku/api/resource_types/aws_ec2_compute_instances/view.py b/koku/api/resource_types/aws_ec2_compute_instances/view.py index 8f7f84f373..f76ab4b3e7 100644 --- a/koku/api/resource_types/aws_ec2_compute_instances/view.py +++ b/koku/api/resource_types/aws_ec2_compute_instances/view.py @@ -25,29 +25,24 @@ class AWSEC2ComputeInstanceView(generics.ListAPIView): queryset = ( AWSCostEntryLineItemSummaryByEC2Compute.objects.annotate( - **( - { - "value": F("resource_id"), - "alias": Coalesce(F("instance_name"), "resource_id"), - } - ) + value=F("resource_id"), ec2_instance_name=Coalesce(F("instance_name"), "resource_id") ) - .values("value", "alias") + .values("value", "ec2_instance_name") .distinct() ) serializer_class = ResourceTypeSerializer permission_classes = [AwsAccessPermission] filter_backends = [filters.OrderingFilter, filters.SearchFilter] - ordering = ["value", "alias"] - search_fields = ["value", "alias"] + ordering = ["value", "ec2_instance_name"] + search_fields = ["value", "ec2_instance_name"] pagination_class = ResourceTypeViewPaginator @method_decorator(vary_on_headers(CACHE_RH_IDENTITY_HEADER)) def list(self, request): # Reads the aws ec2 compute instances info and displays values related to what the user has access to. supported_query_params = ["search", "limit"] - user_access = [] + user_access = None error_message = {} # Test for only supported query_params if self.request.query_params: @@ -62,5 +57,4 @@ def list(self, request): if user_access and user_access[0] == "*": return super().list(request) self.queryset = self.queryset.filter(usage_account_id__in=user_access) - return super().list(request) diff --git a/koku/api/resource_types/aws_ec2_compute_os/view.py b/koku/api/resource_types/aws_ec2_compute_os/view.py index 84d33f288c..d29c0cb4a8 100644 --- a/koku/api/resource_types/aws_ec2_compute_os/view.py +++ b/koku/api/resource_types/aws_ec2_compute_os/view.py @@ -23,7 +23,7 @@ class AWSEC2ComputeOperatingSystemView(generics.ListAPIView): """API GET list view for AWS EC2 compute operating systems.""" queryset = ( - AWSCostEntryLineItemSummaryByEC2Compute.objects.annotate(**{"value": F("operating_system")}) + AWSCostEntryLineItemSummaryByEC2Compute.objects.annotate(value=F("operating_system")) .values("value") .distinct() .filter(operating_system__isnull=False) diff --git a/koku/api/resource_types/serializers.py b/koku/api/resource_types/serializers.py index 32bfa4615f..63d08dc37d 100644 --- a/koku/api/resource_types/serializers.py +++ b/koku/api/resource_types/serializers.py @@ -12,4 +12,5 @@ class ResourceTypeSerializer(serializers.Serializer): extra_kwargs = {"test": {"error_messages": {"required": "Give yourself a username"}}} cluster_alias = serializers.CharField(source="ocp_cluster_alias", required=False) account_alias = serializers.CharField(source="alias", required=False) + instance_name = serializers.CharField(source="ec2_instance_name", required=False) value = serializers.CharField() From a474a41fc8415a8399058391a3285073d17a8f50 Mon Sep 17 00:00:00 2001 From: David N Date: Tue, 16 Jul 2024 11:23:38 -0400 Subject: [PATCH 4/5] address feedback - clean up file description --- koku/api/resource_types/aws_ec2_compute_instances/view.py | 2 +- koku/api/resource_types/aws_ec2_compute_os/view.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/koku/api/resource_types/aws_ec2_compute_instances/view.py b/koku/api/resource_types/aws_ec2_compute_instances/view.py index f76ab4b3e7..b44c9331c5 100644 --- a/koku/api/resource_types/aws_ec2_compute_instances/view.py +++ b/koku/api/resource_types/aws_ec2_compute_instances/view.py @@ -2,7 +2,7 @@ # Copyright 2024 Red Hat Inc. # SPDX-License-Identifier: Apache-2.0 # -"""View for AWS accounts.""" +"""View for AWS EC2 instances.""" from django.conf import settings from django.db.models import F from django.db.models.functions import Coalesce diff --git a/koku/api/resource_types/aws_ec2_compute_os/view.py b/koku/api/resource_types/aws_ec2_compute_os/view.py index d29c0cb4a8..9da367e72f 100644 --- a/koku/api/resource_types/aws_ec2_compute_os/view.py +++ b/koku/api/resource_types/aws_ec2_compute_os/view.py @@ -2,7 +2,7 @@ # Copyright 2024 Red Hat Inc. # SPDX-License-Identifier: Apache-2.0 # -"""View for AWS accounts.""" +"""View for AWS EC2 operating systems.""" from django.conf import settings from django.db.models import F from django.utils.decorators import method_decorator From 7daa8d9baf984661388b2378593baf0f9a6e9ecc Mon Sep 17 00:00:00 2001 From: David N Date: Tue, 16 Jul 2024 19:32:58 -0400 Subject: [PATCH 5/5] update open api spec --- docs/specs/openapi.json | 68 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/docs/specs/openapi.json b/docs/specs/openapi.json index d34e779c26..1754af0e7b 100644 --- a/docs/specs/openapi.json +++ b/docs/specs/openapi.json @@ -3256,6 +3256,74 @@ } } }, + "/resource-types/aws-ec2-compute-instances/": { + "get": { + "tags": [ + "Resource Type" + ], + "summary": "List AWS EC2 Instances For RBAC", + "operationId": "listResourcesAwsEC2Instances", + "parameters": [{ + "$ref": "#/components/parameters/QueryOffset" + }, + { + "$ref": "#/components/parameters/QueryLimit" + }, + { + "$ref": "#/components/parameters/QueryValue" + }, + { + "$ref": "#/components/parameters/QueryOrder" + } + ], + "responses": { + "200": { + "description": "| - 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceTypePagination" + } + } + } + } + } + } + }, + "/resource-types/aws-ec2-compute-os/": { + "get": { + "tags": [ + "Resource Type" + ], + "summary": "List AWS EC2 Operating Systems For RBAC", + "operationId": "listResourcesAwsEC2OperatingSystems", + "parameters": [{ + "$ref": "#/components/parameters/QueryOffset" + }, + { + "$ref": "#/components/parameters/QueryLimit" + }, + { + "$ref": "#/components/parameters/QueryValue" + }, + { + "$ref": "#/components/parameters/QueryOrder" + } + ], + "responses": { + "200": { + "description": "| - 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResourceTypePagination" + } + } + } + } + } + } + }, "/resource-types/gcp-accounts/": { "get": { "tags": [