artillery
Version:
Cloud-scale load testing. https://www.artillery.io
210 lines (185 loc) • 7.16 kB
text/typescript
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import fs from 'node:fs';
import path from 'node:path';
import { Args, Command, Flags } from '@oclif/core';
import dotenv from 'dotenv';
import { CommonRunFlags } from '../cli/common-flags.ts';
import { ECS_WORKER_ROLE_NAME } from '../platform/aws/constants.ts';
import { parseResourceTags } from '../platform/aws/resource-tags.ts';
import PlatformECS from '../platform/aws-ecs/ecs.ts';
import runCluster from '../platform/aws-ecs/legacy/run-cluster.ts';
import { supportedRegions } from '../platform/aws-ecs/legacy/util.ts';
import { Plugin as CloudPlugin } from '../platform/cloud/cloud.ts';
import * as telemetry from '../telemetry.ts';
import generateId from '../util/generate-id.ts';
class RunCommand extends Command {
// Untyped JS class - properties assigned dynamically
[key: string]: any;
static aliases = ['run:fargate', 'run:ecs', 'run-ecs'];
// Enable multiple args:
static strict = false;
async run() {
const { flags, _argv, args }: any = await this.parse(RunCommand);
flags['platform-opt'] = [`region=${flags.region}`];
flags.platform = 'aws:ecs';
// Validate early, before any AWS resources are touched:
try {
parseResourceTags(flags['aws-tags']);
} catch (err) {
console.error((err as Error).message);
process.exit(1);
}
if (flags.dotenv) {
const dotEnvPath = path.resolve(process.cwd(), flags.dotenv);
try {
fs.statSync(dotEnvPath);
} catch (_err) {
console.log(`WARNING: could not read dotenv file: ${flags.dotenv}`);
}
dotenv.config({ path: dotEnvPath });
}
const testRunId = process.env.ARTILLERY_TEST_RUN_ID || generateId('t');
global.artillery.testRunId = testRunId;
const cloud = new CloudPlugin(null, null, { flags });
global.artillery.cloudEnabled = cloud.enabled;
if (cloud.enabled) {
try {
await cloud.init();
} catch (caughtErr) {
const err = caughtErr as Error;
if (err.name === 'CloudAPIKeyMissing') {
console.error(
'Error: API key is required to record test results to Artillery Cloud'
);
console.error(
'See https://docs.art/get-started-cloud for more information'
);
process.exit(7);
} else if (err.name === 'APIKeyUnauthorized') {
console.error(
'Error: API key is not recognized or is not authorized to record tests'
);
process.exit(7);
} else if (err.name === 'PingFailed') {
console.error(
'Error: unable to reach Artillery Cloud API. This could be due to firewall restrictions on your network'
);
console.log('Please see https://docs.art/cloud/err-ping');
process.exit(7);
} else {
console.error(
'Error: something went wrong connecting to Artillery Cloud'
);
console.error('Check https://status.artillery.io for status updates');
console.error(err);
}
}
}
flags.taskRoleName = flags['task-role-name'] || ECS_WORKER_ROLE_NAME;
const ECS = new PlatformECS(
null,
null,
{},
{
testRunId: 'foo',
region: flags.region,
taskRoleName: flags.taskRoleName
}
);
await ECS.init();
process.env.USE_NOOP_BACKEND_STORE = 'true';
telemetry.capture('run:fargate', {
region: flags.region,
count: flags.count
});
// Delegate the rest to existing implementation:
runCluster(args.script, flags);
}
}
RunCommand.description = `launch a test using AWS ECS/Fargate
Examples:
To launch a test with 10 load generating workers using AWS Fargate in us-east-1:
$ artillery run:fargate --count 10 --region us-east-1 my-test.yml
`;
RunCommand.flags = {
...CommonRunFlags,
count: Flags.integer({
description: 'Number of load generator workers to launch'
}),
cluster: Flags.string({
description: 'Name of the Fargate/ECS cluster to run the test on'
}),
region: Flags.string({
char: 'r',
description: 'The AWS region to run in',
options: supportedRegions,
default: 'us-east-1'
}),
secret: Flags.string({
multiple: true,
description:
'Make secrets available to workers. The secret must exist in SSM parameter store for the given region, under /artilleryio/<SECRET_NAME>'
}),
'launch-type': Flags.string({
description: 'The launch type to use for the test. Defaults to Fargate.',
options: ['ecs:fargate', 'ecs:ec2']
}),
spot: Flags.boolean({
description:
'Use Fargate Spot (https://docs.art/fargate-spot) Ignored when --launch-type is set to ecs:ec2'
}),
'launch-config': Flags.string({
description:
'JSON to customize launch configuration of ECS/Fargate tasks (see https://www.artillery.io/docs/reference/cli/run-fargate#using---launch-config)'
}),
'container-dns-servers': Flags.string({
description:
'Comma-separated list of DNS servers for Artillery container. Maps to dnsServers parameter in ECS container definition'
}),
'task-ephemeral-storage': Flags.string({
description:
'Ephemeral storage in GiB for the worker task. Maps to ephemeralStorage parameter in ECS container definition. Fargate-only.',
type: 'integer'
} as any),
'subnet-ids': Flags.string({
description:
'Comma-separated list of AWS VPC subnet IDs to launch Fargate tasks in'
}),
'security-group-ids': Flags.string({
description:
'Comma-separated list of AWS VPC security group IDs to launch Fargate tasks in'
}),
'task-role-name': Flags.string({
description: 'Custom IAM role name for Fargate containers to assume'
}),
cpu: Flags.string({
description:
'Set task vCPU on Fargate (defaults to 4 vCPU). Value may be set as a number of vCPUs between 1-16 (e.g. 4), or as number of vCPU units (e.g. 4096).'
}),
memory: Flags.string({
description:
'Set task memory on Fargate (defaults to 8 GB). Value may be set as number of GB between 1-120 (e.g. 8), or as MiB (e.g. 8192)'
}),
packages: Flags.string({
description:
'Path to package.json file which lists dependencies for the test script'
}),
'max-duration': Flags.string({
description: 'Maximum duration of the test run'
}),
'aws-tags': Flags.string({
description:
'Comma-separated list of tags in key:value format to apply to AWS resources created for the test run (Fargate tasks and SQS queue), for example: --aws-tags "team:perf,cost-center:1234"'
}),
'no-assign-public-ip': Flags.boolean({
description:
'Turn off the default behavior of assigning public IPs to Fargate worker tasks. When this option is used you must make sure tasks have a route to the internet, i.e. via a NAT gateway attached to a private subnet',
default: false
})
};
RunCommand.args = {
script: Args.string()
};
export default RunCommand;