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
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
{
"name": "aws-lambda-sam",
"name": "aws-serverless",
"version": "1.0.0",
"private": true,
"type": "commonjs",
"scripts": {
"test": "playwright test",
"clean": "npx rimraf node_modules pnpm-lock.yaml",
"test:build": "pnpm install && npx rimraf node_modules/@sentry/aws-serverless/nodejs",
"test:pull-sam-image": "./pull-sam-image.sh",
"test:build": "pnpm test:pull-sam-image && pnpm install && npx rimraf node_modules/@sentry/aws-serverless/nodejs",
"test:assert": "pnpm test"
},
"//": "We just need the @sentry/aws-serverless layer zip file, not the NPM package",
Expand All @@ -15,12 +16,10 @@
"@playwright/test": "~1.56.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@sentry/aws-serverless": "link:../../../../packages/aws-serverless/build/aws/dist-serverless/",
"@types/tmp": "^0.2.6",
"aws-cdk-lib": "^2.210.0",
"constructs": "^10.4.2",
"glob": "^11.0.3",
"rimraf": "^5.0.10",
"tmp": "^0.2.5"
"rimraf": "^5.0.10"
},
"volta": {
"extends": "../../package.json"
Expand All @@ -34,12 +33,12 @@
"sentryTest": {
"variants": [
{
"build-command": "NODE_VERSION=20 ./pull-sam-image.sh && pnpm test:build",
"assert-command": "NODE_VERSION=20 pnpm test:assert",
"label": "aws-serverless (Node 20)"
"build-command": "NODE_VERSION=22 pnpm test:build",
"assert-command": "NODE_VERSION=22 pnpm test:assert",
"label": "aws-serverless (Node 22)"
},
{
"build-command": "NODE_VERSION=18 ./pull-sam-image.sh && pnpm test:build",
"build-command": "NODE_VERSION=18 pnpm test:build",
"assert-command": "NODE_VERSION=18 pnpm test:assert",
"label": "aws-serverless (Node 18)"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
#!/bin/bash

# Script to pull the correct Lambda docker image based on the NODE_VERSION environment variable.
# Pull the Lambda Node docker image for SAM local. NODE_VERSION should be the major only (e.g. 20).
# Defaults to 20 to match the repo's Volta Node major (see root package.json "volta.node").

set -e

if [[ -z "$NODE_VERSION" ]]; then
echo "Error: NODE_VERSION not set"
exit 1
fi
NODE_VERSION="${NODE_VERSION:-20}"

echo "Pulling Lambda Node $NODE_VERSION docker image..."
docker pull "public.ecr.aws/lambda/nodejs:${NODE_VERSION}"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# SAM CLI expects this file in the project root; without it, `sam local start-lambda` logs
# OSError / missing file errors when run from the e2e temp directory.
# These values are placeholders — this app only uses `sam local`, not deploy.
version = 0.1

[default.deploy.parameters]
stack_name = "sentry-e2e-aws-serverless-local"
region = "us-east-1"
confirm_changeset = false
capabilities = "CAPABILITY_IAM"
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,21 @@ import * as path from 'node:path';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as dns from 'node:dns/promises';
import { platform } from 'node:process';
import { arch, platform } from 'node:process';
import { globSync } from 'glob';
import { execFileSync } from 'node:child_process';

const LAMBDA_FUNCTIONS_WITH_LAYER_DIR = './src/lambda-functions-layer';
const LAMBDA_FUNCTIONS_WITH_NPM_DIR = './src/lambda-functions-npm';
const LAMBDA_FUNCTION_TIMEOUT = 10;
const LAYER_DIR = './node_modules/@sentry/aws-serverless/';
const DEFAULT_NODE_VERSION = '22';
export const SAM_PORT = 3001;

/** Match SAM / Docker to this machine so Apple Silicon does not mix arm64 images with an x86_64 template default. */
function samLambdaArchitecture(): 'arm64' | 'x86_64' {
return arch === 'arm64' ? 'arm64' : 'x86_64';
}

function resolvePackagesDir(): string {
// When running via the e2e test runner, tests are copied to a temp directory
// so we need the workspace root passed via env var
Expand Down Expand Up @@ -49,6 +53,7 @@ export class LocalLambdaStack extends Stack {
properties: {
ContentUri: path.join(LAYER_DIR, layerZipFile),
CompatibleRuntimes: ['nodejs18.x', 'nodejs20.x', 'nodejs22.x'],
CompatibleArchitectures: [samLambdaArchitecture()],
},
});

Expand Down Expand Up @@ -122,12 +127,17 @@ export class LocalLambdaStack extends Stack {
execFileSync('npm', ['install', '--install-links', '--prefix', lambdaPath], { stdio: 'inherit' });
}

if (!process.env.NODE_VERSION) {
throw new Error('[LocalLambdaStack] NODE_VERSION is not set');
}

new CfnResource(this, functionName, {
type: 'AWS::Serverless::Function',
properties: {
Architectures: [samLambdaArchitecture()],
CodeUri: path.join(functionsDir, lambdaDir),
Handler: 'index.handler',
Runtime: `nodejs${process.env.NODE_VERSION ?? DEFAULT_NODE_VERSION}.x`,
Runtime: `nodejs${process.env.NODE_VERSION}.x`,
Timeout: LAMBDA_FUNCTION_TIMEOUT,
Layers: addLayer ? [{ Ref: this.sentryLayer.logicalId }] : undefined,
Environment: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import { startEventProxyServer } from '@sentry-internal/test-utils';

startEventProxyServer({
port: 3031,
proxyServerName: 'aws-serverless-lambda-sam',
proxyServerName: 'aws-serverless',
});
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
import { test as base, expect } from '@playwright/test';
import { App } from 'aws-cdk-lib';
import * as tmp from 'tmp';
import { LocalLambdaStack, SAM_PORT, getHostIp } from '../src/stack';
import { writeFileSync } from 'node:fs';
import { spawn, execSync } from 'node:child_process';
import { execSync, spawn } from 'node:child_process';
import { LambdaClient } from '@aws-sdk/client-lambda';

const DOCKER_NETWORK_NAME = 'lambda-test-network';
const SAM_TEMPLATE_FILE = 'sam.template.yml';

/** Major Node for SAM `--invoke-image`; default matches root `package.json` `volta.node` and `pull-sam-image.sh`. */
const DEFAULT_NODE_VERSION_MAJOR = '20';
Comment thread
cursor[bot] marked this conversation as resolved.

const SAM_INSTALL_ERROR =
'You need to install sam, e.g. run `brew install aws-sam-cli`. Ensure `sam` is on your PATH when running tests.';

export { expect };

export const test = base.extend<{ testEnvironment: LocalLambdaStack; lambdaClient: LambdaClient }>({
testEnvironment: [
async ({}, use) => {
console.log('[testEnvironment fixture] Setting up AWS Lambda test infrastructure');

const nodeVersionMajor = process.env.NODE_VERSION?.trim() || DEFAULT_NODE_VERSION_MAJOR;
process.env.NODE_VERSION = nodeVersionMajor;

assertSamOnPath();

execSync('docker network prune -f');
createDockerNetwork();

Expand All @@ -26,11 +36,6 @@ export const test = base.extend<{ testEnvironment: LocalLambdaStack; lambdaClien
const template = app.synth().getStackByName('LocalLambdaStack').template;
writeFileSync(SAM_TEMPLATE_FILE, JSON.stringify(template, null, 2));

const debugLog = tmp.fileSync({ prefix: 'sentry_aws_lambda_tests_sam_debug', postfix: '.log' });
if (!process.env.CI) {
console.log(`[test_environment fixture] Writing SAM debug log to: ${debugLog.name}`);
}

const args = [
'local',
'start-lambda',
Expand All @@ -42,16 +47,15 @@ export const test = base.extend<{ testEnvironment: LocalLambdaStack; lambdaClien
'--docker-network',
DOCKER_NETWORK_NAME,
'--skip-pull-image',
'--invoke-image',
`public.ecr.aws/lambda/nodejs:${nodeVersionMajor}`,
];

if (process.env.NODE_VERSION) {
args.push('--invoke-image', `public.ecr.aws/lambda/nodejs:${process.env.NODE_VERSION}`);
}

console.log(`[testEnvironment fixture] Running SAM with args: ${args.join(' ')}`);

const samProcess = spawn('sam', args, {
stdio: process.env.CI ? 'inherit' : ['ignore', debugLog.fd, debugLog.fd],
stdio: process.env.DEBUG ? 'inherit' : 'ignore',
env: envForSamChild(),
});

try {
Expand Down Expand Up @@ -91,6 +95,23 @@ export const test = base.extend<{ testEnvironment: LocalLambdaStack; lambdaClien
},
});

/** Avoid forcing linux/amd64 on Apple Silicon when `DOCKER_DEFAULT_PLATFORM` is set globally. */
function envForSamChild(): NodeJS.ProcessEnv {
const env = { ...process.env };
if (process.arch === 'arm64') {
delete env.DOCKER_DEFAULT_PLATFORM;
}
return env;
}

function assertSamOnPath(): void {
try {
execSync('sam --version', { encoding: 'utf-8', stdio: 'pipe' });
} catch {
throw new Error(SAM_INSTALL_ERROR);
}
}

function createDockerNetwork() {
try {
execSync(`docker network create --driver bridge ${DOCKER_NETWORK_NAME}`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { test, expect } from './lambda-fixtures';

test.describe('Lambda layer', () => {
test('tracing in CJS works', async ({ lambdaClient }) => {
const transactionEventPromise = waitForTransaction('aws-serverless-lambda-sam', transactionEvent => {
const transactionEventPromise = waitForTransaction('aws-serverless', transactionEvent => {
return transactionEvent?.transaction === 'LayerTracingCjs';
});

Expand Down Expand Up @@ -72,7 +72,7 @@ test.describe('Lambda layer', () => {
});

test('tracing in ESM works', async ({ lambdaClient }) => {
const transactionEventPromise = waitForTransaction('aws-serverless-lambda-sam', transactionEvent => {
const transactionEventPromise = waitForTransaction('aws-serverless', transactionEvent => {
return transactionEvent?.transaction === 'LayerTracingEsm';
});

Expand Down Expand Up @@ -140,7 +140,7 @@ test.describe('Lambda layer', () => {
});

test('capturing errors works', async ({ lambdaClient }) => {
const errorEventPromise = waitForError('aws-serverless-lambda-sam', errorEvent => {
const errorEventPromise = waitForError('aws-serverless', errorEvent => {
return errorEvent?.exception?.values?.[0]?.value === 'test';
});

Expand Down Expand Up @@ -168,7 +168,7 @@ test.describe('Lambda layer', () => {
});

test('capturing errors works in ESM', async ({ lambdaClient }) => {
const errorEventPromise = waitForError('aws-serverless-lambda-sam', errorEvent => {
const errorEventPromise = waitForError('aws-serverless', errorEvent => {
return errorEvent?.exception?.values?.[0]?.value === 'test esm';
});

Expand Down Expand Up @@ -196,7 +196,7 @@ test.describe('Lambda layer', () => {
});

test('streaming handlers work', async ({ lambdaClient }) => {
const transactionEventPromise = waitForTransaction('aws-serverless-lambda-sam', transactionEvent => {
const transactionEventPromise = waitForTransaction('aws-serverless', transactionEvent => {
return transactionEvent?.transaction === 'LayerStreaming';
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { test, expect } from './lambda-fixtures';

test.describe('NPM package', () => {
test('tracing in CJS works', async ({ lambdaClient }) => {
const transactionEventPromise = waitForTransaction('aws-serverless-lambda-sam', transactionEvent => {
const transactionEventPromise = waitForTransaction('aws-serverless', transactionEvent => {
return transactionEvent?.transaction === 'NpmTracingCjs';
});

Expand Down Expand Up @@ -72,7 +72,7 @@ test.describe('NPM package', () => {
});

test('tracing in ESM works', async ({ lambdaClient }) => {
const transactionEventPromise = waitForTransaction('aws-serverless-lambda-sam', transactionEvent => {
const transactionEventPromise = waitForTransaction('aws-serverless', transactionEvent => {
return transactionEvent?.transaction === 'NpmTracingEsm';
});

Expand Down
Loading