@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.
Files changed (90) hide show
  1. package/README.md +63 -0
  2. package/bin/run +29 -0
  3. package/bin/run.cmd +3 -0
  4. package/changes.json +138 -0
  5. package/console-reporter.js +1 -0
  6. package/lib/artillery-global.js +33 -0
  7. package/lib/cli/banner.js +8 -0
  8. package/lib/cli/common-flags.js +80 -0
  9. package/lib/cli/hooks/version.js +20 -0
  10. package/lib/cmds/dino.js +109 -0
  11. package/lib/cmds/quick.js +122 -0
  12. package/lib/cmds/report.js +34 -0
  13. package/lib/cmds/run-aci.js +91 -0
  14. package/lib/cmds/run-fargate.js +192 -0
  15. package/lib/cmds/run-lambda.js +96 -0
  16. package/lib/cmds/run.js +671 -0
  17. package/lib/console-capture.js +92 -0
  18. package/lib/console-reporter.js +438 -0
  19. package/lib/create-bom/built-in-plugins.js +12 -0
  20. package/lib/create-bom/create-bom.js +301 -0
  21. package/lib/dispatcher.js +9 -0
  22. package/lib/dist.js +222 -0
  23. package/lib/index.js +5 -0
  24. package/lib/launch-platform.js +439 -0
  25. package/lib/load-plugins.js +113 -0
  26. package/lib/platform/aws/aws-cloudwatch.js +106 -0
  27. package/lib/platform/aws/aws-create-sqs-queue.js +58 -0
  28. package/lib/platform/aws/aws-ensure-s3-bucket-exists.js +78 -0
  29. package/lib/platform/aws/aws-get-account-id.js +26 -0
  30. package/lib/platform/aws/aws-get-bucket-region.js +18 -0
  31. package/lib/platform/aws/aws-get-credentials.js +28 -0
  32. package/lib/platform/aws/aws-get-default-region.js +26 -0
  33. package/lib/platform/aws/aws-whoami.js +15 -0
  34. package/lib/platform/aws/constants.js +7 -0
  35. package/lib/platform/aws/iam-cf-templates/aws-iam-fargate-cf-template.yml +219 -0
  36. package/lib/platform/aws/iam-cf-templates/aws-iam-lambda-cf-template.yml +125 -0
  37. package/lib/platform/aws/iam-cf-templates/gh-oidc-fargate.yml +241 -0
  38. package/lib/platform/aws/iam-cf-templates/gh-oidc-lambda.yml +153 -0
  39. package/lib/platform/aws-ecs/ecs.js +247 -0
  40. package/lib/platform/aws-ecs/legacy/aws-util.js +134 -0
  41. package/lib/platform/aws-ecs/legacy/bom.js +528 -0
  42. package/lib/platform/aws-ecs/legacy/constants.js +27 -0
  43. package/lib/platform/aws-ecs/legacy/create-s3-client.js +24 -0
  44. package/lib/platform/aws-ecs/legacy/create-test.js +247 -0
  45. package/lib/platform/aws-ecs/legacy/errors.js +34 -0
  46. package/lib/platform/aws-ecs/legacy/find-public-subnets.js +149 -0
  47. package/lib/platform/aws-ecs/legacy/plugins/artillery-plugin-inspect-script/index.js +27 -0
  48. package/lib/platform/aws-ecs/legacy/plugins/artillery-plugin-sqs-reporter/azure-aqs.js +80 -0
  49. package/lib/platform/aws-ecs/legacy/plugins/artillery-plugin-sqs-reporter/index.js +202 -0
  50. package/lib/platform/aws-ecs/legacy/plugins.js +16 -0
  51. package/lib/platform/aws-ecs/legacy/run-cluster.js +1994 -0
  52. package/lib/platform/aws-ecs/legacy/sqs-reporter.js +401 -0
  53. package/lib/platform/aws-ecs/legacy/tags.js +22 -0
  54. package/lib/platform/aws-ecs/legacy/test-run-status.js +9 -0
  55. package/lib/platform/aws-ecs/legacy/time.js +67 -0
  56. package/lib/platform/aws-ecs/legacy/util.js +97 -0
  57. package/lib/platform/aws-ecs/worker/Dockerfile +64 -0
  58. package/lib/platform/aws-ecs/worker/helpers.sh +80 -0
  59. package/lib/platform/aws-ecs/worker/loadgen-worker +656 -0
  60. package/lib/platform/aws-lambda/dependencies.js +130 -0
  61. package/lib/platform/aws-lambda/index.js +734 -0
  62. package/lib/platform/aws-lambda/lambda-handler/a9-handler-dependencies.js +73 -0
  63. package/lib/platform/aws-lambda/lambda-handler/a9-handler-helpers.js +43 -0
  64. package/lib/platform/aws-lambda/lambda-handler/a9-handler-index.js +235 -0
  65. package/lib/platform/aws-lambda/lambda-handler/package.json +15 -0
  66. package/lib/platform/aws-lambda/prices.js +29 -0
  67. package/lib/platform/az/aci.js +694 -0
  68. package/lib/platform/az/aqs-queue-consumer.js +88 -0
  69. package/lib/platform/az/regions.js +52 -0
  70. package/lib/platform/cloud/api.js +72 -0
  71. package/lib/platform/cloud/cloud.js +448 -0
  72. package/lib/platform/cloud/http-client.js +19 -0
  73. package/lib/platform/local/artillery-worker-local.js +154 -0
  74. package/lib/platform/local/index.js +174 -0
  75. package/lib/platform/local/worker.js +261 -0
  76. package/lib/platform/worker-states.js +13 -0
  77. package/lib/queue-consumer/index.js +56 -0
  78. package/lib/stash.js +41 -0
  79. package/lib/telemetry.js +78 -0
  80. package/lib/util/await-on-ee.js +24 -0
  81. package/lib/util/generate-id.js +9 -0
  82. package/lib/util/parse-tag-string.js +21 -0
  83. package/lib/util/prepare-test-execution-plan.js +216 -0
  84. package/lib/util/sleep.js +7 -0
  85. package/lib/util/validate-script.js +132 -0
  86. package/lib/util.js +294 -0
  87. package/lib/utils-config.js +31 -0
  88. package/package.json +323 -0
  89. package/types.d.ts +317 -0
  90. 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
+ }