@depup/artillery 2.0.30-depup.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -0
- package/bin/run +29 -0
- package/bin/run.cmd +3 -0
- package/changes.json +138 -0
- package/console-reporter.js +1 -0
- package/lib/artillery-global.js +33 -0
- package/lib/cli/banner.js +8 -0
- package/lib/cli/common-flags.js +80 -0
- package/lib/cli/hooks/version.js +20 -0
- package/lib/cmds/dino.js +109 -0
- package/lib/cmds/quick.js +122 -0
- package/lib/cmds/report.js +34 -0
- package/lib/cmds/run-aci.js +91 -0
- package/lib/cmds/run-fargate.js +192 -0
- package/lib/cmds/run-lambda.js +96 -0
- package/lib/cmds/run.js +671 -0
- package/lib/console-capture.js +92 -0
- package/lib/console-reporter.js +438 -0
- package/lib/create-bom/built-in-plugins.js +12 -0
- package/lib/create-bom/create-bom.js +301 -0
- package/lib/dispatcher.js +9 -0
- package/lib/dist.js +222 -0
- package/lib/index.js +5 -0
- package/lib/launch-platform.js +439 -0
- package/lib/load-plugins.js +113 -0
- package/lib/platform/aws/aws-cloudwatch.js +106 -0
- package/lib/platform/aws/aws-create-sqs-queue.js +58 -0
- package/lib/platform/aws/aws-ensure-s3-bucket-exists.js +78 -0
- package/lib/platform/aws/aws-get-account-id.js +26 -0
- package/lib/platform/aws/aws-get-bucket-region.js +18 -0
- package/lib/platform/aws/aws-get-credentials.js +28 -0
- package/lib/platform/aws/aws-get-default-region.js +26 -0
- package/lib/platform/aws/aws-whoami.js +15 -0
- package/lib/platform/aws/constants.js +7 -0
- package/lib/platform/aws/iam-cf-templates/aws-iam-fargate-cf-template.yml +219 -0
- package/lib/platform/aws/iam-cf-templates/aws-iam-lambda-cf-template.yml +125 -0
- package/lib/platform/aws/iam-cf-templates/gh-oidc-fargate.yml +241 -0
- package/lib/platform/aws/iam-cf-templates/gh-oidc-lambda.yml +153 -0
- package/lib/platform/aws-ecs/ecs.js +247 -0
- package/lib/platform/aws-ecs/legacy/aws-util.js +134 -0
- package/lib/platform/aws-ecs/legacy/bom.js +528 -0
- package/lib/platform/aws-ecs/legacy/constants.js +27 -0
- package/lib/platform/aws-ecs/legacy/create-s3-client.js +24 -0
- package/lib/platform/aws-ecs/legacy/create-test.js +247 -0
- package/lib/platform/aws-ecs/legacy/errors.js +34 -0
- package/lib/platform/aws-ecs/legacy/find-public-subnets.js +149 -0
- package/lib/platform/aws-ecs/legacy/plugins/artillery-plugin-inspect-script/index.js +27 -0
- package/lib/platform/aws-ecs/legacy/plugins/artillery-plugin-sqs-reporter/azure-aqs.js +80 -0
- package/lib/platform/aws-ecs/legacy/plugins/artillery-plugin-sqs-reporter/index.js +202 -0
- package/lib/platform/aws-ecs/legacy/plugins.js +16 -0
- package/lib/platform/aws-ecs/legacy/run-cluster.js +1994 -0
- package/lib/platform/aws-ecs/legacy/sqs-reporter.js +401 -0
- package/lib/platform/aws-ecs/legacy/tags.js +22 -0
- package/lib/platform/aws-ecs/legacy/test-run-status.js +9 -0
- package/lib/platform/aws-ecs/legacy/time.js +67 -0
- package/lib/platform/aws-ecs/legacy/util.js +97 -0
- package/lib/platform/aws-ecs/worker/Dockerfile +64 -0
- package/lib/platform/aws-ecs/worker/helpers.sh +80 -0
- package/lib/platform/aws-ecs/worker/loadgen-worker +656 -0
- package/lib/platform/aws-lambda/dependencies.js +130 -0
- package/lib/platform/aws-lambda/index.js +734 -0
- package/lib/platform/aws-lambda/lambda-handler/a9-handler-dependencies.js +73 -0
- package/lib/platform/aws-lambda/lambda-handler/a9-handler-helpers.js +43 -0
- package/lib/platform/aws-lambda/lambda-handler/a9-handler-index.js +235 -0
- package/lib/platform/aws-lambda/lambda-handler/package.json +15 -0
- package/lib/platform/aws-lambda/prices.js +29 -0
- package/lib/platform/az/aci.js +694 -0
- package/lib/platform/az/aqs-queue-consumer.js +88 -0
- package/lib/platform/az/regions.js +52 -0
- package/lib/platform/cloud/api.js +72 -0
- package/lib/platform/cloud/cloud.js +448 -0
- package/lib/platform/cloud/http-client.js +19 -0
- package/lib/platform/local/artillery-worker-local.js +154 -0
- package/lib/platform/local/index.js +174 -0
- package/lib/platform/local/worker.js +261 -0
- package/lib/platform/worker-states.js +13 -0
- package/lib/queue-consumer/index.js +56 -0
- package/lib/stash.js +41 -0
- package/lib/telemetry.js +78 -0
- package/lib/util/await-on-ee.js +24 -0
- package/lib/util/generate-id.js +9 -0
- package/lib/util/parse-tag-string.js +21 -0
- package/lib/util/prepare-test-execution-plan.js +216 -0
- package/lib/util/sleep.js +7 -0
- package/lib/util/validate-script.js +132 -0
- package/lib/util.js +294 -0
- package/lib/utils-config.js +31 -0
- package/package.json +323 -0
- package/types.d.ts +317 -0
- package/util.js +1 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
const { ECSClient, DescribeTasksCommand } = require('@aws-sdk/client-ecs');
|
|
2
|
+
const {
|
|
3
|
+
SSMClient,
|
|
4
|
+
GetParameterCommand,
|
|
5
|
+
PutParameterCommand,
|
|
6
|
+
DeleteParameterCommand
|
|
7
|
+
} = require('@aws-sdk/client-ssm');
|
|
8
|
+
const debug = require('debug')('util');
|
|
9
|
+
|
|
10
|
+
module.exports = {
|
|
11
|
+
// ECS:
|
|
12
|
+
ecsDescribeTasks,
|
|
13
|
+
|
|
14
|
+
// AWS SSM:
|
|
15
|
+
ensureParameterExists,
|
|
16
|
+
parameterExists,
|
|
17
|
+
putParameter,
|
|
18
|
+
getParameter,
|
|
19
|
+
deleteParameter
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Wraps ecs.describeTasks to support more than 100 task ARNs in params.tasks
|
|
23
|
+
async function ecsDescribeTasks(params, region) {
|
|
24
|
+
const ecs = new ECSClient({ apiVersion: '2014-11-13', region });
|
|
25
|
+
const taskArnChunks = splitIntoSublists(params.tasks, 100);
|
|
26
|
+
const results = { tasks: [], failures: [] };
|
|
27
|
+
for (let i = 0; i < taskArnChunks.length; i++) {
|
|
28
|
+
const params2 = Object.assign({}, params, { tasks: taskArnChunks[i] });
|
|
29
|
+
const ecsData = await ecs.send(new DescribeTasksCommand(params2));
|
|
30
|
+
results.tasks = results.tasks.concat(ecsData.tasks);
|
|
31
|
+
results.failures = results.failures.concat(ecsData.failures);
|
|
32
|
+
}
|
|
33
|
+
return results;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Slice input list into several lists, where each list has no more than maxGroupSize elements
|
|
37
|
+
function splitIntoSublists(list, maxGroupSize) {
|
|
38
|
+
const result = [];
|
|
39
|
+
const numGroups = Math.ceil(list.length / maxGroupSize);
|
|
40
|
+
for (let i = 0; i < numGroups; i++) {
|
|
41
|
+
result.push(list.slice(i * maxGroupSize, i * maxGroupSize + maxGroupSize));
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ********************
|
|
47
|
+
// AWS SSM helpers
|
|
48
|
+
// In future these will be parameter-store agnostic, and work with Kubernetes
|
|
49
|
+
// ConfigMaps or Azure/GCP native equivalents.
|
|
50
|
+
// ********************
|
|
51
|
+
|
|
52
|
+
// If parameter exists, do nothing; otherwise set the value
|
|
53
|
+
async function ensureParameterExists(ssmPath, defaultValue, type, region) {
|
|
54
|
+
const exists = await parameterExists(ssmPath, region);
|
|
55
|
+
if (exists) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
return putParameter(ssmPath, defaultValue, type, region);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function parameterExists(path, region) {
|
|
62
|
+
const ssm = new SSMClient({ apiVersion: '2014-11-06', region });
|
|
63
|
+
const getParams = {
|
|
64
|
+
Name: path,
|
|
65
|
+
WithDecryption: true
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
await ssm.send(new GetParameterCommand(getParams));
|
|
70
|
+
return true;
|
|
71
|
+
} catch (ssmErr) {
|
|
72
|
+
if (ssmErr.name === 'ParameterNotFound') {
|
|
73
|
+
return false;
|
|
74
|
+
} else {
|
|
75
|
+
throw ssmErr;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function putParameter(path, value, type, region) {
|
|
81
|
+
const ssm = new SSMClient({ apiVersion: '2014-11-06', region });
|
|
82
|
+
|
|
83
|
+
const putParams = {
|
|
84
|
+
Name: path,
|
|
85
|
+
Type: type,
|
|
86
|
+
Value: value,
|
|
87
|
+
Overwrite: true
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
await ssm.send(new PutParameterCommand(putParams));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function getParameter(path, region) {
|
|
94
|
+
const ssm = new SSMClient({ apiVersion: '2014-11-06', region });
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const ssmResponse = await ssm.send(
|
|
98
|
+
new GetParameterCommand({
|
|
99
|
+
Name: path,
|
|
100
|
+
WithDecryption: true
|
|
101
|
+
})
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
debug({ ssmResponse });
|
|
105
|
+
return ssmResponse.Parameter?.Value;
|
|
106
|
+
} catch (ssmErr) {
|
|
107
|
+
if (ssmErr.name === 'ParameterNotFound') {
|
|
108
|
+
return false;
|
|
109
|
+
} else {
|
|
110
|
+
throw ssmErr;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function deleteParameter(path, region) {
|
|
116
|
+
const ssm = new SSMClient({ apiVersion: '2014-11-06', region });
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const ssmResponse = await ssm.send(
|
|
120
|
+
new DeleteParameterCommand({
|
|
121
|
+
Name: path
|
|
122
|
+
})
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
debug({ ssmResponse });
|
|
126
|
+
return ssmResponse;
|
|
127
|
+
} catch (ssmErr) {
|
|
128
|
+
if (ssmErr.name === 'ParameterNotFound') {
|
|
129
|
+
return false;
|
|
130
|
+
} else {
|
|
131
|
+
throw ssmErr;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
const path = require('node:path');
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const A = require('async');
|
|
4
|
+
|
|
5
|
+
const { isBuiltin } = require('node:module');
|
|
6
|
+
const detective = require('detective-es6');
|
|
7
|
+
const depTree = require('dependency-tree');
|
|
8
|
+
|
|
9
|
+
const walkSync = require('walk-sync');
|
|
10
|
+
const debug = require('debug')('bom');
|
|
11
|
+
const _ = require('lodash');
|
|
12
|
+
const BUILTIN_PLUGINS = require('./plugins').getAllPluginNames();
|
|
13
|
+
const BUILTIN_ENGINES = require('./plugins').getOfficialEngines();
|
|
14
|
+
|
|
15
|
+
const Table = require('cli-table3');
|
|
16
|
+
|
|
17
|
+
const { resolveConfigTemplates } = require('../../../../util');
|
|
18
|
+
|
|
19
|
+
const prepareTestExecutionPlan = require('../../../../lib/util/prepare-test-execution-plan');
|
|
20
|
+
const { readScript, parseScript } = require('../../../../util');
|
|
21
|
+
|
|
22
|
+
// NOTE: Code below presumes that all paths are absolute
|
|
23
|
+
|
|
24
|
+
//Tests in Fargate run on ubuntu, which uses posix paths
|
|
25
|
+
//This function converts a path to posix path, in case the original path was not posix (e.g. windows runs)
|
|
26
|
+
function _convertToPosixPath(p) {
|
|
27
|
+
return p.split(path.sep).join(path.posix.sep);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// NOTE: absoluteScriptPath here is actually the absolute path to the config file
|
|
31
|
+
function createBOM(absoluteScriptPath, extraFiles, opts, callback) {
|
|
32
|
+
A.waterfall(
|
|
33
|
+
[
|
|
34
|
+
A.constant(absoluteScriptPath),
|
|
35
|
+
async (scriptPath) => {
|
|
36
|
+
let scriptData;
|
|
37
|
+
if (scriptPath.toLowerCase().endsWith('.ts')) {
|
|
38
|
+
scriptData = await prepareTestExecutionPlan(
|
|
39
|
+
[scriptPath],
|
|
40
|
+
opts.flags,
|
|
41
|
+
[]
|
|
42
|
+
);
|
|
43
|
+
scriptData.config.processor = scriptPath;
|
|
44
|
+
} else {
|
|
45
|
+
const data = await readScript(scriptPath);
|
|
46
|
+
scriptData = await parseScript(data);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return scriptData;
|
|
50
|
+
},
|
|
51
|
+
(scriptData, next) => {
|
|
52
|
+
return next(null, {
|
|
53
|
+
opts: {
|
|
54
|
+
scriptData,
|
|
55
|
+
absoluteScriptPath,
|
|
56
|
+
flags: opts.flags,
|
|
57
|
+
scenarioPath: opts.scenarioPath // Absolute path to the file that holds scenarios
|
|
58
|
+
},
|
|
59
|
+
localFilePaths: [absoluteScriptPath],
|
|
60
|
+
npmModules: []
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
applyScriptChanges,
|
|
64
|
+
getPlugins,
|
|
65
|
+
getCustomEngines,
|
|
66
|
+
getCustomJsDependencies,
|
|
67
|
+
getVariableDataFiles,
|
|
68
|
+
getFileUploadPluginFiles,
|
|
69
|
+
getExtraFiles,
|
|
70
|
+
getDotEnv,
|
|
71
|
+
expandDirectories
|
|
72
|
+
],
|
|
73
|
+
|
|
74
|
+
(err, context) => {
|
|
75
|
+
if (err) {
|
|
76
|
+
return callback(err, null);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
context.localFilePaths = context.localFilePaths.concat(extraFiles);
|
|
80
|
+
|
|
81
|
+
// TODO: Entries in localFilePaths may be directories
|
|
82
|
+
|
|
83
|
+
// How many entries do we have here? If we have only one entry, the string itself
|
|
84
|
+
// will be the common prefix, meaning that when we substring() on it later, we'll
|
|
85
|
+
// get an empty string, ending up with a manifest like:
|
|
86
|
+
// { files:
|
|
87
|
+
// [ { orig: '/Users/h/tmp/artillery/hello.yaml', noPrefix: '' } ],
|
|
88
|
+
// modules: [] }
|
|
89
|
+
//
|
|
90
|
+
let prefix = '';
|
|
91
|
+
if (context.localFilePaths.length === 1) {
|
|
92
|
+
prefix = context.localFilePaths[0].substring(
|
|
93
|
+
0,
|
|
94
|
+
context.localFilePaths[0].length -
|
|
95
|
+
path.basename(context.localFilePaths[0]).length
|
|
96
|
+
);
|
|
97
|
+
// This may still be an empty string if the script path is just 'hello.yml':
|
|
98
|
+
prefix = prefix.length === 0 ? context.localFilePaths[0] : prefix;
|
|
99
|
+
} else {
|
|
100
|
+
prefix = commonPrefix(context.localFilePaths);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
prefix = _convertToPosixPath(prefix);
|
|
104
|
+
debug('prefix', prefix);
|
|
105
|
+
|
|
106
|
+
//
|
|
107
|
+
// include package.json / package-lock.json / yarn.lock
|
|
108
|
+
//
|
|
109
|
+
let packageDescriptionFiles = ['.npmrc'];
|
|
110
|
+
if (opts.packageJsonPath) {
|
|
111
|
+
packageDescriptionFiles.push(opts.packageJsonPath);
|
|
112
|
+
} else {
|
|
113
|
+
packageDescriptionFiles = packageDescriptionFiles.concat([
|
|
114
|
+
'package.json',
|
|
115
|
+
'package-lock.json',
|
|
116
|
+
'yarn.lock'
|
|
117
|
+
]);
|
|
118
|
+
}
|
|
119
|
+
const dependencyFiles = packageDescriptionFiles.map((s) =>
|
|
120
|
+
path.join(prefix, s)
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
debug(dependencyFiles);
|
|
124
|
+
|
|
125
|
+
dependencyFiles.forEach((p) => {
|
|
126
|
+
try {
|
|
127
|
+
if (fs.statSync(p)) {
|
|
128
|
+
context.localFilePaths.push(p);
|
|
129
|
+
}
|
|
130
|
+
} catch (_ignoredErr) {}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const files = context.localFilePaths.map((p) => {
|
|
134
|
+
return {
|
|
135
|
+
orig: p,
|
|
136
|
+
noPrefix: p.substring(prefix.length, p.length),
|
|
137
|
+
origPosix: _convertToPosixPath(p),
|
|
138
|
+
noPrefixPosix: _convertToPosixPath(p).substring(
|
|
139
|
+
prefix.length,
|
|
140
|
+
p.length
|
|
141
|
+
)
|
|
142
|
+
};
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const pkgPath = _.find(files, (f) => {
|
|
146
|
+
return f.noPrefix === 'package.json';
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
if (pkgPath) {
|
|
150
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath.orig, 'utf8'));
|
|
151
|
+
const pkgDeps = [].concat(
|
|
152
|
+
Object.keys(pkg.dependencies || {}),
|
|
153
|
+
Object.keys(pkg.devDependencies || {})
|
|
154
|
+
);
|
|
155
|
+
context.pkgDeps = pkgDeps;
|
|
156
|
+
context.npmModules = _.uniq(context.npmModules.concat(pkgDeps)).sort();
|
|
157
|
+
} else {
|
|
158
|
+
context.pkgDeps = [];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return callback(null, {
|
|
162
|
+
files: _.uniqWith(files, _.isEqual),
|
|
163
|
+
modules: _.uniq(context.npmModules).filter(
|
|
164
|
+
(m) =>
|
|
165
|
+
m !== 'artillery' &&
|
|
166
|
+
m !== 'playwright' &&
|
|
167
|
+
!m.startsWith('@playwright/')
|
|
168
|
+
),
|
|
169
|
+
pkgDeps: context.pkgDeps,
|
|
170
|
+
fullyResolvedConfig: context.opts.scriptData.config
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function isLocalModule(modName) {
|
|
177
|
+
// NOTE: Absolute paths not supported
|
|
178
|
+
return modName.startsWith('.');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function applyScriptChanges(context, next) {
|
|
182
|
+
resolveConfigTemplates(
|
|
183
|
+
context.opts.scriptData,
|
|
184
|
+
context.opts.flags,
|
|
185
|
+
context.opts.absoluteScriptPath,
|
|
186
|
+
context.opts.scenarioPath
|
|
187
|
+
).then((resolvedConfig) => {
|
|
188
|
+
context.opts.scriptData = resolvedConfig;
|
|
189
|
+
return next(null, context);
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function getPlugins(context, next) {
|
|
194
|
+
const environmentPlugins = _.reduce(
|
|
195
|
+
_.get(context, 'opts.scriptData.config.environments', {}),
|
|
196
|
+
function getEnvironmentPlugins(acc, envSpec, _envName) {
|
|
197
|
+
acc = acc.concat(Object.keys(envSpec.plugins || []));
|
|
198
|
+
return acc;
|
|
199
|
+
},
|
|
200
|
+
[]
|
|
201
|
+
);
|
|
202
|
+
const pluginNames = Object.keys(
|
|
203
|
+
_.get(context, 'opts.scriptData.config.plugins', {})
|
|
204
|
+
).concat(environmentPlugins);
|
|
205
|
+
|
|
206
|
+
const pluginPackages = _.uniq(
|
|
207
|
+
pluginNames
|
|
208
|
+
.filter((p) => BUILTIN_PLUGINS.indexOf(p) === -1)
|
|
209
|
+
.map((p) => `artillery-plugin-${p}`)
|
|
210
|
+
);
|
|
211
|
+
debug(pluginPackages);
|
|
212
|
+
context.npmModules = context.npmModules.concat(pluginPackages);
|
|
213
|
+
|
|
214
|
+
return next(null, context);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function getCustomEngines(context, next) {
|
|
218
|
+
const environmentEngines = _.reduce(
|
|
219
|
+
_.get(context, 'opts.scriptData.config.environments', {}),
|
|
220
|
+
function getEnvironmentEngines(acc, envSpec, _envName) {
|
|
221
|
+
acc = acc.concat(Object.keys(envSpec.engines || []));
|
|
222
|
+
return acc;
|
|
223
|
+
},
|
|
224
|
+
[]
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
const engineNames = Object.keys(
|
|
228
|
+
_.get(context, 'opts.scriptData.config.engines', {})
|
|
229
|
+
).concat(environmentEngines);
|
|
230
|
+
|
|
231
|
+
const enginePackages = _.uniq(
|
|
232
|
+
engineNames
|
|
233
|
+
.filter((p) => BUILTIN_ENGINES.indexOf(p) === -1)
|
|
234
|
+
.map((p) => `artillery-engine-${p}`)
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
context.npmModules = context.npmModules.concat(enginePackages);
|
|
238
|
+
|
|
239
|
+
return next(null, context);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function getCustomJsDependencies(context, next) {
|
|
243
|
+
if (context.opts.scriptData.config?.processor) {
|
|
244
|
+
//
|
|
245
|
+
// Path to the main processor file:
|
|
246
|
+
//
|
|
247
|
+
|
|
248
|
+
const procPath = path.resolve(
|
|
249
|
+
path.dirname(context.opts.absoluteScriptPath),
|
|
250
|
+
context.opts.scriptData.config.processor
|
|
251
|
+
);
|
|
252
|
+
context.localFilePaths.push(procPath);
|
|
253
|
+
|
|
254
|
+
// Get the tree of requires from the main processor file:
|
|
255
|
+
const tree = depTree.toList({
|
|
256
|
+
filename: procPath,
|
|
257
|
+
directory: path.dirname(context.opts.absoluteScriptPath),
|
|
258
|
+
filter: (path) => path.indexOf('node_modules') === -1 // optional
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
debug('tree');
|
|
262
|
+
debug(tree);
|
|
263
|
+
|
|
264
|
+
function getNpmDependencies(filename) {
|
|
265
|
+
const src = fs.readFileSync(filename);
|
|
266
|
+
const requires = detective(src);
|
|
267
|
+
const npmPackages = requires
|
|
268
|
+
.filter(
|
|
269
|
+
(requireString) =>
|
|
270
|
+
!isBuiltin(requireString) && !isLocalModule(requireString)
|
|
271
|
+
)
|
|
272
|
+
.map((requireString) => {
|
|
273
|
+
return requireString.startsWith('@')
|
|
274
|
+
? `${requireString.split('/')[0]}/${requireString.split('/')[1]}`
|
|
275
|
+
: requireString.split('/')[0];
|
|
276
|
+
});
|
|
277
|
+
return npmPackages;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const allNpmDeps = tree.map(getNpmDependencies);
|
|
281
|
+
debug(allNpmDeps);
|
|
282
|
+
const reduced = allNpmDeps.reduce((acc, deps) => {
|
|
283
|
+
deps.forEach((d) => {
|
|
284
|
+
if (acc.indexOf(d) === -1) {
|
|
285
|
+
acc.push(d);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
return acc;
|
|
289
|
+
}, []);
|
|
290
|
+
debug(reduced);
|
|
291
|
+
|
|
292
|
+
//
|
|
293
|
+
// Any other local JS files and npm packages:
|
|
294
|
+
//
|
|
295
|
+
const procSrc = fs.readFileSync(procPath);
|
|
296
|
+
const _processorRequires = detective(procSrc);
|
|
297
|
+
// TODO: Look for and load dir/index.js and get its dependencies,
|
|
298
|
+
// rather than just grabbing the entire directory.
|
|
299
|
+
// NOTE: Some of these may be directories (with an index.js inside)
|
|
300
|
+
// Could be JSON files too.
|
|
301
|
+
context.localFilePaths = context.localFilePaths.concat(tree);
|
|
302
|
+
context.npmModules = context.npmModules.concat(reduced);
|
|
303
|
+
// Remove duplicate entries for the same file when invoked on a single .ts script
|
|
304
|
+
// See line 44 - the config.processor property is always set on .ts files, which leads to
|
|
305
|
+
// multiple entries in the localFilePaths array for the same file
|
|
306
|
+
context.localFilePaths = _.uniq(context.localFilePaths);
|
|
307
|
+
debug('got custom JS dependencies');
|
|
308
|
+
return next(null, context);
|
|
309
|
+
} else {
|
|
310
|
+
debug('no custom JS dependencies');
|
|
311
|
+
return next(null, context);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function getVariableDataFiles(context, next) {
|
|
316
|
+
// NOTE: assuming that context.opts.scriptData contains both the config and
|
|
317
|
+
// the scenarios section here.
|
|
318
|
+
|
|
319
|
+
// Iterate over environments
|
|
320
|
+
|
|
321
|
+
function resolvePayloadPaths(obj) {
|
|
322
|
+
const result = [];
|
|
323
|
+
if (obj.payload) {
|
|
324
|
+
// When using a separate config file, resolve paths relative to the scenario file
|
|
325
|
+
// Otherwise, resolve relative to the config file
|
|
326
|
+
const baseDir = context.opts.scenarioPath
|
|
327
|
+
? path.dirname(context.opts.scenarioPath)
|
|
328
|
+
: path.dirname(context.opts.absoluteScriptPath);
|
|
329
|
+
|
|
330
|
+
if (_.isArray(obj.payload)) {
|
|
331
|
+
obj.payload.forEach((payloadSpec) => {
|
|
332
|
+
result.push(path.resolve(baseDir, payloadSpec.path));
|
|
333
|
+
});
|
|
334
|
+
} else if (_.isObject(obj.payload)) {
|
|
335
|
+
// isObject returns true for arrays, so this branch must come second
|
|
336
|
+
result.push(path.resolve(baseDir, obj.payload.path));
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return result;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
context.localFilePaths = context.localFilePaths.concat(
|
|
343
|
+
resolvePayloadPaths(context.opts.scriptData.config)
|
|
344
|
+
);
|
|
345
|
+
context.opts.scriptData.config.environments =
|
|
346
|
+
context.opts.scriptData.config.environments || {};
|
|
347
|
+
Object.keys(context.opts.scriptData.config.environments).forEach(
|
|
348
|
+
(envName) => {
|
|
349
|
+
const envSpec = context.opts.scriptData.config.environments[envName];
|
|
350
|
+
context.localFilePaths = context.localFilePaths.concat(
|
|
351
|
+
resolvePayloadPaths(envSpec)
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
return next(null, context);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function getFileUploadPluginFiles(context, next) {
|
|
360
|
+
if (context.opts.scriptData.config?.plugins?.['http-file-uploads']) {
|
|
361
|
+
// Append filePaths array if it's there:
|
|
362
|
+
|
|
363
|
+
if (context.opts.scriptData.config.plugins['http-file-uploads'].filePaths) {
|
|
364
|
+
// When using a separate config file, resolve paths relative to the scenario file
|
|
365
|
+
// Otherwise, resolve relative to the config file
|
|
366
|
+
const baseDir = context.opts.scenarioPath
|
|
367
|
+
? path.dirname(context.opts.scenarioPath)
|
|
368
|
+
: path.dirname(context.opts.absoluteScriptPath);
|
|
369
|
+
|
|
370
|
+
const absPaths = context.opts.scriptData.config.plugins[
|
|
371
|
+
'http-file-uploads'
|
|
372
|
+
].filePaths.map((p) => {
|
|
373
|
+
return path.resolve(baseDir, p);
|
|
374
|
+
});
|
|
375
|
+
context.localFilePaths = context.localFilePaths.concat(absPaths);
|
|
376
|
+
}
|
|
377
|
+
return next(null, context);
|
|
378
|
+
} else {
|
|
379
|
+
return next(null, context);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function getExtraFiles(context, next) {
|
|
384
|
+
if (context.opts.scriptData.config?.includeFiles) {
|
|
385
|
+
// When using a separate config file, resolve paths relative to the scenario file
|
|
386
|
+
// Otherwise, resolve relative to the config file
|
|
387
|
+
const baseDir = context.opts.scenarioPath
|
|
388
|
+
? path.dirname(context.opts.scenarioPath)
|
|
389
|
+
: path.dirname(context.opts.absoluteScriptPath);
|
|
390
|
+
|
|
391
|
+
const absPaths = _.map(context.opts.scriptData.config.includeFiles, (p) => {
|
|
392
|
+
const includePath = path.resolve(baseDir, p);
|
|
393
|
+
debug('includeFile:', includePath);
|
|
394
|
+
return includePath;
|
|
395
|
+
});
|
|
396
|
+
context.localFilePaths = context.localFilePaths.concat(absPaths);
|
|
397
|
+
return next(null, context);
|
|
398
|
+
} else {
|
|
399
|
+
return next(null, context);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function getDotEnv(context, next) {
|
|
404
|
+
const flags = context.opts.flags;
|
|
405
|
+
if (!flags.dotenv || flags.platform === 'aws:ecs') {
|
|
406
|
+
return next(null, context);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const dotEnvPath = path.resolve(process.cwd(), flags.dotenv);
|
|
410
|
+
try {
|
|
411
|
+
if (fs.statSync(dotEnvPath)) {
|
|
412
|
+
context.localFilePaths.push(dotEnvPath);
|
|
413
|
+
}
|
|
414
|
+
} catch (_ignoredErr) {
|
|
415
|
+
console.log(`WARNING: could not find dotenv file: ${flags.dotenv}`);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return next(null, context);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function expandDirectories(context, next) {
|
|
422
|
+
// This can potentially lead to VERY unexpected behaviour, when used
|
|
423
|
+
// without due care with the file upload plugin (if filePaths is pointed at
|
|
424
|
+
// a directory that contains files OTHER than those to be used with the
|
|
425
|
+
// plugin)
|
|
426
|
+
//
|
|
427
|
+
// TODO: Warn if there are too many files in the directory
|
|
428
|
+
// TODO: Only allow specific filenames or globs, not directories
|
|
429
|
+
debug(context.localFilePaths);
|
|
430
|
+
// FIXME: Don't need to scan twice:
|
|
431
|
+
const dirs = context.localFilePaths.filter((p) => {
|
|
432
|
+
let result = false;
|
|
433
|
+
try {
|
|
434
|
+
result = fs.statSync(p).isDirectory();
|
|
435
|
+
} catch (_fsErr) {}
|
|
436
|
+
return result;
|
|
437
|
+
});
|
|
438
|
+
// Remove directories from the list:
|
|
439
|
+
context.localFilePaths = context.localFilePaths.filter((p) => {
|
|
440
|
+
let result = true;
|
|
441
|
+
try {
|
|
442
|
+
result = !fs.statSync(p).isDirectory();
|
|
443
|
+
} catch (_fsErr) {}
|
|
444
|
+
return result;
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
debug('Dirs to expand');
|
|
448
|
+
debug(dirs);
|
|
449
|
+
dirs.forEach((d) => {
|
|
450
|
+
const entries = walkSync.entries(d, { directories: false });
|
|
451
|
+
debug(entries);
|
|
452
|
+
context.localFilePaths = context.localFilePaths.concat(
|
|
453
|
+
entries.map((e) => {
|
|
454
|
+
return path.resolve(d, e.relativePath);
|
|
455
|
+
})
|
|
456
|
+
);
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
return next(null, context);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function commonPrefix(paths, separator) {
|
|
463
|
+
if (
|
|
464
|
+
!paths ||
|
|
465
|
+
paths.length === 0 ||
|
|
466
|
+
paths.filter((s) => typeof s !== 'string').length > 0
|
|
467
|
+
) {
|
|
468
|
+
return '';
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (paths.includes('/')) {
|
|
472
|
+
return '/';
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const sep = separator ? separator : path.sep;
|
|
476
|
+
|
|
477
|
+
const splitPaths = paths.map((p) => p.split(sep));
|
|
478
|
+
const shortestPath = splitPaths.reduce((a, b) => {
|
|
479
|
+
return a.length < b.length ? a : b;
|
|
480
|
+
}, splitPaths[0]);
|
|
481
|
+
|
|
482
|
+
let furthestIndex = shortestPath.length;
|
|
483
|
+
|
|
484
|
+
for (const p of splitPaths) {
|
|
485
|
+
for (let i = 0; i < furthestIndex; i++) {
|
|
486
|
+
if (p[i] !== shortestPath[i]) {
|
|
487
|
+
furthestIndex = i;
|
|
488
|
+
break;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const joined = shortestPath.slice(0, furthestIndex).join(sep);
|
|
494
|
+
|
|
495
|
+
if (joined.length > 0) {
|
|
496
|
+
// Check if joined path already ends with separator which
|
|
497
|
+
// will happen when input is a root drive on Windows, e.g. "C:\"
|
|
498
|
+
return joined.endsWith(sep) ? joined : joined + sep;
|
|
499
|
+
} else {
|
|
500
|
+
return '';
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function prettyPrint(manifest) {
|
|
505
|
+
artillery.logger({ showTimestamp: true }).log('Test bundle prepared...');
|
|
506
|
+
artillery.log('Test bundle contents:');
|
|
507
|
+
const t = new Table({ head: ['Name', 'Type', 'Notes'] });
|
|
508
|
+
for (const f of manifest.files) {
|
|
509
|
+
t.push([f.noPrefix, 'file']);
|
|
510
|
+
}
|
|
511
|
+
for (const m of manifest.modules) {
|
|
512
|
+
t.push([
|
|
513
|
+
m,
|
|
514
|
+
'package',
|
|
515
|
+
manifest.pkgDeps.indexOf(m) === -1 ? 'not in package.json' : ''
|
|
516
|
+
]);
|
|
517
|
+
}
|
|
518
|
+
artillery.log(t.toString());
|
|
519
|
+
artillery.log();
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
module.exports = {
|
|
523
|
+
createBOM,
|
|
524
|
+
commonPrefix,
|
|
525
|
+
prettyPrint,
|
|
526
|
+
applyScriptChanges,
|
|
527
|
+
getCustomJsDependencies
|
|
528
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const pkgJson = require('../../../../package.json');
|
|
2
|
+
const DEFAULT_IMAGE_TAG = pkgJson.version;
|
|
3
|
+
|
|
4
|
+
// Default wait timeout for cloud workers to start
|
|
5
|
+
let WAIT_TIMEOUT_SEC = 600;
|
|
6
|
+
|
|
7
|
+
// Legacy override
|
|
8
|
+
if (process.env.ECS_WAIT_TIMEOUT) {
|
|
9
|
+
WAIT_TIMEOUT_SEC = parseInt(process.env.ECS_WAIT_TIMEOUT, 10);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Override
|
|
13
|
+
if (process.env.WORKER_WAIT_TIMEOUT_SEC) {
|
|
14
|
+
WAIT_TIMEOUT_SEC = parseInt(process.env.WORKER_WAIT_TIMEOUT_SEC, 10);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = {
|
|
18
|
+
ARTILLERY_CLUSTER_NAME: 'artilleryio-cluster',
|
|
19
|
+
TASK_NAME: 'artilleryio-loadgen-worker',
|
|
20
|
+
SQS_QUEUES_NAME_PREFIX: 'artilleryio_test_metrics',
|
|
21
|
+
S3_BUCKET_NAME_PREFIX: 'artilleryio-test-data',
|
|
22
|
+
LOGGROUP_NAME: 'artilleryio-log-group',
|
|
23
|
+
LOGGROUP_RETENTION_DAYS: process.env.ARTILLERY_LOGGROUP_RETENTION_DAYS || 180,
|
|
24
|
+
IMAGE_VERSION: process.env.ECR_IMAGE_VERSION || DEFAULT_IMAGE_TAG,
|
|
25
|
+
WAIT_TIMEOUT: WAIT_TIMEOUT_SEC,
|
|
26
|
+
TEST_RUNS_MAX_TAGS: parseInt(process.env.TEST_RUNS_MAX_TAGS, 10) || 8
|
|
27
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const { S3Client } = require('@aws-sdk/client-s3');
|
|
2
|
+
|
|
3
|
+
module.exports = createS3Client;
|
|
4
|
+
|
|
5
|
+
function createS3Client(opts = {}) {
|
|
6
|
+
const defaultOpts = {
|
|
7
|
+
apiVersion: '2006-03-01'
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
let clientOpts = Object.assign(defaultOpts, opts);
|
|
11
|
+
|
|
12
|
+
if (process.env.ARTILLERY_S3_OPTS) {
|
|
13
|
+
clientOpts = Object.assign(
|
|
14
|
+
defaultOpts,
|
|
15
|
+
JSON.parse(process.env.ARTILLERY_S3_OPTS)
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!opts.region) {
|
|
20
|
+
clientOpts.region = global.artillery.s3BucketRegion;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return new S3Client(clientOpts);
|
|
24
|
+
}
|