From 6efda30e3de4ca984b59a8c9e1b07f7ca80fd6a9 Mon Sep 17 00:00:00 2001 From: Evan Purkhiser Date: Tue, 14 Apr 2026 17:00:04 -0400 Subject: [PATCH] ref(aws-lambda): Remove legacy pipeline views These are no longer needed now that the integration setup is fully API driven. Refs [VDY-80: Remove legacy AWS Lambda integration setup views](https://linear.app/getsentry/issue/VDY-80/remove-legacy-aws-lambda-integration-setup-views) --- .../integrations/aws_lambda/integration.py | 228 +------- .../aws_lambda/test_integration.py | 526 +----------------- 2 files changed, 5 insertions(+), 749 deletions(-) diff --git a/src/sentry/integrations/aws_lambda/integration.py b/src/sentry/integrations/aws_lambda/integration.py index 7681be867a32fc..923e9af93d3a5f 100644 --- a/src/sentry/integrations/aws_lambda/integration.py +++ b/src/sentry/integrations/aws_lambda/integration.py @@ -6,7 +6,6 @@ from botocore.exceptions import ClientError from django.http.request import HttpRequest -from django.http.response import HttpResponseBase from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from rest_framework.fields import CharField, ChoiceField, IntegerField, ListField @@ -26,14 +25,10 @@ from sentry.integrations.models.integration import Integration from sentry.integrations.models.organization_integration import OrganizationIntegration from sentry.integrations.pipeline import IntegrationPipeline -from sentry.organizations.services.organization import organization_service from sentry.organizations.services.organization.model import RpcOrganization from sentry.pipeline.types import PipelineStepResult -from sentry.pipeline.views.base import ApiPipelineSteps, PipelineView, render_react_view -from sentry.projects.services.project import project_service +from sentry.pipeline.views.base import ApiPipelineSteps from sentry.silo.base import control_silo_function -from sentry.users.models.user import User -from sentry.users.services.user.serial import serialize_rpc_user from sentry.utils.concurrent import ContextPropagatingThreadPoolExecutor from sentry.utils.sdk import capture_exception @@ -412,13 +407,8 @@ class AwsLambdaIntegrationProvider(IntegrationProvider): integration_cls = AwsLambdaIntegration features = frozenset([IntegrationFeatures.SERVERLESS]) - def get_pipeline_views(self) -> list[PipelineView[IntegrationPipeline]]: - return [ - AwsLambdaProjectSelectPipelineView(), - AwsLambdaCloudFormationPipelineView(), - AwsLambdaListFunctionsPipelineView(), - AwsLambdaSetupLayerPipelineView(), - ] + def get_pipeline_views(self) -> list: + return [] def get_pipeline_api_steps(self) -> ApiPipelineSteps[IntegrationPipeline]: return [ @@ -475,215 +465,3 @@ def post_install( organization_id=organization.id, integration=integration ): oi.update(config={"default_project_id": default_project_id}) - - -class AwsLambdaProjectSelectPipelineView: - def dispatch(self, request: HttpRequest, pipeline: IntegrationPipeline) -> HttpResponseBase: - # if we have the projectId, go to the next step - if "projectId" in request.GET: - pipeline.bind_state("project_id", request.GET["projectId"]) - return pipeline.next_step() - - assert pipeline.organization is not None - organization = pipeline.organization - projects = organization.projects - - # if only one project, automatically use that - if len(projects) == 1: - pipeline.bind_state("skipped_project_select", True) - pipeline.bind_state("project_id", projects[0].id) - return pipeline.next_step() - - projects = sorted(projects, key=lambda p: p.slug) - serialized_projects = project_service.serialize_many( - organization_id=organization.id, - filter=dict(project_ids=[p.id for p in projects]), - ) - return render_react_view( - request, "awsLambdaProjectSelect", {"projects": serialized_projects} - ) - - -class AwsLambdaCloudFormationPipelineView: - def dispatch(self, request: HttpRequest, pipeline: IntegrationPipeline) -> HttpResponseBase: - curr_step = 0 if pipeline.fetch_state("skipped_project_select") else 1 - - def render_response(error=None): - assert pipeline.organization is not None - serialized_organization = organization_service.serialize_organization( - id=pipeline.organization.id, - as_user=( - serialize_rpc_user(request.user) if isinstance(request.user, User) else None - ), - ) - template_url = options.get("aws-lambda.cloudformation-url") - context = { - "baseCloudformationUrl": "https://console.aws.amazon.com/cloudformation/home#/stacks/create/review", - "templateUrl": template_url, - "stackName": "Sentry-Monitoring-Stack", - "regionList": ALL_AWS_REGIONS, - "accountNumber": pipeline.fetch_state("account_number"), - "region": pipeline.fetch_state("region"), - "error": error, - "initialStepNumber": curr_step, - "organization": serialized_organization, - "awsExternalId": pipeline.fetch_state("aws_external_id"), - } - return render_react_view(request, "awsLambdaCloudformation", context) - - # form submit adds accountNumber to GET parameters - if "accountNumber" in request.GET: - data = request.GET - - # load parameters post request - account_number = data["accountNumber"] - region = data["region"] - aws_external_id = data["awsExternalId"] - - pipeline.bind_state("account_number", account_number) - pipeline.bind_state("region", region) - pipeline.bind_state("aws_external_id", aws_external_id) - - # now validate the arn works - try: - gen_aws_client(account_number, region, aws_external_id) - except ClientError: - return render_response( - _("Please validate the Cloudformation stack was created successfully") - ) - except ConfigurationError: - # if we have a configuration error, we should blow up the pipeline - raise - except Exception as e: - logger.warning( - "AwsLambdaCloudFormationPipelineView.unexpected_error", - extra={"error": str(e)}, - ) - return render_response(_("Unknown error")) - - # if no error, continue - return pipeline.next_step() - - return render_response() - - -class AwsLambdaListFunctionsPipelineView: - def dispatch(self, request: HttpRequest, pipeline: IntegrationPipeline) -> HttpResponseBase: - if request.method == "POST": - raw_data = request.POST - data = {} - for key, val in raw_data.items(): - # form posts have string values for booleans and this form only sends booleans - data[key] = val == "true" - pipeline.bind_state("enabled_lambdas", data) - return pipeline.next_step() - - account_number = pipeline.fetch_state("account_number") - region = pipeline.fetch_state("region") - aws_external_id = pipeline.fetch_state("aws_external_id") - - lambda_client = gen_aws_client(account_number, region, aws_external_id) - - lambda_functions = get_supported_functions(lambda_client) - - curr_step = 2 if pipeline.fetch_state("skipped_project_select") else 3 - - return render_react_view( - request, - "awsLambdaFunctionSelect", - {"lambdaFunctions": lambda_functions, "initialStepNumber": curr_step}, - ) - - -class AwsLambdaSetupLayerPipelineView: - def dispatch(self, request: HttpRequest, pipeline: IntegrationPipeline) -> HttpResponseBase: - if "finish_pipeline" in request.GET: - return pipeline.finish_pipeline() - - assert pipeline.organization is not None - organization = pipeline.organization - - account_number = pipeline.fetch_state("account_number") - region = pipeline.fetch_state("region") - - project_id = pipeline.fetch_state("project_id") - aws_external_id = pipeline.fetch_state("aws_external_id") - enabled_lambdas = pipeline.fetch_state("enabled_lambdas") - assert enabled_lambdas is not None - - sentry_project_dsn = get_dsn_for_project(organization.id, project_id) - - lambda_client = gen_aws_client(account_number, region, aws_external_id) - - lambda_functions = get_supported_functions(lambda_client) - lambda_functions.sort(key=lambda x: x["FunctionName"].lower()) - - def is_lambda_enabled(function): - name = function["FunctionName"] - # check to see if the user wants to enable this function - return enabled_lambdas.get(name) - - lambda_functions = filter(is_lambda_enabled, lambda_functions) - - def _enable_lambda(function): - try: - enable_single_lambda(lambda_client, function, sentry_project_dsn) - return (True, function, None) - except Exception as e: - return (False, function, e) - - failures = [] - success_count = 0 - - with ContextPropagatingThreadPoolExecutor( - max_workers=options.get("aws-lambda.thread-count") - ) as _lambda_setup_thread_pool: - # use threading here to parallelize requests - # no timeout on the thread since the underlying request will time out - # if it takes too long - for success, function, e in _lambda_setup_thread_pool.map( - _enable_lambda, lambda_functions - ): - name = function["FunctionName"] - if success: - success_count += 1 - else: - # need to make sure we catch any error to continue to the next function - err_message: str | _StrPromise = str(e) - is_custom_err, err_message = get_sentry_err_message(err_message) - if not is_custom_err: - capture_exception(e) - err_message = _("Unknown Error") - failures.append({"name": function["FunctionName"], "error": err_message}) - logger.info( - "update_function_configuration.error", - extra={ - "organization_id": organization.id, - "lambda_name": name, - "account_number": account_number, - "region": region, - "error": str(e), - }, - ) - - analytics.record( - IntegrationServerlessSetup( - user_id=request.user.id, - organization_id=organization.id, - integration="aws_lambda", - success_count=success_count, - failure_count=len(failures), - ) - ) - - # if we have failures, show them to the user - # otherwise, finish - - if failures: - return render_react_view( - request, - "awsLambdaFailureDetails", - {"lambdaFunctionFailures": failures, "successCount": success_count}, - ) - else: - return pipeline.finish_pipeline() diff --git a/tests/sentry/integrations/aws_lambda/test_integration.py b/tests/sentry/integrations/aws_lambda/test_integration.py index 27068acc21b5e0..ce86024faa8b6b 100644 --- a/tests/sentry/integrations/aws_lambda/test_integration.py +++ b/tests/sentry/integrations/aws_lambda/test_integration.py @@ -1,541 +1,22 @@ from typing import Any -from unittest.mock import ANY, MagicMock, patch -from urllib.parse import urlencode +from unittest.mock import MagicMock, patch from botocore.exceptions import ClientError -from django.http import HttpResponse from django.urls import reverse -from sentry.integrations.aws_lambda import AwsLambdaIntegrationProvider -from sentry.integrations.aws_lambda import integration as aws_lambda_integration -from sentry.integrations.aws_lambda.utils import ALL_AWS_REGIONS from sentry.integrations.models.integration import Integration from sentry.integrations.models.organization_integration import OrganizationIntegration from sentry.integrations.pipeline import IntegrationPipeline from sentry.models.projectkey import ProjectKey -from sentry.organizations.services.organization import organization_service -from sentry.projects.services.project import project_service from sentry.silo.base import SiloMode -from sentry.testutils.cases import APITestCase, IntegrationTestCase -from sentry.testutils.helpers.options import override_options +from sentry.testutils.cases import APITestCase from sentry.testutils.silo import assume_test_silo_mode, control_silo_test -from sentry.users.services.user.serial import serialize_rpc_user - -arn = ( - "arn:aws:cloudformation:us-east-2:599817902985:stack/" - "Sentry-Monitoring-Stack/e42083d0-3e3f-11eb-b66a-0ac9b5db7f30" -) account_number = "599817902985" region = "us-east-2" aws_external_id = "test-external-id-1234" -@control_silo_test -class AwsLambdaIntegrationTest(IntegrationTestCase): - provider = AwsLambdaIntegrationProvider - - def setUp(self) -> None: - super().setUp() - self.projectA = self.create_project(organization=self.organization, slug="projA") - self.projectB = self.create_project(organization=self.organization, slug="projB") - - @patch.object(aws_lambda_integration, "render_react_view", return_value=HttpResponse()) - def test_project_select(self, mock_react_view: MagicMock) -> None: - resp = self.client.get(self.setup_path) - assert resp.status_code == 200 - serialized_projects = project_service.serialize_many( - organization_id=self.projectA.organization_id, - filter=dict(project_ids=[self.projectA.id, self.projectB.id]), - ) - mock_react_view.assert_called_with( - ANY, "awsLambdaProjectSelect", {"projects": serialized_projects} - ) - - @patch.object(aws_lambda_integration, "render_react_view", return_value=HttpResponse()) - def test_one_project(self, mock_react_view: MagicMock) -> None: - with assume_test_silo_mode(SiloMode.MONOLITH): - self.projectB.delete() - resp = self.client.get(self.setup_path) - assert resp.status_code == 200 - mock_react_view.assert_called_with(ANY, "awsLambdaCloudformation", ANY) - - @patch.object(aws_lambda_integration, "render_react_view", return_value=HttpResponse()) - def test_render_cloudformation_view(self, mock_react_view: MagicMock) -> None: - self.pipeline.state.step_index = 1 - resp = self.client.get(self.setup_path) - assert resp.status_code == 200 - mock_react_view.assert_called_with( - ANY, - "awsLambdaCloudformation", - { - "baseCloudformationUrl": "https://console.aws.amazon.com/cloudformation/home#/stacks/create/review", - "templateUrl": "https://example.com/file.json", - "stackName": "Sentry-Monitoring-Stack", - "regionList": ALL_AWS_REGIONS, - "region": None, - "accountNumber": None, - "error": None, - "initialStepNumber": 1, - "organization": organization_service.serialize_organization( - id=self.organization.id, as_user=serialize_rpc_user(self.user) - ), - "awsExternalId": None, - }, - ) - - @patch("sentry.integrations.aws_lambda.integration.gen_aws_client") - @patch.object(aws_lambda_integration, "render_react_view", return_value=HttpResponse()) - def test_set_valid_arn( - self, mock_react_view: MagicMock, mock_gen_aws_client: MagicMock - ) -> None: - self.pipeline.state.step_index = 1 - data = {"region": region, "accountNumber": account_number, "awsExternalId": "my-id"} - resp = self.client.get(self.setup_path + "?" + urlencode(data)) - assert resp.status_code == 200 - mock_react_view.assert_called_with(ANY, "awsLambdaFunctionSelect", ANY) - - @patch("sentry.integrations.aws_lambda.integration.gen_aws_client") - @patch.object(aws_lambda_integration, "render_react_view", return_value=HttpResponse()) - def test_set_arn_with_error( - self, mock_react_view: MagicMock, mock_gen_aws_client: MagicMock - ) -> None: - self.pipeline.state.step_index = 1 - mock_gen_aws_client.side_effect = ClientError({"Error": {}}, "assume_role") - data = {"region": region, "accountNumber": account_number, "awsExternalId": "my-id"} - resp = self.client.get(self.setup_path + "?" + urlencode(data)) - assert resp.status_code == 200 - mock_react_view.assert_called_with( - ANY, - "awsLambdaCloudformation", - # Ensure that the expected value passes through json serialization - { - "baseCloudformationUrl": "https://console.aws.amazon.com/cloudformation/home#/stacks/create/review", - "templateUrl": "https://example.com/file.json", - "stackName": "Sentry-Monitoring-Stack", - "regionList": ALL_AWS_REGIONS, - "region": region, - "accountNumber": account_number, - "error": "Please validate the Cloudformation stack was created successfully", - "initialStepNumber": 1, - "organization": organization_service.serialize_organization( - id=self.organization.id, as_user=serialize_rpc_user(self.user) - ), - "awsExternalId": "my-id", - }, - ) - - @patch("sentry.integrations.aws_lambda.integration.get_supported_functions") - @patch("sentry.integrations.aws_lambda.integration.gen_aws_client") - @patch.object(aws_lambda_integration, "render_react_view", return_value=HttpResponse()) - def test_lambda_list( - self, - mock_react_view: MagicMock, - mock_gen_aws_client: MagicMock, - mock_get_supported_functions: MagicMock, - ) -> None: - mock_get_supported_functions.return_value = [ - {"FunctionName": "lambdaA", "Runtime": "nodejs12.x"}, - {"FunctionName": "lambdaB", "Runtime": "nodejs10.x"}, - {"FunctionName": "lambdaC", "Runtime": "python3.6"}, - {"FunctionName": "lambdaD", "Runtime": "python3.7"}, - {"FunctionName": "lambdaE", "Runtime": "python3.8"}, - {"FunctionName": "lambdaD", "Runtime": "python3.9"}, - ] - - aws_external_id = "12-323" - self.pipeline.state.step_index = 2 - self.pipeline.state.data = { - "region": region, - "accountNumber": account_number, - "aws_external_id": aws_external_id, - "project_id": self.projectA.id, - } - resp = self.client.get(self.setup_path) - assert resp.status_code == 200 - mock_react_view.assert_called_with( - ANY, - "awsLambdaFunctionSelect", - { - "lambdaFunctions": [ - {"FunctionName": "lambdaA", "Runtime": "nodejs12.x"}, - {"FunctionName": "lambdaB", "Runtime": "nodejs10.x"}, - {"FunctionName": "lambdaC", "Runtime": "python3.6"}, - {"FunctionName": "lambdaD", "Runtime": "python3.7"}, - {"FunctionName": "lambdaE", "Runtime": "python3.8"}, - {"FunctionName": "lambdaD", "Runtime": "python3.9"}, - ], - "initialStepNumber": 3, - }, - ) - - @patch("sentry.integrations.aws_lambda.integration.get_supported_functions") - @patch("sentry.integrations.aws_lambda.integration.gen_aws_client") - def test_node_lambda_setup_layer_success( - self, - mock_gen_aws_client, - mock_get_supported_functions, - ): - for layer_name, layer_version, expected_node_options in [ - ("SentryNodeServerlessSDKv7", "5", "-r @sentry/serverless/dist/awslambda-auto"), - ("SentryNodeServerlessSDK", "168", "-r @sentry/serverless/dist/awslambda-auto"), - ("SentryNodeServerlessSDK", "235", "-r @sentry/serverless/dist/awslambda-auto"), - ("SentryNodeServerlessSDK", "236", "-r @sentry/aws-serverless/awslambda-auto"), - ("SentryNodeServerlessSDKv8", "3", "-r @sentry/aws-serverless/awslambda-auto"), - ("SentryNodeServerlessSDKv9", "235", "-r @sentry/aws-serverless/awslambda-auto"), - ]: - with override_options( - { - "aws-lambda.node.layer-name": layer_name, - "aws-lambda.node.layer-version": layer_version, - } - ): - # Ensure we reset everything - self.setUp() - mock_get_supported_functions.reset_mock() - mock_gen_aws_client.reset_mock() - - mock_client = mock_gen_aws_client.return_value - mock_client.update_function_configuration = MagicMock() - mock_client.describe_account = MagicMock( - return_value={"Account": {"Name": "my_name"}} - ) - - mock_get_supported_functions.return_value = [ - { - "FunctionName": "lambdaA", - "Runtime": "nodejs12.x", - "FunctionArn": "arn:aws:lambda:us-east-2:599817902985:function:lambdaA", - }, - { - "FunctionName": "lambdaB", - "Runtime": "nodejs10.x", - "FunctionArn": "arn:aws:lambda:us-east-2:599817902985:function:lambdaB", - }, - ] - - aws_external_id = "12-323" - self.pipeline.state.step_index = 2 - self.pipeline.state.data = { - "region": region, - "account_number": account_number, - "aws_external_id": aws_external_id, - "project_id": self.projectA.id, - } - - with assume_test_silo_mode(SiloMode.CELL): - sentry_project_dsn = ProjectKey.get_default(project=self.projectA).get_dsn( - public=True - ) - - # TODO: pass in lambdaA=false - # having issues with reading json data - # request.POST looks like {"lambdaB": "True"} - # string instead of boolean - resp = self.client.post(self.setup_path, {"lambdaB": "true", "lambdaA": "false"}) - - assert resp.status_code == 200 - - mock_client.update_function_configuration.assert_called_once_with( - FunctionName="lambdaB", - Layers=[f"arn:aws:lambda:us-east-2:1234:layer:{layer_name}:{layer_version}"], - Environment={ - "Variables": { - "NODE_OPTIONS": expected_node_options, - "SENTRY_DSN": sentry_project_dsn, - "SENTRY_TRACES_SAMPLE_RATE": "1.0", - } - }, - ) - - integration = Integration.objects.get(provider=self.provider.key) - assert integration.name == "my_name us-east-2" - assert integration.external_id == "599817902985-us-east-2" - assert integration.metadata == { - "region": region, - "account_number": account_number, - "aws_external_id": aws_external_id, - } - assert OrganizationIntegration.objects.filter( - integration=integration, organization_id=self.organization.id - ) - - @patch("sentry.integrations.aws_lambda.integration.get_supported_functions") - @patch("sentry.integrations.aws_lambda.integration.gen_aws_client") - def test_python_lambda_setup_layer_success( - self, mock_gen_aws_client, mock_get_supported_functions - ): - mock_client = mock_gen_aws_client.return_value - mock_client.update_function_configuration = MagicMock() - mock_client.describe_account = MagicMock(return_value={"Account": {"Name": "my_name"}}) - - mock_get_supported_functions.return_value = [ - { - "FunctionName": "lambdaA", - "Handler": "lambda_handler.test_handler", - "Runtime": "python3.6", - "FunctionArn": "arn:aws:lambda:us-east-2:599817902985:function:lambdaA", - } - ] - - aws_external_id = "12-323" - self.pipeline.state.step_index = 2 - self.pipeline.state.data = { - "region": region, - "account_number": account_number, - "aws_external_id": aws_external_id, - "project_id": self.projectA.id, - } - - with assume_test_silo_mode(SiloMode.CELL): - sentry_project_dsn = ProjectKey.get_default(project=self.projectA).get_dsn(public=True) - - resp = self.client.post(self.setup_path, {"lambdaA": "true"}) - - assert resp.status_code == 200 - - mock_client.update_function_configuration.assert_called_once_with( - FunctionName="lambdaA", - Layers=["arn:aws:lambda:us-east-2:1234:layer:my-python-layer:34"], - Environment={ - "Variables": { - "SENTRY_INITIAL_HANDLER": "lambda_handler.test_handler", - "SENTRY_DSN": sentry_project_dsn, - "SENTRY_TRACES_SAMPLE_RATE": "1.0", - } - }, - Handler="sentry_sdk.integrations.init_serverless_sdk.sentry_lambda_handler", - ) - - integration = Integration.objects.get(provider=self.provider.key) - assert integration.name == "my_name us-east-2" - assert integration.external_id == "599817902985-us-east-2" - assert integration.metadata == { - "region": region, - "account_number": account_number, - "aws_external_id": aws_external_id, - } - assert OrganizationIntegration.objects.filter( - integration=integration, organization_id=self.organization.id - ) - - @patch("sentry.integrations.aws_lambda.integration.get_supported_functions") - @patch("sentry.integrations.aws_lambda.integration.gen_aws_client") - @patch.object(aws_lambda_integration, "render_react_view", return_value=HttpResponse()) - def test_lambda_setup_layer_error( - self, mock_react_view, mock_gen_aws_client, mock_get_supported_functions - ): - class MockException(Exception): - pass - - bad_layer = "arn:aws:lambda:us-east-2:546545:layer:another-layer:5" - mock_client = mock_gen_aws_client.return_value - mock_client.update_function_configuration = MagicMock( - side_effect=Exception(f"Layer version {bad_layer} does not exist") - ) - mock_client.describe_account = MagicMock(return_value={"Account": {"Name": "my_name"}}) - mock_client.exceptions = MagicMock() - mock_client.exceptions.ResourceConflictException = MockException - - mock_get_supported_functions.return_value = [ - { - "FunctionName": "lambdaA", - "Runtime": "nodejs12.x", - "FunctionArn": "arn:aws:lambda:us-east-2:599817902985:function:lambdaA", - }, - { - "FunctionName": "lambdaB", - "Runtime": "nodejs10.x", - "FunctionArn": "arn:aws:lambda:us-east-2:599817902985:function:lambdaB", - }, - ] - - aws_external_id = "12-323" - self.pipeline.state.step_index = 2 - self.pipeline.state.data = { - "region": region, - "account_number": account_number, - "aws_external_id": aws_external_id, - "project_id": self.projectA.id, - } - - resp = self.client.post(self.setup_path, {"lambdaB": "true"}) - - assert resp.status_code == 200 - assert not Integration.objects.filter(provider=self.provider.key).exists() - - failures = [{"name": "lambdaB", "error": "Invalid existing layer another-layer"}] - - mock_react_view.assert_called_with( - ANY, "awsLambdaFailureDetails", {"lambdaFunctionFailures": failures, "successCount": 0} - ) - - @patch("sentry.integrations.aws_lambda.integration.get_supported_functions") - @patch("sentry.integrations.aws_lambda.integration.gen_aws_client") - @patch.object(aws_lambda_integration, "render_react_view", return_value=HttpResponse()) - def test_lambda_setup_layer_missing_role_error( - self, mock_react_view, mock_gen_aws_client, mock_get_supported_functions - ): - class MockException(Exception): - pass - - missing_role_err = ( - "An error occurred (InvalidParameterValueException) when " - "calling the UpdateFunctionConfiguration operation: " - "The role defined for the function cannot be " - "assumed by Lambda." - ) - mock_client = mock_gen_aws_client.return_value - mock_client.update_function_configuration = MagicMock( - side_effect=Exception(missing_role_err) - ) - mock_client.describe_account = MagicMock(return_value={"Account": {"Name": "my_name"}}) - mock_client.exceptions = MagicMock() - mock_client.exceptions.ResourceConflictException = MockException - - mock_get_supported_functions.return_value = [ - { - "FunctionName": "lambdaB", - "Runtime": "nodejs10.x", - "FunctionArn": "arn:aws:lambda:us-east-2:599817902985:function:lambdaB", - }, - ] - - aws_external_id = "12-323" - self.pipeline.state.step_index = 2 - self.pipeline.state.data = { - "region": region, - "account_number": account_number, - "aws_external_id": aws_external_id, - "project_id": self.projectA.id, - } - - resp = self.client.post(self.setup_path, {"lambdaB": "true"}) - - assert resp.status_code == 200 - assert not Integration.objects.filter(provider=self.provider.key).exists() - - failures = [ - {"name": "lambdaB", "error": "Invalid role associated with the lambda function"} - ] - - mock_react_view.assert_called_with( - ANY, "awsLambdaFailureDetails", {"lambdaFunctionFailures": failures, "successCount": 0} - ) - - @patch("sentry.integrations.aws_lambda.integration.get_supported_functions") - @patch("sentry.integrations.aws_lambda.integration.gen_aws_client") - @patch.object(aws_lambda_integration, "render_react_view", return_value=HttpResponse()) - def test_lambda_setup_layer_too_many_requests_exception( - self, mock_react_view, mock_gen_aws_client, mock_get_supported_functions - ): - class MockException(Exception): - pass - - too_many_requests_err = ( - "An error occurred (TooManyRequestsException) when calling the " - "UpdateFunctionConfiguration operation (reached max retries: 4): " - "Rate exceeded" - ) - mock_client = mock_gen_aws_client.return_value - mock_client.update_function_configuration = MagicMock( - side_effect=Exception(too_many_requests_err) - ) - mock_client.describe_account = MagicMock(return_value={"Account": {"Name": "my_name"}}) - mock_client.exceptions = MagicMock() - mock_client.exceptions.ResourceConflictException = MockException - - mock_get_supported_functions.return_value = [ - { - "FunctionName": "lambdaB", - "Runtime": "nodejs10.x", - "FunctionArn": "arn:aws:lambda:us-east-2:599817902985:function:lambdaB", - }, - ] - - aws_external_id = "12-323" - self.pipeline.state.step_index = 2 - self.pipeline.state.data = { - "region": region, - "account_number": account_number, - "aws_external_id": aws_external_id, - "project_id": self.projectA.id, - } - - resp = self.client.post(self.setup_path, {"lambdaB": "true"}) - - assert resp.status_code == 200 - assert not Integration.objects.filter(provider=self.provider.key).exists() - - failures = [ - { - "name": "lambdaB", - "error": "Something went wrong! Please enable function manually after installation", - } - ] - - mock_react_view.assert_called_with( - ANY, "awsLambdaFailureDetails", {"lambdaFunctionFailures": failures, "successCount": 0} - ) - - @patch("sentry.integrations.aws_lambda.integration.get_supported_functions") - @patch("sentry.integrations.aws_lambda.integration.gen_aws_client") - @patch.object(aws_lambda_integration, "render_react_view", return_value=HttpResponse()) - def test_lambda_setup_layer_env_vars_limit_exceeded_exception( - self, mock_react_view, mock_gen_aws_client, mock_get_supported_functions - ): - class MockException(Exception): - pass - - env_vars_size_limit_err = ( - "An error occurred (InvalidParameterValueException) when calling the " - "UpdateFunctionConfiguration operation: Lambda was unable to configure " - "your environment variables because the environment variables you have " - "provided exceeded the 4KB limit. String measured: {'MESSAGE':'This is production " - "environment','TARGET_ENV' :'pre-production','IS_SERVERLESS':'true','STAGE':'pre-prod'" - ) - mock_client = mock_gen_aws_client.return_value - mock_client.update_function_configuration = MagicMock( - side_effect=Exception(env_vars_size_limit_err) - ) - mock_client.describe_account = MagicMock(return_value={"Account": {"Name": "my_name"}}) - mock_client.exceptions = MagicMock() - mock_client.exceptions.ResourceConflictException = MockException - - mock_get_supported_functions.return_value = [ - { - "FunctionName": "lambdaB", - "Runtime": "nodejs10.x", - "FunctionArn": "arn:aws:lambda:us-east-2:599817902985:function:lambdaB", - }, - ] - - aws_external_id = "12-323" - self.pipeline.state.step_index = 2 - self.pipeline.state.data = { - "region": region, - "account_number": account_number, - "aws_external_id": aws_external_id, - "project_id": self.projectA.id, - } - - resp = self.client.post(self.setup_path, {"lambdaB": "true"}) - - assert resp.status_code == 200 - assert not Integration.objects.filter(provider=self.provider.key).exists() - - failures = [ - { - "name": "lambdaB", - "error": "Environment variables size limit of 4KB was exceeded", - } - ] - - mock_react_view.assert_called_with( - ANY, "awsLambdaFailureDetails", {"lambdaFunctionFailures": failures, "successCount": 0} - ) - - @control_silo_test class AwsLambdaApiPipelineTest(APITestCase): endpoint = "sentry-api-0-organization-pipeline" @@ -802,6 +283,3 @@ class MockException(Exception): integration = Integration.objects.get(provider="aws_lambda") assert integration.external_id == f"{account_number}-{region}" - - integration = Integration.objects.get(provider="aws_lambda") - assert integration.external_id == f"{account_number}-{region}"