Skip to content

Commit 14effda

Browse files
andreiborzaandrewshie-sentry
authored andcommitted
ref(aws-lambda-integration): Ensure correct node options for all layers (#83633)
Previously, we only had one node lambda layer called `SentryNodeServerlessSDK`. With the `v8` major of the sentry-javascript SDKs a new layer for `v7` was published under `SentryNodeServerlessSDKv7` while the `v8` layer rolled over into `SentryNodeServerlessSDK`. With `v8` the `@sentry/serverless` package was removed (see: #70137) and a version check was added to the integration to update `NODE_OPTIONS` for the integration accordingly. The version check assumes the `SentryNodeServerlessSDK` name and only checks the ARN version which is not enough to identify a `v7` layer since the introduction of `SentryNodeServerlessSDKv7`. With `v9` we will stop updating the `SentryNodeServerlessSDK` and instead publish `SentryNodeServerlessSDKv9` exclusively, the integration has to ensure the correct `NODE_OPTIONS` are set depending on layer name and ARN version which this PR adds. Closes: #82646
1 parent 6181a54 commit 14effda

File tree

3 files changed

+125
-79
lines changed

3 files changed

+125
-79
lines changed

src/sentry/integrations/aws_lambda/utils.py

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,35 @@ def get_dsn_for_project(organization_id, project_id):
204204
return enabled_dsn.dsn_public
205205

206206

207+
def get_node_options_for_layer(layer_name: str, layer_version: int | None) -> str:
208+
"""
209+
Depending on the SDK major our Lambda Layer represents, a different SDK
210+
package has to be used when setting `NODE_OPTIONS`.
211+
This helper generates the correct options for all the layers we support.
212+
"""
213+
# Lambda layers for v7 of our AWS SDK use the older `@sentry/serverless` SDK
214+
#
215+
# These are specifically
216+
# - `SentryNodeServerlessSDKv7`
217+
# - `SentryNodeServerlessSDK:235` and lower
218+
if layer_name == "SentryNodeServerlessSDKv7" or (
219+
layer_name == "SentryNodeServerlessSDK"
220+
and layer_version is not None
221+
and layer_version <= 235
222+
):
223+
return "-r @sentry/serverless/dist/awslambda-auto"
224+
225+
# Lambda layers for v8 and above of our AWS SDK use
226+
# the newer `@sentry/aws-serverless` SDK
227+
#
228+
# These are specifically
229+
# - `SentryNodeServerlessSDK:236` and above
230+
# - `SentryNodeServerlessSDKv8`
231+
# - and any other layer with a version suffix above, e.g.
232+
# `SentryNodeServerlessSDKv9`
233+
return "-r @sentry/aws-serverless/awslambda-auto"
234+
235+
207236
def enable_single_lambda(lambda_client, function, sentry_project_dsn, retries_left=3):
208237
# find the latest layer for this function
209238
layer_arn = get_latest_layer_for_function(function)
@@ -226,31 +255,20 @@ def enable_single_lambda(lambda_client, function, sentry_project_dsn, retries_le
226255

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

236-
if (
237-
# Lambda layer version 235 was the latest version using `@sentry/serverless` before we switched to `@sentry/aws-serverless`
238-
parsed_version is not None
239-
and parsed_version <= 235
240-
):
241-
env_variables.update(
242-
{
243-
"NODE_OPTIONS": "-r @sentry/serverless/dist/awslambda-auto",
244-
**sentry_env_variables,
245-
}
246-
)
247-
else:
248-
env_variables.update(
249-
{
250-
"NODE_OPTIONS": "-r @sentry/aws-serverless/awslambda-auto",
251-
**sentry_env_variables,
252-
}
253-
)
266+
env_variables.update(
267+
{
268+
"NODE_OPTIONS": get_node_options_for_layer(layer_name, parsed_version),
269+
**sentry_env_variables,
270+
}
271+
)
254272

255273
elif runtime.startswith("python"):
256274
# Check if we are trying to re-enable an already enabled python, and if

tests/sentry/integrations/api/endpoints/test_organization_integration_serverless_functions.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ def test_enable_node_layer(self, mock_gen_aws_client, mock_get_serialized_lambda
253253
],
254254
"Environment": {
255255
"Variables": {
256-
"NODE_OPTIONS": "-r @sentry/serverless/dist/awslambda-auto",
256+
"NODE_OPTIONS": "-r @sentry/aws-serverless/awslambda-auto",
257257
"SENTRY_DSN": self.sentry_dsn,
258258
"SENTRY_TRACES_SAMPLE_RATE": "1.0",
259259
}
@@ -286,7 +286,7 @@ def test_enable_node_layer(self, mock_gen_aws_client, mock_get_serialized_lambda
286286
],
287287
Environment={
288288
"Variables": {
289-
"NODE_OPTIONS": "-r @sentry/serverless/dist/awslambda-auto",
289+
"NODE_OPTIONS": "-r @sentry/aws-serverless/awslambda-auto",
290290
"SENTRY_DSN": self.sentry_dsn,
291291
"SENTRY_TRACES_SAMPLE_RATE": "1.0",
292292
}

tests/sentry/integrations/aws_lambda/test_integration.py

Lines changed: 87 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from sentry.projects.services.project import project_service
1515
from sentry.silo.base import SiloMode
1616
from sentry.testutils.cases import IntegrationTestCase
17+
from sentry.testutils.helpers.options import override_options
1718
from sentry.testutils.silo import assume_test_silo_mode, control_silo_test
1819
from sentry.users.services.user.serial import serialize_rpc_user
1920

@@ -157,67 +158,94 @@ def test_lambda_list(self, mock_react_view, mock_gen_aws_client, mock_get_suppor
157158

158159
@patch("sentry.integrations.aws_lambda.integration.get_supported_functions")
159160
@patch("sentry.integrations.aws_lambda.integration.gen_aws_client")
160-
def test_lambda_setup_layer_success(self, mock_gen_aws_client, mock_get_supported_functions):
161-
mock_client = mock_gen_aws_client.return_value
162-
mock_client.update_function_configuration = MagicMock()
163-
mock_client.describe_account = MagicMock(return_value={"Account": {"Name": "my_name"}})
164-
165-
mock_get_supported_functions.return_value = [
166-
{
167-
"FunctionName": "lambdaA",
168-
"Runtime": "nodejs12.x",
169-
"FunctionArn": "arn:aws:lambda:us-east-2:599817902985:function:lambdaA",
170-
},
171-
{
172-
"FunctionName": "lambdaB",
173-
"Runtime": "nodejs10.x",
174-
"FunctionArn": "arn:aws:lambda:us-east-2:599817902985:function:lambdaB",
175-
},
176-
]
177-
178-
aws_external_id = "12-323"
179-
self.pipeline.state.step_index = 2
180-
self.pipeline.state.data = {
181-
"region": region,
182-
"account_number": account_number,
183-
"aws_external_id": aws_external_id,
184-
"project_id": self.projectA.id,
185-
}
186-
187-
with assume_test_silo_mode(SiloMode.REGION):
188-
sentry_project_dsn = ProjectKey.get_default(project=self.projectA).get_dsn(public=True)
189-
190-
# TODO: pass in lambdaA=false
191-
# having issues with reading json data
192-
# request.POST looks like {"lambdaB": "True"}
193-
# string instead of boolean
194-
resp = self.client.post(self.setup_path, {"lambdaB": "true", "lambdaA": "false"})
195-
196-
assert resp.status_code == 200
197-
198-
mock_client.update_function_configuration.assert_called_once_with(
199-
FunctionName="lambdaB",
200-
Layers=["arn:aws:lambda:us-east-2:1234:layer:my-layer:3"],
201-
Environment={
202-
"Variables": {
203-
"NODE_OPTIONS": "-r @sentry/serverless/dist/awslambda-auto",
204-
"SENTRY_DSN": sentry_project_dsn,
205-
"SENTRY_TRACES_SAMPLE_RATE": "1.0",
161+
def test_node_lambda_setup_layer_success(
162+
self,
163+
mock_gen_aws_client,
164+
mock_get_supported_functions,
165+
):
166+
for layer_name, layer_version, expected_node_options in [
167+
("SentryNodeServerlessSDKv7", "5", "-r @sentry/serverless/dist/awslambda-auto"),
168+
("SentryNodeServerlessSDK", "168", "-r @sentry/serverless/dist/awslambda-auto"),
169+
("SentryNodeServerlessSDK", "235", "-r @sentry/serverless/dist/awslambda-auto"),
170+
("SentryNodeServerlessSDK", "236", "-r @sentry/aws-serverless/awslambda-auto"),
171+
("SentryNodeServerlessSDKv8", "3", "-r @sentry/aws-serverless/awslambda-auto"),
172+
("SentryNodeServerlessSDKv9", "235", "-r @sentry/aws-serverless/awslambda-auto"),
173+
]:
174+
with override_options(
175+
{
176+
"aws-lambda.node.layer-name": layer_name,
177+
"aws-lambda.node.layer-version": layer_version,
178+
}
179+
):
180+
# Ensure we reset everything
181+
self.setUp()
182+
mock_get_supported_functions.reset_mock()
183+
mock_gen_aws_client.reset_mock()
184+
185+
mock_client = mock_gen_aws_client.return_value
186+
mock_client.update_function_configuration = MagicMock()
187+
mock_client.describe_account = MagicMock(
188+
return_value={"Account": {"Name": "my_name"}}
189+
)
190+
191+
mock_get_supported_functions.return_value = [
192+
{
193+
"FunctionName": "lambdaA",
194+
"Runtime": "nodejs12.x",
195+
"FunctionArn": "arn:aws:lambda:us-east-2:599817902985:function:lambdaA",
196+
},
197+
{
198+
"FunctionName": "lambdaB",
199+
"Runtime": "nodejs10.x",
200+
"FunctionArn": "arn:aws:lambda:us-east-2:599817902985:function:lambdaB",
201+
},
202+
]
203+
204+
aws_external_id = "12-323"
205+
self.pipeline.state.step_index = 2
206+
self.pipeline.state.data = {
207+
"region": region,
208+
"account_number": account_number,
209+
"aws_external_id": aws_external_id,
210+
"project_id": self.projectA.id,
206211
}
207-
},
208-
)
209212

210-
integration = Integration.objects.get(provider=self.provider.key)
211-
assert integration.name == "my_name us-east-2"
212-
assert integration.external_id == "599817902985-us-east-2"
213-
assert integration.metadata == {
214-
"region": region,
215-
"account_number": account_number,
216-
"aws_external_id": aws_external_id,
217-
}
218-
assert OrganizationIntegration.objects.filter(
219-
integration=integration, organization_id=self.organization.id
220-
)
213+
with assume_test_silo_mode(SiloMode.REGION):
214+
sentry_project_dsn = ProjectKey.get_default(project=self.projectA).get_dsn(
215+
public=True
216+
)
217+
218+
# TODO: pass in lambdaA=false
219+
# having issues with reading json data
220+
# request.POST looks like {"lambdaB": "True"}
221+
# string instead of boolean
222+
resp = self.client.post(self.setup_path, {"lambdaB": "true", "lambdaA": "false"})
223+
224+
assert resp.status_code == 200
225+
226+
mock_client.update_function_configuration.assert_called_once_with(
227+
FunctionName="lambdaB",
228+
Layers=[f"arn:aws:lambda:us-east-2:1234:layer:{layer_name}:{layer_version}"],
229+
Environment={
230+
"Variables": {
231+
"NODE_OPTIONS": expected_node_options,
232+
"SENTRY_DSN": sentry_project_dsn,
233+
"SENTRY_TRACES_SAMPLE_RATE": "1.0",
234+
}
235+
},
236+
)
237+
238+
integration = Integration.objects.get(provider=self.provider.key)
239+
assert integration.name == "my_name us-east-2"
240+
assert integration.external_id == "599817902985-us-east-2"
241+
assert integration.metadata == {
242+
"region": region,
243+
"account_number": account_number,
244+
"aws_external_id": aws_external_id,
245+
}
246+
assert OrganizationIntegration.objects.filter(
247+
integration=integration, organization_id=self.organization.id
248+
)
221249

222250
@patch("sentry.integrations.aws_lambda.integration.get_supported_functions")
223251
@patch("sentry.integrations.aws_lambda.integration.gen_aws_client")

0 commit comments

Comments
 (0)