Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 36 additions & 18 deletions src/sentry/integrations/aws_lambda/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,35 @@ def get_dsn_for_project(organization_id, project_id):
return enabled_dsn.dsn_public


def get_node_options_for_layer(layer_name: str, layer_version: int | None) -> str:
"""
Depending on the SDK major our Lambda Layer represents, a different SDK
package has to be used when setting `NODE_OPTIONS`.
This helper generates the correct options for all the layers we support.
"""
# Lambda layers for v7 of our AWS SDK use the older `@sentry/serverless` SDK
#
# These are specifically
# - `SentryNodeServerlessSDKv7`
# - `SentryNodeServerlessSDK:235` and lower
if layer_name == "SentryNodeServerlessSDKv7" or (
layer_name == "SentryNodeServerlessSDK"
and layer_version is not None
and layer_version <= 235
):
return "-r @sentry/serverless/dist/awslambda-auto"

# Lambda layers for v8 and above of our AWS SDK use
# the newer `@sentry/aws-serverless` SDK
#
# These are specifically
# - `SentryNodeServerlessSDK:236` and above
# - `SentryNodeServerlessSDKv8`
# - and any other layer with a version suffix above, e.g.
# `SentryNodeServerlessSDKv9`
return "-r @sentry/aws-serverless/awslambda-auto"


def enable_single_lambda(lambda_client, function, sentry_project_dsn, retries_left=3):
# find the latest layer for this function
layer_arn = get_latest_layer_for_function(function)
Expand All @@ -226,31 +255,20 @@ def enable_single_lambda(lambda_client, function, sentry_project_dsn, retries_le

if runtime.startswith("nodejs"):
# note the env variables would be different for non-Node runtimes
layer_name = get_option_value(function, OPTION_LAYER_NAME)
version = get_option_value(function, OPTION_VERSION)
try:
parsed_version = int(version)
except Exception:
sentry_sdk.capture_message("Invariant: Unable to parse AWS lambda version")
parsed_version = None

if (
# Lambda layer version 235 was the latest version using `@sentry/serverless` before we switched to `@sentry/aws-serverless`
parsed_version is not None
and parsed_version <= 235
):
env_variables.update(
{
"NODE_OPTIONS": "-r @sentry/serverless/dist/awslambda-auto",
**sentry_env_variables,
}
)
else:
env_variables.update(
{
"NODE_OPTIONS": "-r @sentry/aws-serverless/awslambda-auto",
**sentry_env_variables,
}
)
env_variables.update(
{
"NODE_OPTIONS": get_node_options_for_layer(layer_name, parsed_version),
**sentry_env_variables,
}
)

elif runtime.startswith("python"):
# Check if we are trying to re-enable an already enabled python, and if
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ def test_enable_node_layer(self, mock_gen_aws_client, mock_get_serialized_lambda
],
"Environment": {
"Variables": {
"NODE_OPTIONS": "-r @sentry/serverless/dist/awslambda-auto",
"NODE_OPTIONS": "-r @sentry/aws-serverless/awslambda-auto",
"SENTRY_DSN": self.sentry_dsn,
"SENTRY_TRACES_SAMPLE_RATE": "1.0",
}
Expand Down Expand Up @@ -286,7 +286,7 @@ def test_enable_node_layer(self, mock_gen_aws_client, mock_get_serialized_lambda
],
Environment={
"Variables": {
"NODE_OPTIONS": "-r @sentry/serverless/dist/awslambda-auto",
"NODE_OPTIONS": "-r @sentry/aws-serverless/awslambda-auto",
"SENTRY_DSN": self.sentry_dsn,
"SENTRY_TRACES_SAMPLE_RATE": "1.0",
}
Expand Down
146 changes: 87 additions & 59 deletions tests/sentry/integrations/aws_lambda/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from sentry.projects.services.project import project_service
from sentry.silo.base import SiloMode
from sentry.testutils.cases import IntegrationTestCase
from sentry.testutils.helpers.options import override_options
from sentry.testutils.silo import assume_test_silo_mode, control_silo_test
from sentry.users.services.user.serial import serialize_rpc_user

Expand Down Expand Up @@ -157,67 +158,94 @@ def test_lambda_list(self, mock_react_view, mock_gen_aws_client, mock_get_suppor

@patch("sentry.integrations.aws_lambda.integration.get_supported_functions")
@patch("sentry.integrations.aws_lambda.integration.gen_aws_client")
def test_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",
"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.REGION):
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=["arn:aws:lambda:us-east-2:1234:layer:my-layer:3"],
Environment={
"Variables": {
"NODE_OPTIONS": "-r @sentry/serverless/dist/awslambda-auto",
"SENTRY_DSN": sentry_project_dsn,
"SENTRY_TRACES_SAMPLE_RATE": "1.0",
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,
}
},
)

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
)
with assume_test_silo_mode(SiloMode.REGION):
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")
Expand Down
Loading