@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,247 @@
1
+
2
+
3
+ const A = require('async');
4
+ const debug = require('debug')('commands:create-test');
5
+
6
+ const { getBucketName } = require('./util');
7
+ const createS3Client = require('./create-s3-client');
8
+
9
+ const path = require('node:path');
10
+
11
+ const fs = require('node:fs');
12
+
13
+ const { createBOM, prettyPrint } = require('./bom');
14
+
15
+ const { PutObjectCommand } = require('@aws-sdk/client-s3');
16
+
17
+ function tryCreateTest(scriptPath, options) {
18
+ createTest(scriptPath, options);
19
+ }
20
+
21
+ async function createTest(scriptPath, options, callback) {
22
+ const absoluteScriptPath = path.resolve(process.cwd(), scriptPath);
23
+
24
+ const contextPath = options.context
25
+ ? path.resolve(options.context)
26
+ : path.dirname(absoluteScriptPath);
27
+
28
+ debug('script:', absoluteScriptPath);
29
+ debug('root:', contextPath);
30
+
31
+ const context = {
32
+ contextDir: contextPath,
33
+ scriptPath: absoluteScriptPath,
34
+ originalScriptPath: scriptPath,
35
+ name: options.name, // test name, eg simple-bom or aht_$UUID
36
+ manifestPath: options.manifestPath,
37
+ packageJsonPath: options.packageJsonPath,
38
+ flags: options.flags
39
+ };
40
+
41
+ if (typeof options.config === 'string') {
42
+ const absoluteConfigPath = path.resolve(process.cwd(), options.config);
43
+ context.configPath = absoluteConfigPath;
44
+ }
45
+
46
+ if (options.customSyncClient) {
47
+ context.customSyncClient = options.customSyncClient;
48
+ }
49
+
50
+ return new Promise((resolve, reject) => {
51
+ A.waterfall(
52
+ [
53
+ A.constant(context),
54
+ async (context) => {
55
+ if (!context.customSyncClient) {
56
+ context.s3Bucket = await getBucketName();
57
+ return context;
58
+ } else {
59
+ context.s3Bucket = 'S3_BUCKET_ARGUMENT_NOT_USED_ON_AZURE';
60
+ return context;
61
+ }
62
+ },
63
+ prepareManifest,
64
+ printManifest,
65
+ syncS3,
66
+ writeTestMetadata
67
+ ],
68
+ (err, context) => {
69
+ if (err) {
70
+ console.log(err);
71
+ return;
72
+ }
73
+
74
+ if (callback) {
75
+ callback(err, context);
76
+ } else if (err) {
77
+ reject(err);
78
+ } else {
79
+ resolve(context);
80
+ }
81
+ }
82
+ );
83
+ });
84
+ }
85
+
86
+ function prepareManifest(context, callback) {
87
+ let fileToAnalyse = context.scriptPath;
88
+ const extraFiles = [];
89
+ if (context.configPath) {
90
+ debug('context has been provided; extraFiles =', extraFiles);
91
+ fileToAnalyse = context.configPath;
92
+ extraFiles.push(context.scriptPath);
93
+ }
94
+
95
+ createBOM(
96
+ fileToAnalyse,
97
+ extraFiles,
98
+ {
99
+ packageJsonPath: context.packageJsonPath,
100
+ flags: context.flags,
101
+ scenarioPath: context.scriptPath
102
+ },
103
+ (err, bom) => {
104
+ debug(err);
105
+ debug(bom);
106
+ context.manifest = bom;
107
+ return callback(err, context);
108
+ }
109
+ );
110
+ }
111
+
112
+ function printManifest(context, callback) {
113
+ prettyPrint(context.manifest);
114
+ return callback(null, context);
115
+ }
116
+
117
+ async function syncS3(context) {
118
+ let s3;
119
+ if (context.customSyncClient) {
120
+ s3 = context.customSyncClient;
121
+ } else {
122
+ s3 = createS3Client();
123
+ }
124
+
125
+ const prefix = `tests/${context.name}`;
126
+
127
+ context.s3Prefix = prefix;
128
+
129
+ debug('Will try syncing to:', context.s3Bucket);
130
+
131
+ debug('Manifest: ', context.manifest);
132
+
133
+ // Iterate through manifest, for each element: has orig (local source) and noPrefix (S3
134
+ // destination) properties
135
+ return new Promise((resolve, reject) => {
136
+ A.eachLimit(
137
+ context.manifest.files,
138
+ 3,
139
+ async (item, eachDone) => {
140
+ // If we can't read the file, it may have been specified with a
141
+ // template in its name, e.g. a payload file like:
142
+ // {{ $environment }}-users.csv
143
+ // If so, ignore it, hope config.includeFiles was used, and let
144
+ // "artillery run" in the worker deal with it.
145
+ let body;
146
+ try {
147
+ body = fs.readFileSync(item.orig);
148
+ } catch (fsErr) {
149
+ debug(fsErr);
150
+ }
151
+
152
+ if (!body) {
153
+ return eachDone(null, context);
154
+ }
155
+
156
+ // Filter bundled packages from package.json before upload
157
+ if (item.noPrefix === 'package.json') {
158
+ const pkg = JSON.parse(body.toString());
159
+ const filterBundled = (deps) => {
160
+ if (!deps) return deps;
161
+ const filtered = {};
162
+ for (const [name, version] of Object.entries(deps)) {
163
+ if (
164
+ name !== 'artillery' &&
165
+ name !== 'playwright' &&
166
+ !name.startsWith('@playwright/')
167
+ ) {
168
+ filtered[name] = version;
169
+ }
170
+ }
171
+ return filtered;
172
+ };
173
+ pkg.dependencies = filterBundled(pkg.dependencies);
174
+ pkg.devDependencies = filterBundled(pkg.devDependencies);
175
+ body = Buffer.from(JSON.stringify(pkg, null, 2));
176
+ }
177
+
178
+ const key = `${context.s3Prefix}/${item.noPrefixPosix}`;
179
+ await s3.send(
180
+ new PutObjectCommand({
181
+ Bucket: context.s3Bucket,
182
+ Key: key,
183
+ Body: body
184
+ })
185
+ );
186
+ },
187
+ (err) => {
188
+ if (err) {
189
+ reject(err);
190
+ } else {
191
+ resolve(context);
192
+ }
193
+ }
194
+ );
195
+ });
196
+ }
197
+
198
+ // create just overwrites an existing test for now
199
+ async function writeTestMetadata(context) {
200
+ const metadata = {
201
+ createdOn: Date.now(),
202
+ name: context.name,
203
+ modules: context.manifest.modules
204
+ };
205
+
206
+ // Here we need to provide config information (if given) -- so that the worker knows how to load it
207
+ if (context.configPath) {
208
+ const res = context.manifest.files.filter((o) => {
209
+ return o.orig === context.configPath;
210
+ });
211
+ const newConfigPath = res[0].noPrefixPosix; // if we have been given a config, we must have an entry
212
+ metadata.configPath = newConfigPath;
213
+ }
214
+
215
+ const newScriptPath = context.manifest.files.filter((o) => {
216
+ return o.orig === context.scriptPath;
217
+ })[0].noPrefixPosix;
218
+ metadata.scriptPath = newScriptPath;
219
+
220
+ debug('metadata', metadata);
221
+
222
+ let s3 = null;
223
+ if (context.customSyncClient) {
224
+ s3 = context.customSyncClient;
225
+ } else {
226
+ s3 = createS3Client();
227
+ }
228
+
229
+ const key = `${context.s3Prefix}/metadata.json`; // TODO: Rename to something less likely to clash
230
+ debug('metadata location:', `${context.s3Bucket}/${key}`);
231
+ await s3.send(
232
+ new PutObjectCommand({
233
+ Body: JSON.stringify(metadata),
234
+ Bucket: context.s3Bucket,
235
+ Key: key
236
+ })
237
+ );
238
+
239
+ return context;
240
+ }
241
+
242
+ module.exports = {
243
+ tryCreateTest,
244
+ createTest,
245
+ syncS3,
246
+ prepareManifest
247
+ };
@@ -0,0 +1,34 @@
1
+ class TestNotFoundError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = 'TestNotFoundError';
5
+ }
6
+ }
7
+
8
+ class NoAvailableQueueError extends Error {
9
+ constructor(message) {
10
+ super(message);
11
+ this.name = 'NoAvailableQueueError';
12
+ }
13
+ }
14
+
15
+ class ClientServerVersionMismatchError extends Error {
16
+ constructor(message) {
17
+ super(message);
18
+ this.name = 'ClientServerMismatchError';
19
+ }
20
+ }
21
+
22
+ class ConsoleOutputSerializeError extends Error {
23
+ constructor(message) {
24
+ super(message);
25
+ this.name = 'OutputSerializeError';
26
+ }
27
+ }
28
+
29
+ module.exports = {
30
+ TestNotFoundError,
31
+ NoAvailableQueueError,
32
+ ClientServerVersionMismatchError,
33
+ ConsoleOutputSerializeError
34
+ };
@@ -0,0 +1,149 @@
1
+ const assert = require('node:assert').strict;
2
+ const {
3
+ EC2Client,
4
+ DescribeRouteTablesCommand,
5
+ DescribeVpcsCommand,
6
+ DescribeSubnetsCommand
7
+ } = require('@aws-sdk/client-ec2');
8
+
9
+ class VPCSubnetFinder {
10
+ constructor(opts) {
11
+ this.ec2 = new EC2Client(opts);
12
+ }
13
+
14
+ async getRouteTables(vpcId) {
15
+ const rts = await this.ec2.send(
16
+ new DescribeRouteTablesCommand({
17
+ Filters: [
18
+ {
19
+ Name: 'vpc-id',
20
+ Values: [vpcId]
21
+ }
22
+ ]
23
+ })
24
+ );
25
+
26
+ return rts.RouteTables;
27
+ }
28
+
29
+ async findDefaultVpc() {
30
+ const vpcRes = await this.ec2.send(
31
+ new DescribeVpcsCommand({
32
+ Filters: [
33
+ {
34
+ Name: 'isDefault',
35
+ Values: ['true']
36
+ }
37
+ ]
38
+ })
39
+ );
40
+
41
+ assert.ok(vpcRes.Vpcs.length <= 1);
42
+
43
+ if (vpcRes.Vpcs.length !== 1) {
44
+ return null;
45
+ } else {
46
+ return vpcRes.Vpcs[0].VpcId;
47
+ }
48
+ }
49
+
50
+ async getSubnets(vpcId) {
51
+ const subRes = await this.ec2.send(
52
+ new DescribeSubnetsCommand({
53
+ Filters: [
54
+ {
55
+ Name: 'vpc-id',
56
+ Values: [vpcId]
57
+ }
58
+ ]
59
+ })
60
+ );
61
+
62
+ return subRes.Subnets;
63
+ }
64
+
65
+ isSubnetPublic(routeTables, subnetId) {
66
+ //
67
+ // Inspect associations of each route table (of a specific VPC). A route
68
+ // table record has an Associations field, which is a list of association
69
+ // objects. There are two types of those:
70
+ //
71
+ // 1. An implicit association, which is indicated by field Main set to
72
+ // true and no explicit subnet id.
73
+ // 2. An explicit association, which is indicated by field Main set to
74
+ // false, and a SubnetId field containing a subnet id.
75
+ //
76
+
77
+ // Route table for the subnet - can there only be one?
78
+ let subnetTable = routeTables.filter((rt) => {
79
+ const explicitAssoc = rt.Associations.filter((assoc) => {
80
+ return assoc.SubnetId && assoc.SubnetId === subnetId;
81
+ });
82
+
83
+ assert.ok(explicitAssoc.length <= 1);
84
+
85
+ return explicitAssoc.length === 1;
86
+ });
87
+
88
+ if (subnetTable.length === 0) {
89
+ // There is no explicit association for this subnet so it will be implicitly
90
+ // associated with the VPC's main routing table.
91
+ subnetTable = routeTables.filter((rt) => {
92
+ const implicitAssoc = rt.Associations.filter((assoc) => {
93
+ return assoc.Main === true;
94
+ });
95
+
96
+ assert.ok(implicitAssoc.length <= 1);
97
+
98
+ return implicitAssoc.length === 1;
99
+ });
100
+ }
101
+
102
+ if (subnetTable.length !== 1) {
103
+ throw new Error(
104
+ `Could not locate routing table for subnet: subnet id: ${subnetId}`
105
+ );
106
+ }
107
+
108
+ const igwRoutes = subnetTable[0].Routes.filter((route) => {
109
+ // NOTE: there may be no IGW attached to route
110
+ return route.GatewayId?.startsWith('igw-');
111
+ });
112
+
113
+ return igwRoutes.length > 0;
114
+ }
115
+
116
+ // TODO: Distinguish between there being no default VPC,
117
+ // or being given an invalid VPC ID, and no public subnets
118
+ // existing in a VPC that definitely exists.
119
+ async findPublicSubnets(vpcId) {
120
+ if (!vpcId) {
121
+ vpcId = await this.findDefaultVpc();
122
+ }
123
+ const rts = await this.getRouteTables(vpcId);
124
+ const subnets = await this.getSubnets(vpcId);
125
+
126
+ const publicSubnets = subnets.filter((subnet) => {
127
+ return this.isSubnetPublic(rts, subnet.SubnetId);
128
+ });
129
+
130
+ return publicSubnets;
131
+ }
132
+ }
133
+
134
+ async function main() {
135
+ const f = new VPCSubnetFinder({ region: process.env.REGION });
136
+
137
+ try {
138
+ const publicSubnets = await f.findPublicSubnets(process.env.VPC_ID);
139
+ console.log(publicSubnets.map((s) => s.SubnetId).join('\n'));
140
+ } catch (err) {
141
+ console.log(err);
142
+ }
143
+ }
144
+
145
+ if (require.main === module) {
146
+ main();
147
+ }
148
+
149
+ module.exports = { VPCSubnetFinder };
@@ -0,0 +1,27 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
+
5
+
6
+
7
+ module.exports.Plugin = ArtilleryInspectScriptPlugin;
8
+
9
+ const { btoa } = require('../../util');
10
+
11
+ function ArtilleryInspectScriptPlugin(script, events) {
12
+ this.script = script;
13
+ this.events = events;
14
+
15
+ const checksConfig = script.config?.ensure || script.config?.plugins?.ensure;
16
+
17
+ if (checksConfig) {
18
+ console.log(
19
+ `inspect-script.config.ensure=${btoa(JSON.stringify(checksConfig))}`
20
+ );
21
+ }
22
+
23
+ return this;
24
+ }
25
+ ArtilleryInspectScriptPlugin.prototype.cleanup = (done) => {
26
+ done(null);
27
+ };
@@ -0,0 +1,80 @@
1
+ // Copyright (c) Artillery Software Inc.
2
+ // SPDX-License-Identifier: BUSL-1.1
3
+ //
4
+ // Non-evaluation use of Artillery on Azure requires a commercial license
5
+
6
+ const { QueueClient } = require('@azure/storage-queue');
7
+ const { BlobServiceClient } = require('@azure/storage-blob');
8
+ const { DefaultAzureCredential } = require('@azure/identity');
9
+ const { randomUUID } = require('node:crypto');
10
+
11
+ function getAQS() {
12
+ return new QueueClient(
13
+ process.env.AZURE_STORAGE_QUEUE_URL,
14
+ new DefaultAzureCredential()
15
+ );
16
+ }
17
+
18
+ // Azure Queue Storage has a 64KB message limit
19
+ // Use 60KB threshold to leave margin for encoding overhead
20
+ const AQS_SIZE_LIMIT = 60 * 1024;
21
+
22
+ let blobContainerClient = null;
23
+
24
+ function getBlobClient() {
25
+ if (!blobContainerClient) {
26
+ const storageAccount = process.env.AZURE_STORAGE_ACCOUNT;
27
+ const containerName = process.env.AZURE_STORAGE_BLOB_CONTAINER;
28
+ if (!storageAccount || !containerName) {
29
+ throw new Error(
30
+ 'AZURE_STORAGE_ACCOUNT and AZURE_STORAGE_BLOB_CONTAINER must be set'
31
+ );
32
+ }
33
+ const blobServiceClient = new BlobServiceClient(
34
+ `https://${storageAccount}.blob.core.windows.net`,
35
+ new DefaultAzureCredential()
36
+ );
37
+ blobContainerClient = blobServiceClient.getContainerClient(containerName);
38
+ }
39
+ return blobContainerClient;
40
+ }
41
+
42
+ async function sendMessage(queue, body, tags) {
43
+ const payload = JSON.stringify({
44
+ payload: body,
45
+ attributes: tags.reduce((acc, tag) => {
46
+ acc[tag.key] = tag.value;
47
+ return acc;
48
+ }, {})
49
+ });
50
+
51
+ // Check if payload exceeds Azure Queue Storage limit
52
+ if (Buffer.byteLength(payload, 'utf8') > AQS_SIZE_LIMIT) {
53
+ // Upload to blob storage and send reference
54
+ const testId = tags.find((t) => t.key === 'testId')?.value;
55
+ const workerId = tags.find((t) => t.key === 'workerId')?.value;
56
+ const messageId = randomUUID();
57
+ const blobName = `tests/${testId}/overflow/${workerId}/${messageId}.json`;
58
+
59
+ const blobClient = getBlobClient().getBlockBlobClient(blobName);
60
+ await blobClient.upload(payload, Buffer.byteLength(payload, 'utf8'));
61
+
62
+ // Send reference message
63
+ const refPayload = JSON.stringify({
64
+ payload: {
65
+ _overflowRef: blobName,
66
+ event: body.event
67
+ },
68
+ attributes: tags.reduce((acc, tag) => {
69
+ acc[tag.key] = tag.value;
70
+ return acc;
71
+ }, {})
72
+ });
73
+
74
+ return queue.sendMessage(refPayload);
75
+ }
76
+
77
+ return queue.sendMessage(payload);
78
+ }
79
+
80
+ module.exports = { getAQS, sendMessage };