diff --git a/packages/artillery/lib/cmds/run-lambda.js b/packages/artillery/lib/cmds/run-lambda.js index be2ff6f008..fe45bb9322 100644 --- a/packages/artillery/lib/cmds/run-lambda.js +++ b/packages/artillery/lib/cmds/run-lambda.js @@ -34,10 +34,6 @@ class RunLambdaCommand extends Command { flags['platform-opt'].push(`subnet-ids=${flags['subnet-ids']}`); } - if (flags.container) { - flags['platform-opt'].push('container=true'); - } - flags.platform = 'aws:lambda'; RunCommand.runCommandImplementation(flags, argv, args); @@ -65,8 +61,11 @@ RunLambdaCommand.flags = { default: '1' }), container: Flags.boolean({ - description: 'Use a container image for Lambda (experimental)', - default: false + description: 'Use a container image for Lambda', + deprecated: { + message: + 'The --container flag has been deprecated. Container images are now the default mode for Lambda functions.' + } }), architecture: Flags.string({ description: 'Architecture of the Lambda function', diff --git a/packages/artillery/lib/cmds/run.js b/packages/artillery/lib/cmds/run.js index edfa051d52..843cc16d91 100644 --- a/packages/artillery/lib/cmds/run.js +++ b/packages/artillery/lib/cmds/run.js @@ -429,12 +429,7 @@ RunCommand.runCommandImplementation = async function (flags, argv, args) { } }; -function replaceProcessorIfTypescript( - script, - scriptPath, - platform, - isContainerLambda -) { +function replaceProcessorIfTypescript(script, scriptPath) { const relativeProcessorPath = script.config.processor; const userExternalPackages = script.config.bundling?.external || []; @@ -447,10 +442,6 @@ function replaceProcessorIfTypescript( return script; } - if (platform == 'aws:lambda' && !isContainerLambda) { - throw new Error('Typescript processor is not supported on AWS Lambda'); - } - const actualProcessorPath = path.resolve( path.dirname(scriptPath), relativeProcessorPath @@ -538,12 +529,7 @@ async function prepareTestExecutionPlan(inputFiles, flags, args) { script6.config.statsInterval = script6.config.statsInterval || 30; const script7 = addDefaultPlugins(script5); - const script8 = replaceProcessorIfTypescript( - script7, - scriptPath, - flags.platform, - flags.container - ); + const script8 = replaceProcessorIfTypescript(script7, scriptPath); return script8; } @@ -614,10 +600,6 @@ async function sendTelemetry(script, flags, extraProps) { properties.platform = flags.platform; - if (flags.container) { - properties.isContainerLambda = true; - } - properties.count = flags.count; if (properties.targetHash) { diff --git a/packages/artillery/lib/platform/aws-lambda/dependencies.js b/packages/artillery/lib/platform/aws-lambda/dependencies.js index 0de1a0a174..a48cd7aa6c 100644 --- a/packages/artillery/lib/platform/aws-lambda/dependencies.js +++ b/packages/artillery/lib/platform/aws-lambda/dependencies.js @@ -1,15 +1,9 @@ const fs = require('fs-extra'); -const path = require('path'); -const temp = require('temp'); -const spawn = require('cross-spawn'); -const archiver = require('archiver'); const AWS = require('aws-sdk'); const debug = require('debug')('platform:aws-lambda'); const Table = require('cli-table3'); -const { randomUUID } = require('crypto'); const { promisify } = require('node:util'); -const { createBOM: createBOMForZip } = require('../../create-bom/create-bom'); -const { createBOM: createBOMForContainer } = require('../aws-ecs/legacy/bom'); +const { createBOM } = require('../aws-ecs/legacy/bom'); const _createLambdaBom = async ( absoluteScriptPath, @@ -29,203 +23,11 @@ const _createLambdaBom = async ( createBomOpts.flags = flags; } - const createBOM = flags.container ? createBOMForContainer : createBOMForZip; - const bom = await promisify(createBOM)(entryPoint, extraFiles, createBomOpts); return bom; }; -async function createZip(src, out) { - const archive = archiver('zip', { zlib: { level: 9 } }); - const stream = fs.createWriteStream(out); - - return new Promise((resolve, reject) => { - archive - .directory(src, false) - .on('error', (err) => reject(err)) - .pipe(stream); - - stream.on('close', () => resolve()); - archive.finalize(); - }); -} - -async function _uploadLambdaZip(bucketName, zipfile) { - const key = `lambda/${randomUUID()}.zip`; - - // TODO: Set lifecycle policy on the bucket/key prefix to delete after 24 hours - - const s3 = new AWS.S3(); - const s3res = await s3 - .putObject({ - Body: fs.createReadStream(zipfile), - Bucket: bucketName, - Key: key - }) - .promise(); - - return key; -} - -const createAndUploadLambdaZip = async ( - bucketName, - absoluteScriptPath, - absoluteConfigPath, - flags -) => { - const dirname = temp.mkdirSync(); // TODO: May want a way to override this by the user - const zipfile = temp.path({ suffix: '.zip' }); - debug({ dirname, zipfile }); - - artillery.log('- Bundling test data'); - const bom = await _createLambdaBom( - absoluteScriptPath, - absoluteConfigPath, - flags - ); - - for (const f of bom.files) { - artillery.log(' -', f.noPrefix); - } - - if (flags.dotenv) { - fs.copyFileSync( - path.resolve(process.cwd(), flags.dotenv), - path.join(dirname, path.basename(flags.dotenv)) - ); - } - - // Copy handler: - fs.copyFileSync( - path.resolve(__dirname, 'lambda-handler', 'a9-handler-index.js'), - path.join(dirname, 'a9-handler-index.js') - ); - fs.copyFileSync( - path.resolve(__dirname, 'lambda-handler', 'a9-handler-helpers.js'), - path.join(dirname, 'a9-handler-helpers.js') - ); - fs.copyFileSync( - path.resolve(__dirname, 'lambda-handler', 'a9-handler-dependencies.js'), - path.join(dirname, 'a9-handler-dependencies.js') - ); - fs.copyFileSync( - path.resolve(__dirname, 'lambda-handler', 'package.json'), - path.join(dirname, 'package.json') - ); - - // FIXME: This may overwrite lambda-handler's index.js or package.json - // Copy files that make up the test: - for (const o of bom.files) { - fs.ensureFileSync(path.join(dirname, o.noPrefix)); - fs.copyFileSync(o.orig, path.join(dirname, o.noPrefix)); - } - - artillery.log('- Installing dependencies'); - const { stdout, stderr, status, error } = spawn.sync( - 'npm', - ['install', '--omit', 'dev'], - { - cwd: dirname - } - ); - - if (error) { - artillery.log(stdout?.toString(), stderr?.toString(), status, error); - } else { - // artillery.log(' npm log is in:', temp.path({suffix: '.log'})); - } - - // Install extra plugins & engines - if (bom.modules.length > 0) { - artillery.log( - `- Installing extra engines & plugins: ${bom.modules.join(', ')}` - ); - const { stdout, stderr, status, error } = spawn.sync( - 'npm', - ['install'].concat(bom.modules), - { cwd: dirname } - ); - if (error) { - artillery.log(stdout?.toString(), stderr?.toString(), status, error); - } - } - - // Copy this version of Artillery into the Lambda package - const a9basepath = path.resolve(__dirname, '..', '..', '..'); - // TODO: read this from .files in package.json instead: - for (const dir of ['bin', 'lib']) { - const destdir = path.join(dirname, 'node_modules', 'artillery', dir); - const srcdir = path.join(a9basepath, dir); - fs.ensureDirSync(destdir); - fs.copySync(srcdir, destdir); - } - for (const fn of ['console-reporter.js', 'util.js']) { - const destfn = path.join(dirname, 'node_modules', 'artillery', fn); - const srcfn = path.join(a9basepath, fn); - fs.copyFileSync(srcfn, destfn); - } - - fs.copyFileSync( - path.resolve(a9basepath, 'package.json'), - path.join(dirname, 'node_modules', 'artillery', 'package.json') - ); - - const a9cwd = path.join(dirname, 'node_modules', 'artillery'); - debug({ a9basepath, a9cwd }); - - const { - stdout: stdout2, - stderr: stderr2, - status: status2, - error: error2 - } = spawn.sync('npm', ['install', '--omit', 'dev'], { cwd: a9cwd }); - if (error2) { - artillery.log(stdout2?.toString(), stderr2?.toString(), status2, error2); - } else { - // artillery.log(' npm log is in:', temp.path({suffix: '.log'})); - } - - const { - stdout: stdout3, - stderr: stderr3, - status: status3, - error: error3 - } = spawn.sync( - 'npm', - [ - 'uninstall', - 'try-require', - 'esbuild-wasm', - 'artillery-plugin-publish-metrics' - ], - { - cwd: a9cwd - } - ); - if (error3) { - artillery.log(stdout3?.toString(), stderr3?.toString(), status3, error3); - } else { - // artillery.log(' npm log is in:', temp.path({suffix: '.log'})); - } - - fs.removeSync(path.join(dirname, 'node_modules', 'aws-sdk')); - fs.removeSync(path.join(a9cwd, 'node_modules', 'tap')); - fs.removeSync(path.join(a9cwd, 'node_modules', 'prettier')); - - artillery.log('- Creating zip package'); - await createZip(dirname, zipfile); - - artillery.log('Preparing AWS environment...'); - const s3Path = await _uploadLambdaZip(bucketName, zipfile); - debug({ s3Path }); - - return { - s3Path, - bom - }; -}; - async function _uploadFileToS3(item, testRunId, bucketName) { const s3 = new AWS.S3(); const prefix = `tests/${testRunId}`; @@ -333,6 +135,5 @@ const createAndUploadTestDependencies = async ( }; module.exports = { - createAndUploadLambdaZip, createAndUploadTestDependencies }; diff --git a/packages/artillery/lib/platform/aws-lambda/index.js b/packages/artillery/lib/platform/aws-lambda/index.js index 02a7319c43..6d3e4efe8e 100644 --- a/packages/artillery/lib/platform/aws-lambda/index.js +++ b/packages/artillery/lib/platform/aws-lambda/index.js @@ -28,10 +28,7 @@ const ensureS3BucketExists = require('../aws/aws-ensure-s3-bucket-exists'); const getAccountId = require('../aws/aws-get-account-id'); const createSQSQueue = require('../aws/aws-create-sqs-queue'); -const { - createAndUploadLambdaZip, - createAndUploadTestDependencies -} = require('./dependencies'); +const { createAndUploadTestDependencies } = require('./dependencies'); const pkgVersion = require('../../../package.json').version; // https://stackoverflow.com/a/66523153 @@ -76,7 +73,6 @@ class PlatformLambda { this.currentVersion = process.env.LAMBDA_IMAGE_VERSION || pkgVersion; this.ecrImageUrl = process.env.WORKER_IMAGE_URL; - this.isContainerLambda = platformConfig.container === 'true'; this.architecture = platformConfig.architecture || 'arm64'; this.region = platformConfig.region || 'us-east-1'; @@ -137,29 +133,13 @@ class PlatformLambda { ); this.bucketName = bucketName; - let bom, s3Path; - if (this.isContainerLambda) { - const result = await createAndUploadTestDependencies( - this.bucketName, - this.testRunId, - this.opts.absoluteScriptPath, - this.opts.absoluteConfigPath, - this.platformOpts.cliArgs - ); - bom = result.bom; - s3Path = result.s3Path; - } else { - //NOTE: for now this is the default behavior to preserve backwards compatibility - const result = await createAndUploadLambdaZip( - this.bucketName, - this.opts.absoluteScriptPath, - this.opts.absoluteConfigPath, - this.platformOpts.cliArgs - ); - bom = result.bom; - s3Path = result.s3Path; - this.lambdaZipPath = s3Path; - } + const { bom, s3Path } = await createAndUploadTestDependencies( + this.bucketName, + this.testRunId, + this.opts.absoluteScriptPath, + this.opts.absoluteConfigPath, + this.platformOpts.cliArgs + ); this.artilleryArgs.push('run'); @@ -225,27 +205,20 @@ class PlatformLambda { artillery.log(` - Lambda role ARN: ${this.lambdaRoleArn}`); } - let shouldCreateLambda; - if (this.isContainerLambda) { - this.functionName = `artilleryio-v${this.currentVersion.replace( - /\./g, - '-' - )}-${this.architecture}`; - shouldCreateLambda = await this.checkIfNewLambdaIsNeeded({ - memorySize: this.memorySize, - functionName: this.functionName - }); - } else { - this.functionName = `artilleryio-${this.testRunId}`; - shouldCreateLambda = true; - } + this.functionName = `artilleryio-v${this.currentVersion.replace( + /\./g, + '-' + )}-${this.architecture}`; + const shouldCreateLambda = await this.checkIfNewLambdaIsNeeded({ + memorySize: this.memorySize, + functionName: this.functionName + }); if (shouldCreateLambda) { try { await this.createLambda({ bucketName: this.bucketName, - functionName: this.functionName, - zipPath: this.lambdaZipPath + functionName: this.functionName }); } catch (err) { throw new Error(`Failed to create Lambda Function: \n${err}`); @@ -442,10 +415,6 @@ class PlatformLambda { WAIT_FOR_GREEN: true }; - if (this.isContainerLambda) { - event.IS_CONTAINER_LAMBDA = true; - } - debug('Lambda event payload:'); debug({ event }); @@ -515,26 +484,13 @@ class PlatformLambda { }); try { - if (!this.isContainerLambda) { - await s3 - .deleteObject({ - Bucket: this.bucketName, - Key: this.lambdaZipPath - }) - .promise(); - } - await sqs .deleteQueue({ QueueUrl: this.sqsQueueUrl }) .promise(); - if ( - (typeof process.env.RETAIN_LAMBDA === 'undefined' && - !this.isContainerLambda) || - process.env.RETAIN_LAMBDA === 'false' - ) { + if (process.env.RETAIN_LAMBDA === 'false') { await lambda .deleteFunction({ FunctionName: this.functionName @@ -669,53 +625,33 @@ class PlatformLambda { region: this.region }); - let lambdaConfig; - - if (this.isContainerLambda) { - lambdaConfig = { - PackageType: 'Image', - Code: { - ImageUri: - this.ecrImageUrl || - `248481025674.dkr.ecr.${this.region}.amazonaws.com/artillery-worker:${this.currentVersion}-${this.architecture}` - }, - ImageConfig: { - Command: ['a9-handler-index.handler'], - EntryPoint: ['/usr/bin/npx', 'aws-lambda-ric'] - }, - FunctionName: functionName, - Description: 'Artillery.io test', - MemorySize: this.memorySize, - Timeout: 900, - Role: this.lambdaRoleArn, - //TODO: architecture influences the entrypoint. We should review which architecture to use in the end (may impact Playwright viability) - Architectures: [this.architecture], - Environment: { - Variables: { - S3_BUCKET_PATH: this.bucketName, - NPM_CONFIG_CACHE: '/tmp/.npm', //TODO: move this to Dockerfile - AWS_LAMBDA_LOG_FORMAT: 'JSON', //TODO: review this. we need to find a ways for logs to look better in Cloudwatch - ARTILLERY_WORKER_PLATFORM: 'aws:lambda' - } + const lambdaConfig = { + PackageType: 'Image', + Code: { + ImageUri: + this.ecrImageUrl || + `248481025674.dkr.ecr.${this.region}.amazonaws.com/artillery-worker:${this.currentVersion}-${this.architecture}` + }, + ImageConfig: { + Command: ['a9-handler-index.handler'], + EntryPoint: ['/usr/bin/npx', 'aws-lambda-ric'] + }, + FunctionName: functionName, + Description: 'Artillery.io test', + MemorySize: this.memorySize, + Timeout: 900, + Role: this.lambdaRoleArn, + //TODO: architecture influences the entrypoint. We should review which architecture to use in the end (may impact Playwright viability) + Architectures: [this.architecture], + Environment: { + Variables: { + S3_BUCKET_PATH: this.bucketName, + NPM_CONFIG_CACHE: '/tmp/.npm', //TODO: move this to Dockerfile + AWS_LAMBDA_LOG_FORMAT: 'JSON', //TODO: review this. we need to find a ways for logs to look better in Cloudwatch + ARTILLERY_WORKER_PLATFORM: 'aws:lambda' } - }; - } else { - lambdaConfig = { - Code: { - S3Bucket: bucketName, - S3Key: zipPath - }, - FunctionName: functionName, - Description: 'Artillery.io test', - Handler: 'a9-handler-index.handler', - MemorySize: this.memorySize, - PackageType: 'Zip', - Runtime: 'nodejs16.x', - Architectures: [this.architecture], - Timeout: 900, - Role: this.lambdaRoleArn - }; - } + } + }; if (this.useVPC) { lambdaConfig.VpcConfig = { diff --git a/packages/artillery/lib/platform/aws-lambda/lambda-handler/a9-handler-index.js b/packages/artillery/lib/platform/aws-lambda/lambda-handler/a9-handler-index.js index 188dc6352c..a1231977d4 100644 --- a/packages/artillery/lib/platform/aws-lambda/lambda-handler/a9-handler-index.js +++ b/packages/artillery/lib/platform/aws-lambda/lambda-handler/a9-handler-index.js @@ -50,8 +50,7 @@ async function handler(event, context) { ARTILLERY_ARGS, BUCKET, ENV, - WAIT_FOR_GREEN, - IS_CONTAINER_LAMBDA + WAIT_FOR_GREEN } = event; console.log('TEST_RUN_ID: ', TEST_RUN_ID); @@ -67,36 +66,34 @@ async function handler(event, context) { const TEST_DATA_LOCATION = `/tmp/test_data/${TEST_RUN_ID}`; - if (IS_CONTAINER_LAMBDA) { - try { - await syncTestData(BUCKET, TEST_RUN_ID); - } catch (err) { - await mq.send({ - event: 'workerError', - reason: 'TestDataSyncFailure', - logs: { - err: { - message: err.message, - stack: err.stack - } + try { + await syncTestData(BUCKET, TEST_RUN_ID); + } catch (err) { + await mq.send({ + event: 'workerError', + reason: 'TestDataSyncFailure', + logs: { + err: { + message: err.message, + stack: err.stack } - }); - } + } + }); + } - try { - await installNpmDependencies(TEST_DATA_LOCATION); - } catch (err) { - await mq.send({ - event: 'workerError', - reason: 'InstallDependenciesFailure', - logs: { - err: { - message: err.message, - stack: err.stack - } + try { + await installNpmDependencies(TEST_DATA_LOCATION); + } catch (err) { + await mq.send({ + event: 'workerError', + reason: 'InstallDependenciesFailure', + logs: { + err: { + message: err.message, + stack: err.stack } - }); - } + } + }); } const interval = setInterval(async () => { @@ -146,7 +143,6 @@ async function handler(event, context) { TEST_RUN_ID, WORKER_ID, ARTILLERY_ARGS, - IS_CONTAINER_LAMBDA, TEST_DATA_LOCATION, ENV }); @@ -186,7 +182,6 @@ async function execArtillery(options) { ENV, NODE_BINARY_PATH, ARTILLERY_BINARY_PATH, - IS_CONTAINER_LAMBDA, TEST_DATA_LOCATION } = options; @@ -208,20 +203,16 @@ async function execArtillery(options) { ENV ); - let ARTILLERY_PATH = - ARTILLERY_BINARY_PATH || './node_modules/artillery/bin/run'; - - if (IS_CONTAINER_LAMBDA) { - const TEST_DATA_NODE_MODULES = `${TEST_DATA_LOCATION}/node_modules`; - const ARTILLERY_NODE_MODULES = '/artillery/node_modules'; + const TEST_DATA_NODE_MODULES = `${TEST_DATA_LOCATION}/node_modules`; + const ARTILLERY_NODE_MODULES = '/artillery/node_modules'; + const ARTILLERY_PATH = + ARTILLERY_BINARY_PATH || `${ARTILLERY_NODE_MODULES}/artillery/bin/run`; - ARTILLERY_PATH = `${ARTILLERY_NODE_MODULES}/artillery/bin/run`; - env.ARTILLERY_PLUGIN_PATH = TEST_DATA_NODE_MODULES; - env.HOME = '/tmp'; - env.NODE_PATH = ['/artillery/node_modules', TEST_DATA_NODE_MODULES].join( - path.delimiter - ); - } + env.ARTILLERY_PLUGIN_PATH = TEST_DATA_NODE_MODULES; + env.HOME = '/tmp'; + env.NODE_PATH = ['/artillery/node_modules', TEST_DATA_NODE_MODULES].join( + path.delimiter + ); return runProcess( NODE_BINARY_PATH || 'node', diff --git a/packages/artillery/test/cloud-e2e/lambda/run-lambda-container.test.js b/packages/artillery/test/cloud-e2e/lambda/lambda-smoke.test.js similarity index 100% rename from packages/artillery/test/cloud-e2e/lambda/run-lambda-container.test.js rename to packages/artillery/test/cloud-e2e/lambda/lambda-smoke.test.js diff --git a/packages/artillery/test/cloud-e2e/lambda/run-lambda.test.js b/packages/artillery/test/cloud-e2e/lambda/run-lambda.test.js deleted file mode 100644 index 53d4eff88a..0000000000 --- a/packages/artillery/test/cloud-e2e/lambda/run-lambda.test.js +++ /dev/null @@ -1,33 +0,0 @@ -const tap = require('tap'); -const { $ } = require('zx'); -const { getTestTags } = require('../../cli/_helpers.js'); - -tap.test('Run a test on AWS Lambda', async (t) => { - const tags = getTestTags(['type:acceptance']); - const configPath = `${__dirname}/fixtures/quick-loop-with-csv/config.yml`; - const scenarioPath = `${__dirname}/fixtures/quick-loop-with-csv/blitz.yml`; - - const output = - await $`artillery run-lambda --count 10 --region eu-west-1 --config ${configPath} --record --tags ${tags} ${scenarioPath}`; - - t.equal(output.exitCode, 0, 'CLI should exit with code 0'); - - t.ok( - output.stdout.indexOf('Summary report') > 0, - 'Should print summary report' - ); - t.ok( - output.stdout.indexOf('http.codes.200') > 0, - 'Should print http.codes.200' - ); - - t.ok( - output.stdout.indexOf('csv_number_') > 0, - 'Should print csv_number_ counters' - ); - - t.ok( - output.stdout.indexOf('csv_name_') > 0, - 'Should print csv_name_ counters' - ); -});