@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,694 @@
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 { QueueConsumer } = require('./aqs-queue-consumer');
7
+ const { SQS_QUEUES_NAME_PREFIX } = require('../aws/constants');
8
+ const { DefaultAzureCredential } = require('@azure/identity');
9
+ const { QueueClient } = require('@azure/storage-queue');
10
+ const {
11
+ ContainerInstanceManagementClient
12
+ } = require('@azure/arm-containerinstance');
13
+ const { BlobServiceClient } = require('@azure/storage-blob');
14
+ const { createTest } = require('../aws-ecs/legacy/create-test');
15
+ const util = require('../aws-ecs/legacy/util');
16
+ const generateId = require('../../util/generate-id');
17
+ const EventEmitter = require('eventemitter3');
18
+ const debug = require('debug')('platform:azure-aci');
19
+ const { IMAGE_VERSION, WAIT_TIMEOUT } = require('../aws-ecs/legacy/constants');
20
+ const { regionNames } = require('./regions');
21
+ const path = require('node:path');
22
+ const { Timeout, sleep } = require('../aws-ecs/legacy/time');
23
+ const dotenv = require('dotenv');
24
+ const fs = require('node:fs');
25
+ const request = require('got');
26
+
27
+ // Helper to convert readable stream to string
28
+ async function streamToString(readableStream) {
29
+ return new Promise((resolve, reject) => {
30
+ const chunks = [];
31
+ readableStream.on('data', (data) => {
32
+ chunks.push(data.toString());
33
+ });
34
+ readableStream.on('end', () => {
35
+ resolve(chunks.join(''));
36
+ });
37
+ readableStream.on('error', reject);
38
+ });
39
+ }
40
+
41
+ class PlatformAzureACI {
42
+ constructor(script, variablePayload, opts, platformOpts) {
43
+ this.script = script;
44
+ this.variablePayload = variablePayload;
45
+ this.opts = opts;
46
+ this.platformOpts = platformOpts;
47
+
48
+ this.cloudKey =
49
+ this.platformOpts.cliArgs.key || process.env.ARTILLERY_CLOUD_API_KEY;
50
+
51
+ this.events = new EventEmitter();
52
+
53
+ this.testRunId = platformOpts.testRunId;
54
+
55
+ this.workers = {};
56
+ this.count = 0;
57
+ this.waitingReadyCount = 0;
58
+ this.artilleryArgs = [];
59
+
60
+ this.azureTenantId =
61
+ process.env.AZURE_TENANT_ID || platformOpts.platformConfig['tenant-id'];
62
+ this.azureSubscriptionId =
63
+ process.env.AZURE_SUBSCRIPTION_ID ||
64
+ platformOpts.platformConfig['subscription-id'];
65
+ this.azureClientId = process.env.AZURE_CLIENT_ID;
66
+ this.azureClientSecret = process.env.AZURE_CLIENT_SECRET;
67
+
68
+ this.storageAccount =
69
+ process.env.AZURE_STORAGE_ACCOUNT ||
70
+ platformOpts.platformConfig['storage-account'];
71
+ this.blobContainerName =
72
+ process.env.AZURE_STORAGE_BLOB_CONTAINER ||
73
+ platformOpts.platformConfig['blob-container'];
74
+ this.resourceGroupName =
75
+ process.env.AZURE_RESOURCE_GROUP ||
76
+ platformOpts.platformConfig['resource-group'];
77
+
78
+ this.cpu = parseInt(platformOpts.platformConfig.cpu, 10) || 4;
79
+ this.memory = parseInt(platformOpts.platformConfig.memory, 10) || 8;
80
+ this.region = platformOpts.platformConfig.region || 'eastus';
81
+
82
+ this.extraEnvVars = {};
83
+
84
+ if (!regionNames.includes(this.region)) {
85
+ const err = new Error(`Invalid region: ${this.region}`);
86
+ err.code = 'INVALID_REGION';
87
+ err.url = 'https://docs.art/az/regions';
88
+ throw err;
89
+ }
90
+
91
+ if (
92
+ !this.azureTenantId ||
93
+ !this.azureSubscriptionId ||
94
+ !this.azureClientId ||
95
+ !this.azureClientSecret
96
+ ) {
97
+ const err = new Error('Azure credentials not found');
98
+ err.code = 'AZURE_CREDENTIALS_NOT_FOUND';
99
+ err.url = 'https://docs.art/az/credentials';
100
+ throw err;
101
+ }
102
+
103
+ if (
104
+ !this.storageAccount ||
105
+ !this.blobContainerName ||
106
+ !this.resourceGroupName
107
+ ) {
108
+ const err = new Error('Azure configuration not found');
109
+ err.code = 'AZURE_CONFIG_NOT_FOUND';
110
+ err.url = 'https://docs.art/az/configuration';
111
+ throw err;
112
+ }
113
+
114
+ this.containerInstances = [];
115
+ }
116
+
117
+ async init() {
118
+ const credential = new DefaultAzureCredential();
119
+
120
+ artillery.log('Tenant ID:', this.azureTenantId);
121
+ artillery.log('Subscription ID:', this.azureSubscriptionId);
122
+ artillery.log('Storage account:', this.storageAccount);
123
+ artillery.log('Blob container:', this.blobContainerName);
124
+ artillery.log('Resource group:', this.resourceGroupName);
125
+
126
+ if (this.platformOpts.count > 5) {
127
+ const ok = await this.checkLicense();
128
+ if (!ok) {
129
+ console.log();
130
+ console.log(`
131
+ +--------------------------------------------------+
132
+ | License for Azure integration not found |
133
+ | |
134
+ | Load tests on Azure are limited to a maximum of |
135
+ | 5 workers without a valid license. |
136
+ | See https://docs.art/az/license for more details |
137
+ +--------------------------------------------------+
138
+ `);
139
+ throw new Error('ERR_LICENSE_REQUIRED');
140
+ }
141
+ }
142
+ //
143
+ // Upload test bundle
144
+ //
145
+
146
+ this.blobServiceClient = new BlobServiceClient(
147
+ `https://${this.storageAccount}.blob.core.windows.net`,
148
+ credential
149
+ );
150
+ this.blobContainerClient = this.blobServiceClient.getContainerClient(
151
+ this.blobContainerName
152
+ );
153
+
154
+ const customSyncClient = {
155
+ send: async (command) => {
156
+ // command is always an instance of PutObjectCommand() from @aws-sdk/client-s3
157
+ const { Key, Body } = command.input;
158
+ const blockBlobClient =
159
+ this.blobContainerClient.getBlockBlobClient(Key);
160
+ await blockBlobClient.upload(Body, Body.length);
161
+ }
162
+ };
163
+
164
+ const { manifest } = await createTest(this.opts.absoluteScriptPath, {
165
+ name: this.testRunId,
166
+ config: this.platformOpts.cliArgs.config,
167
+ flags: this.platformOpts.cliArgs,
168
+ customSyncClient
169
+ });
170
+
171
+ //
172
+ // Create the queue
173
+ //
174
+ this.queueName = `${SQS_QUEUES_NAME_PREFIX}_${this.testRunId}.`
175
+ .replaceAll('_', '-')
176
+ .slice(0, 63);
177
+ this.queueUrl =
178
+ process.env.AZURE_STORAGE_QUEUE_URL ||
179
+ `https://${this.storageAccount}.queue.core.windows.net/${this.queueName}`;
180
+ const queueClient = new QueueClient(this.queueUrl, credential);
181
+ await queueClient.create();
182
+ this.aqsClient = queueClient;
183
+
184
+ // Construct CLI args for the container
185
+
186
+ this.artilleryArgs = [];
187
+ this.artilleryArgs.push('run');
188
+
189
+ if (this.platformOpts.cliArgs.environment) {
190
+ this.artilleryArgs.push('-e');
191
+ this.artilleryArgs.push(this.platformOpts.cliArgs.environment);
192
+ }
193
+ if (this.platformOpts.cliArgs.solo) {
194
+ this.artilleryArgs.push('--solo');
195
+ }
196
+
197
+ if (this.platformOpts.cliArgs.target) {
198
+ this.artilleryArgs.push('--target');
199
+ this.artilleryArgs.push(this.platformOpts.cliArgs.target);
200
+ }
201
+
202
+ if (this.platformOpts.cliArgs.variables) {
203
+ this.artilleryArgs.push('-v');
204
+ this.artilleryArgs.push(this.platformOpts.cliArgs.variables);
205
+ }
206
+
207
+ if (this.platformOpts.cliArgs.overrides) {
208
+ this.artilleryArgs.push('--overrides');
209
+ this.artilleryArgs.push(this.platformOpts.cliArgs.overrides);
210
+ }
211
+
212
+ if (this.platformOpts.cliArgs.dotenv) {
213
+ const dotEnvPath = path.resolve(
214
+ process.cwd(),
215
+ this.platformOpts.cliArgs.dotenv
216
+ );
217
+ const contents = fs.readFileSync(dotEnvPath);
218
+ const envVars = dotenv.parse(contents);
219
+ this.extraEnvVars = Object.assign({}, this.extraEnvVars, envVars);
220
+ }
221
+
222
+ if (this.platformOpts.cliArgs['scenario-name']) {
223
+ this.artilleryArgs.push('--scenario-name');
224
+ this.artilleryArgs.push(this.platformOpts.cliArgs['scenario-name']);
225
+ }
226
+
227
+ if (this.platformOpts.cliArgs.config) {
228
+ this.artilleryArgs.push('--config');
229
+ const p = manifest.files.filter(
230
+ (x) => x.orig === this.opts.absoluteConfigPath
231
+ )[0];
232
+ this.artilleryArgs.push(p.noPrefixPosix);
233
+ }
234
+
235
+ if (this.platformOpts.cliArgs.quiet) {
236
+ this.artilleryArgs.push('--quiet');
237
+ }
238
+
239
+ // This needs to be the last argument for now:
240
+ const p = manifest.files.filter(
241
+ (x) => x.orig === this.opts.absoluteScriptPath
242
+ )[0];
243
+ this.artilleryArgs.push(p.noPrefixPosix);
244
+
245
+ const poolSize =
246
+ typeof process.env.CONSUMER_POOL_SIZE !== 'undefined'
247
+ ? parseInt(process.env.CONSUMER_POOL_SIZE, 10)
248
+ : Math.max(Math.ceil(this.count / 25), 5);
249
+
250
+ const consumer = new QueueConsumer(
251
+ { poolSize },
252
+ {
253
+ queueUrl: process.env.AZURE_STORAGE_QUEUE_URL || this.queueUrl,
254
+ handleMessage: async (message) => {
255
+ let payload = null;
256
+ let attributes = null;
257
+ try {
258
+ const result = JSON.parse(message.Body);
259
+ payload = result.payload;
260
+ attributes = result.attributes;
261
+ } catch (parseErr) {
262
+ console.error(parseErr);
263
+ console.error(message.Body);
264
+ }
265
+
266
+ if (process.env.LOG_QUEUE_MESSAGES) {
267
+ console.log(message);
268
+ }
269
+
270
+ if (!payload) {
271
+ throw new Error('AQS message with an empty body');
272
+ }
273
+
274
+ // Handle overflow messages stored in blob storage
275
+ if (payload._overflowRef) {
276
+ try {
277
+ const blobClient =
278
+ this.blobContainerClient.getBlockBlobClient(
279
+ payload._overflowRef
280
+ );
281
+ const downloadResponse = await blobClient.download(0);
282
+ const downloaded = await streamToString(
283
+ downloadResponse.readableStreamBody
284
+ );
285
+ const fullMessage = JSON.parse(downloaded);
286
+ payload = fullMessage.payload;
287
+ attributes = fullMessage.attributes;
288
+ } catch (blobErr) {
289
+ console.error('Failed to fetch worker message:', blobErr);
290
+ throw new Error(
291
+ `Failed to fetch worker message: ${payload._overflowRef}`
292
+ );
293
+ }
294
+ }
295
+
296
+ if (!attributes || !attributes.testId || !attributes.workerId) {
297
+ throw new Error('AQS message with no testId or workerId');
298
+ }
299
+
300
+ if (this.testRunId !== attributes.testId) {
301
+ throw new Error('AQS message for an unknown testId');
302
+ }
303
+
304
+ const workerId = attributes.workerId;
305
+ if (payload.event === 'workerStats') {
306
+ this.events.emit('stats', workerId, payload);
307
+ } else if (payload.event === 'artillery.log') {
308
+ console.log(payload.log);
309
+ } else if (payload.event === 'done') {
310
+ // 'done' handler in Launcher exects the message argument to have an "id" and "report" fields
311
+ payload.id = workerId;
312
+ payload.report = payload.stats;
313
+ this.events.emit('done', workerId, payload);
314
+ } else if (
315
+ payload.event === 'phaseStarted' ||
316
+ payload.event === 'phaseCompleted'
317
+ ) {
318
+ payload.id = workerId;
319
+ this.events.emit(payload.event, workerId, { phase: payload.phase });
320
+ } else if (payload.event === 'workerError') {
321
+ global.artillery.suggestedExitCode = payload.exitCode || 1;
322
+
323
+ if (payload.exitCode !== 21) {
324
+ this.events.emit(payload.event, workerId, {
325
+ id: workerId,
326
+ error: new Error(
327
+ `A worker has exited with an error. Reason: ${payload.reason}`
328
+ ),
329
+ level: 'error',
330
+ aggregatable: false,
331
+ logs: payload.logs
332
+ });
333
+ }
334
+ } else if (payload.event === 'workerReady') {
335
+ this.events.emit(payload.event, workerId);
336
+ this.waitingReadyCount++;
337
+
338
+ // TODO: Do this only for batches of workers with "wait" option set
339
+ if (this.waitingReadyCount === this.count) {
340
+ await this.sendGoSignal();
341
+ }
342
+ } else {
343
+ debug(payload);
344
+ }
345
+ }
346
+ }
347
+ );
348
+
349
+ consumer.on('error', (err) => {
350
+ console.error(err);
351
+ });
352
+
353
+ this.queueConsumer = consumer;
354
+
355
+ const metadata = {
356
+ region: this.region,
357
+ platformConfig: {
358
+ memory: this.memory,
359
+ cpu: this.cpu
360
+ }
361
+ };
362
+ global.artillery.globalEvents.emit('metadata', metadata);
363
+ }
364
+
365
+ getDesiredWorkerCount() {
366
+ return this.platformOpts.count;
367
+ }
368
+
369
+ async startJob() {
370
+ await this.init();
371
+
372
+ console.log('Creating container instances...');
373
+
374
+ // Create & run the leader:
375
+ const { workerId } = await this.createWorker();
376
+ this.workers[workerId] = { workerId };
377
+ await this.runWorker(workerId, { isLeader: true });
378
+
379
+ // Run the rest of the containers we need:
380
+ for (let i = 0; i < this.platformOpts.count - 1; i++) {
381
+ const { workerId } = await this.createWorker();
382
+ this.workers[workerId] = { workerId };
383
+ await this.runWorker(workerId);
384
+
385
+ if (i > 0 && i % 10 === 0) {
386
+ const delayMs =
387
+ Math.floor(
388
+ Math.random() *
389
+ parseInt(process.env.AZURE_LAUNCH_STAGGER_SEC || '5', 10)
390
+ ) * 1000;
391
+ await sleep(delayMs);
392
+ }
393
+ }
394
+
395
+ let instancesCreated = false;
396
+ console.log('Waiting for Azure ACI to create container instances...');
397
+
398
+ const containerInstanceClient = new ContainerInstanceManagementClient(
399
+ new DefaultAzureCredential(),
400
+ this.azureSubscriptionId
401
+ );
402
+
403
+ const provisioningWaitTimeout = new Timeout(WAIT_TIMEOUT * 1000).start();
404
+
405
+ let containerGroupsInTestRun = [];
406
+ while (true) {
407
+ const containerGroupListResult =
408
+ containerInstanceClient.containerGroups.listByResourceGroup(
409
+ this.resourceGroupName
410
+ );
411
+
412
+ containerGroupsInTestRun = [];
413
+ for await (const containerGroup of containerGroupListResult) {
414
+ if (containerGroup.name.indexOf(this.testRunId) > 0) {
415
+ containerGroupsInTestRun.push(containerGroup);
416
+ }
417
+ }
418
+
419
+ const byStatus = containerGroupsInTestRun.reduce((acc, cg) => {
420
+ if (!acc[cg.provisioningState]) {
421
+ acc[cg.provisioningState] = 0;
422
+ }
423
+ acc[cg.provisioningState]++;
424
+ return acc;
425
+ }, {});
426
+
427
+ if (
428
+ (byStatus.Succeeded || 0) + (byStatus.Running || 0) ===
429
+ this.count
430
+ ) {
431
+ instancesCreated = true;
432
+ break;
433
+ }
434
+
435
+ if (provisioningWaitTimeout.timedout()) {
436
+ break;
437
+ }
438
+
439
+ await sleep(10000);
440
+ }
441
+
442
+ if (instancesCreated) {
443
+ console.log(
444
+ 'Container instances have been created. Waiting for workers to start...'
445
+ );
446
+ await this.queueConsumer.start();
447
+ } else {
448
+ console.log('Some containers instances failed to provision');
449
+ console.log('Please see the Azure console for details');
450
+ console.log(
451
+ 'https://portal.azure.com/#view/HubsExtension/BrowseResource/resourceType/Microsoft.ContainerInstance%2FcontainerGroups'
452
+ );
453
+ await global.artillery.shutdown();
454
+ }
455
+ }
456
+
457
+ async shutdown() {
458
+ this.queueConsumer.stop();
459
+ try {
460
+ await this.aqsClient.delete();
461
+ } catch (_err) {}
462
+
463
+ const credential = new DefaultAzureCredential();
464
+
465
+ if (process.env.RETAIN_CONTAINER_INSTANCES !== 'true') {
466
+ const containerInstanceClient = new ContainerInstanceManagementClient(
467
+ credential,
468
+ this.azureSubscriptionId
469
+ );
470
+
471
+ const containerGroupListResult =
472
+ containerInstanceClient.containerGroups.listByResourceGroup(
473
+ this.resourceGroupName
474
+ );
475
+
476
+ for await (const containerGroup of containerGroupListResult) {
477
+ if (containerGroup.name.indexOf(this.testRunId) > 0) {
478
+ try {
479
+ await containerInstanceClient.containerGroups.beginDeleteAndWait(
480
+ this.resourceGroupName,
481
+ containerGroup.name
482
+ );
483
+ } catch (err) {
484
+ console.log(err);
485
+ }
486
+ }
487
+ }
488
+ }
489
+ }
490
+
491
+ async sendGoSignal() {
492
+ const Key = `tests/${this.testRunId}/go.json`;
493
+ const blockBlobClient = this.blobContainerClient.getBlockBlobClient(Key);
494
+ const _res = await blockBlobClient.upload('', 0);
495
+ }
496
+
497
+ async createWorker() {
498
+ const workerId = generateId('worker');
499
+ return { workerId };
500
+ }
501
+
502
+ async runWorker(workerId, opts = { isLeader: false }) {
503
+ const credential = new DefaultAzureCredential();
504
+
505
+ const imageVersion =
506
+ process.env.ARTILLERY_WORKER_IMAGE_VERSION || IMAGE_VERSION;
507
+ const defaultArchitecture = 'x86_64';
508
+ const containerImageURL =
509
+ process.env.WORKER_IMAGE_URL ||
510
+ `public.ecr.aws/d8a4z9o5/artillery-worker:${imageVersion}-${defaultArchitecture}`;
511
+
512
+ const client = new ContainerInstanceManagementClient(
513
+ credential,
514
+ this.azureSubscriptionId
515
+ );
516
+
517
+ const environmentVariables = [
518
+ {
519
+ name: 'WORKER_ID_OVERRIDE',
520
+ value: workerId
521
+ },
522
+ {
523
+ name: 'ARTILLERY_TEST_RUN_ID',
524
+ value: this.testRunId
525
+ },
526
+ // {
527
+ // name: 'DEBUGX',
528
+ // value: 'true',
529
+ // },
530
+ {
531
+ name: 'DEBUG',
532
+ value: 'cloud'
533
+ },
534
+ {
535
+ name: 'IS_LEADER',
536
+ value: String(opts.isLeader)
537
+ },
538
+ {
539
+ name: 'AQS_QUEUE_NAME',
540
+ value: this.queueName
541
+ },
542
+ {
543
+ name: 'AZURE_STORAGE_ACCOUNT',
544
+ value: this.storageAccount
545
+ },
546
+ {
547
+ name: 'AZURE_STORAGE_BLOB_CONTAINER',
548
+ value: this.blobContainerName
549
+ },
550
+ {
551
+ name: 'AZURE_SUBSCRIPTION_ID',
552
+ secureValue: this.azureSubscriptionId
553
+ },
554
+ {
555
+ name: 'AZURE_TENANT_ID',
556
+ secureValue: this.azureTenantId
557
+ },
558
+ {
559
+ name: 'AZURE_CLIENT_ID',
560
+ secureValue: this.azureClientId
561
+ },
562
+ {
563
+ name: 'AZURE_CLIENT_SECRET',
564
+ secureValue: this.azureClientSecret
565
+ },
566
+ {
567
+ name: 'AZURE_STORAGE_AUTH_MODE',
568
+ value: 'login'
569
+ }
570
+ ];
571
+
572
+ if (this.cloudKey) {
573
+ environmentVariables.push({
574
+ name: 'ARTILLERY_CLOUD_API_KEY',
575
+ secureValue: this.cloudKey
576
+ });
577
+ }
578
+
579
+ const cloudEndpoint = process.env.ARTILLERY_CLOUD_ENDPOINT;
580
+ if (cloudEndpoint) {
581
+ environmentVariables.push({
582
+ name: 'ARTILLERY_CLOUD_ENDPOINT',
583
+ secureValue: cloudEndpoint
584
+ });
585
+ }
586
+
587
+ for (const [name, value] of Object.entries(this.extraEnvVars)) {
588
+ environmentVariables.push({ name, value });
589
+ }
590
+
591
+ const containerGroup = {
592
+ location: this.region,
593
+ containers: [
594
+ {
595
+ name: 'artillery-worker',
596
+ image: containerImageURL,
597
+ resources: {
598
+ requests: {
599
+ cpu: this.cpu,
600
+ memoryInGB: this.memory
601
+ }
602
+ },
603
+ command: [
604
+ '/artillery/loadgen-worker',
605
+ '-z',
606
+ 'yes', // yes for Azure
607
+ '-q',
608
+ this.queueUrl,
609
+ '-p',
610
+ this.blobContainerName,
611
+ '-a',
612
+ util.btoa(JSON.stringify(this.artilleryArgs)),
613
+ '-i',
614
+ this.testRunId,
615
+ '-t',
616
+ String(WAIT_TIMEOUT),
617
+ '-d',
618
+ 'NOT_USED_ON_AZURE',
619
+ '-r',
620
+ 'NOT_USED_ON_AZURE'
621
+ ],
622
+ environmentVariables
623
+ }
624
+ ],
625
+ osType: 'Linux',
626
+ restartPolicy: 'Never'
627
+ };
628
+
629
+ if (!this.ts) {
630
+ this.ts = Date.now();
631
+ }
632
+
633
+ const containerGroupName = `artillery-test-${this.ts}-${this.testRunId}-${this.count}`;
634
+ try {
635
+ const containerInstance =
636
+ await client.containerGroups.beginCreateOrUpdate(
637
+ this.resourceGroupName,
638
+ containerGroupName,
639
+ containerGroup
640
+ );
641
+
642
+ this.containerInstances.push(containerInstance);
643
+
644
+ this.count++;
645
+ } catch (err) {
646
+ // TODO: Make this better
647
+ console.log(err.code);
648
+ console.log(err.details?.error?.message);
649
+ throw err;
650
+ }
651
+ }
652
+
653
+ async stopWorker(_workerId) {}
654
+
655
+ async checkLicense() {
656
+ const baseUrl =
657
+ process.env.ARTILLERY_CLOUD_ENDPOINT || 'https://app.artillery.io';
658
+ const res = await request.get(`${baseUrl}/api/user/whoami`, {
659
+ headers: {
660
+ 'x-auth-token': this.cloudKey
661
+ },
662
+ throwHttpErrors: false,
663
+ retry: {
664
+ limit: 3
665
+ }
666
+ });
667
+
668
+ try {
669
+ const body = JSON.parse(res.body);
670
+ const activeOrg = body.activeOrg;
671
+ if (!activeOrg) {
672
+ return false;
673
+ }
674
+ if (!Array.isArray(body.memberships)) {
675
+ return false;
676
+ }
677
+
678
+ const activeMembership = body.memberships.find(
679
+ (membership) => membership.id === activeOrg
680
+ );
681
+
682
+ if (!activeMembership) {
683
+ return false;
684
+ }
685
+
686
+ const plan = activeMembership.plan;
687
+ return plan !== 'developer';
688
+ } catch (_err) {
689
+ return false;
690
+ }
691
+ }
692
+ }
693
+
694
+ module.exports = PlatformAzureACI;