@fishawack/lab-env 5.7.0-beta.2 → 5.7.0-beta.4

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 (37) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/_Test/provision.js +1 -8
  3. package/_Test/prune.js +8 -5
  4. package/bitbucket-pipelines.yml +2 -2
  5. package/cli.js +1 -0
  6. package/commands/create/cmds/dekey.js +13 -9
  7. package/commands/create/cmds/deprovision.js +29 -0
  8. package/commands/create/cmds/key.js +60 -41
  9. package/commands/create/cmds/provision.js +425 -55
  10. package/commands/create/cmds/rekey.js +218 -0
  11. package/commands/create/libs/output-credentials.js +59 -0
  12. package/commands/create/libs/parallel-runner.js +204 -0
  13. package/commands/create/libs/resolve-operator.js +59 -0
  14. package/commands/create/libs/utilities.js +71 -8
  15. package/commands/create/libs/vars.js +70 -86
  16. package/commands/create/services/aws/acm.js +4 -2
  17. package/commands/create/services/aws/cloudfront.js +51 -45
  18. package/commands/create/services/aws/ec2.js +132 -53
  19. package/commands/create/services/aws/elasticache.js +159 -0
  20. package/commands/create/services/aws/elasticbeanstalk.js +141 -60
  21. package/commands/create/services/aws/iam.js +178 -98
  22. package/commands/create/services/aws/index.js +1202 -466
  23. package/commands/create/services/aws/opensearch.js +25 -17
  24. package/commands/create/services/aws/rds.js +22 -34
  25. package/commands/create/services/aws/s3.js +14 -27
  26. package/commands/create/services/aws/secretsmanager.js +8 -15
  27. package/commands/create/services/aws/ses.js +195 -0
  28. package/commands/create/templates/elasticbeanstalk/.ebextensions/laravel/cron.config +45 -0
  29. package/commands/create/templates/elasticbeanstalk/.ebextensions/laravel/queue-worker.config +29 -0
  30. package/commands/create/templates/elasticbeanstalk/.ebextensions/laravel/software.config +12 -0
  31. package/commands/create/templates/elasticbeanstalk/.ebextensions/misc/setvars.config +2 -16
  32. package/commands/create/templates/elasticbeanstalk/.platform/confighooks/postdeploy/rewrite-flush.sh +1 -1
  33. package/commands/create/templates/elasticbeanstalk/.platform/confighooks/postdeploy/setvars.sh +19 -0
  34. package/commands/create/templates/elasticbeanstalk/.platform/hooks/postdeploy/restart_queue.sh +4 -0
  35. package/commands/create/templates/elasticbeanstalk/.platform/hooks/prebuild/setvars.sh +19 -0
  36. package/commands/scan.js +1 -1
  37. package/package.json +5 -2
@@ -20,14 +20,15 @@ const {
20
20
  DescribeInternetGatewaysCommand,
21
21
  } = require("@aws-sdk/client-ec2");
22
22
  const { Spinner, poll } = require("../../libs/utilities");
23
+ const { awsClientConfig } = require("../../libs/vars");
23
24
 
24
- module.exports.getKeyPair = async (KeyName) => {
25
- const client = new EC2Client({});
25
+ const getClient = () => new EC2Client(awsClientConfig);
26
26
 
27
+ module.exports.getKeyPair = async (KeyName) => {
27
28
  let res = await Spinner.prototype.simple(
28
29
  `Retrieving the KeyPair ${KeyName}`,
29
30
  () => {
30
- return client.send(
31
+ return getClient().send(
31
32
  new DescribeKeyPairsCommand({ KeyNames: [KeyName] }),
32
33
  );
33
34
  },
@@ -37,15 +38,13 @@ module.exports.getKeyPair = async (KeyName) => {
37
38
  };
38
39
 
39
40
  module.exports.createRDSSecurityGroup = async (name, tags = []) => {
40
- const client = new EC2Client({});
41
-
42
41
  const groupName = `${name}-rds`;
43
42
 
44
43
  try {
45
44
  const sg = await Spinner.prototype.simple(
46
45
  `Creating security group ${groupName}`,
47
46
  () => {
48
- return client.send(
47
+ return getClient().send(
49
48
  new CreateSecurityGroupCommand({
50
49
  GroupName: groupName,
51
50
  Description: `RDS access for ${name}`,
@@ -84,12 +83,10 @@ module.exports.authorizeRDSIngress = async (
84
83
  rdsSecurityGroupId,
85
84
  sourceSecurityGroupId,
86
85
  ) => {
87
- const client = new EC2Client({});
88
-
89
86
  await Spinner.prototype.simple(
90
87
  `Adding MySQL inbound rule from EB security group`,
91
88
  () => {
92
- return client.send(
89
+ return getClient().send(
93
90
  new AuthorizeSecurityGroupIngressCommand({
94
91
  GroupId: rdsSecurityGroupId,
95
92
  IpPermissions: [
@@ -116,12 +113,10 @@ module.exports.revokeRDSIngress = async (
116
113
  rdsSecurityGroupId,
117
114
  sourceSecurityGroupId,
118
115
  ) => {
119
- const client = new EC2Client({});
120
-
121
116
  await Spinner.prototype.simple(
122
117
  `Removing MySQL inbound rule from RDS security group`,
123
118
  () => {
124
- return client.send(
119
+ return getClient().send(
125
120
  new RevokeSecurityGroupIngressCommand({
126
121
  GroupId: rdsSecurityGroupId,
127
122
  IpPermissions: [
@@ -143,15 +138,13 @@ module.exports.revokeRDSIngress = async (
143
138
  };
144
139
 
145
140
  module.exports.createOpenSearchSecurityGroup = async (name, tags = []) => {
146
- const client = new EC2Client({});
147
-
148
141
  const groupName = `${name}-os`;
149
142
 
150
143
  try {
151
144
  const sg = await Spinner.prototype.simple(
152
145
  `Creating security group ${groupName}`,
153
146
  () => {
154
- return client.send(
147
+ return getClient().send(
155
148
  new CreateSecurityGroupCommand({
156
149
  GroupName: groupName,
157
150
  Description: `OpenSearch access for ${name}`,
@@ -190,12 +183,10 @@ module.exports.authorizeOpenSearchIngress = async (
190
183
  osSecurityGroupId,
191
184
  sourceSecurityGroupId,
192
185
  ) => {
193
- const client = new EC2Client({});
194
-
195
186
  await Spinner.prototype.simple(
196
187
  `Adding HTTPS inbound rule from EB security group`,
197
188
  () => {
198
- return client.send(
189
+ return getClient().send(
199
190
  new AuthorizeSecurityGroupIngressCommand({
200
191
  GroupId: osSecurityGroupId,
201
192
  IpPermissions: [
@@ -222,12 +213,10 @@ module.exports.revokeOpenSearchIngress = async (
222
213
  osSecurityGroupId,
223
214
  sourceSecurityGroupId,
224
215
  ) => {
225
- const client = new EC2Client({});
226
-
227
216
  await Spinner.prototype.simple(
228
217
  `Removing HTTPS inbound rule from OpenSearch security group`,
229
218
  () => {
230
- return client.send(
219
+ return getClient().send(
231
220
  new RevokeSecurityGroupIngressCommand({
232
221
  GroupId: osSecurityGroupId,
233
222
  IpPermissions: [
@@ -248,13 +237,111 @@ module.exports.revokeOpenSearchIngress = async (
248
237
  );
249
238
  };
250
239
 
251
- module.exports.findSecurityGroupByTag = async (filters = []) => {
252
- const client = new EC2Client({});
240
+ module.exports.createCacheSecurityGroup = async (name, tags = []) => {
241
+ const groupName = `${name}-cache`;
253
242
 
243
+ try {
244
+ const sg = await Spinner.prototype.simple(
245
+ `Creating security group ${groupName}`,
246
+ () => {
247
+ return getClient().send(
248
+ new CreateSecurityGroupCommand({
249
+ GroupName: groupName,
250
+ Description: `ElastiCache access for ${name}`,
251
+ TagSpecifications: [
252
+ {
253
+ ResourceType: "security-group",
254
+ Tags: [
255
+ {
256
+ Key: "Name",
257
+ Value: groupName,
258
+ },
259
+ {
260
+ Key: "client",
261
+ Value: process.env.AWS_PROFILE,
262
+ },
263
+ ].concat(tags),
264
+ },
265
+ ],
266
+ }),
267
+ );
268
+ },
269
+ );
270
+
271
+ return sg.GroupId;
272
+ } catch (e) {
273
+ if (e.Code === "InvalidGroup.Duplicate") {
274
+ const existing =
275
+ await module.exports.findSecurityGroupByName(groupName);
276
+ return existing.GroupId;
277
+ }
278
+ throw e;
279
+ }
280
+ };
281
+
282
+ module.exports.authorizeCacheIngress = async (
283
+ cacheSecurityGroupId,
284
+ sourceSecurityGroupId,
285
+ ) => {
286
+ await Spinner.prototype.simple(
287
+ `Adding Redis inbound rule from EB security group`,
288
+ () => {
289
+ return getClient().send(
290
+ new AuthorizeSecurityGroupIngressCommand({
291
+ GroupId: cacheSecurityGroupId,
292
+ IpPermissions: [
293
+ {
294
+ IpProtocol: "tcp",
295
+ FromPort: 6380,
296
+ ToPort: 6380,
297
+ UserIdGroupPairs: [
298
+ {
299
+ GroupId: sourceSecurityGroupId,
300
+ Description:
301
+ "Redis/Valkey access for EB instances",
302
+ },
303
+ ],
304
+ },
305
+ ],
306
+ }),
307
+ );
308
+ },
309
+ );
310
+ };
311
+
312
+ module.exports.revokeCacheIngress = async (
313
+ cacheSecurityGroupId,
314
+ sourceSecurityGroupId,
315
+ ) => {
316
+ await Spinner.prototype.simple(
317
+ `Removing Redis inbound rule from cache security group`,
318
+ () => {
319
+ return getClient().send(
320
+ new RevokeSecurityGroupIngressCommand({
321
+ GroupId: cacheSecurityGroupId,
322
+ IpPermissions: [
323
+ {
324
+ IpProtocol: "tcp",
325
+ FromPort: 6380,
326
+ ToPort: 6380,
327
+ UserIdGroupPairs: [
328
+ {
329
+ GroupId: sourceSecurityGroupId,
330
+ },
331
+ ],
332
+ },
333
+ ],
334
+ }),
335
+ );
336
+ },
337
+ );
338
+ };
339
+
340
+ module.exports.findSecurityGroupByTag = async (filters = []) => {
254
341
  const res = await Spinner.prototype.simple(
255
342
  `Looking up security group by filters`,
256
343
  () => {
257
- return client.send(
344
+ return getClient().send(
258
345
  new DescribeSecurityGroupsCommand({
259
346
  Filters: filters,
260
347
  }),
@@ -266,12 +353,10 @@ module.exports.findSecurityGroupByTag = async (filters = []) => {
266
353
  };
267
354
 
268
355
  module.exports.findSecurityGroupByName = async (name) => {
269
- const client = new EC2Client({});
270
-
271
356
  const res = await Spinner.prototype.simple(
272
357
  `Looking up security group ${name}`,
273
358
  () => {
274
- return client.send(
359
+ return getClient().send(
275
360
  new DescribeSecurityGroupsCommand({
276
361
  Filters: [{ Name: "group-name", Values: [name] }],
277
362
  }),
@@ -283,23 +368,21 @@ module.exports.findSecurityGroupByName = async (name) => {
283
368
  };
284
369
 
285
370
  module.exports.deleteSecurityGroup = async (groupId) => {
286
- const client = new EC2Client({});
287
-
288
371
  await Spinner.prototype.simple(`Deleting security group ${groupId}`, () => {
289
- return client.send(
372
+ return getClient().send(
290
373
  new DeleteSecurityGroupCommand({ GroupId: groupId }),
291
374
  );
292
375
  });
293
376
  };
294
377
 
295
- const discoverPublicSubnets = async (client, vpcId, excludeIds, azs) => {
296
- const { Subnets: allSubnets } = await client.send(
378
+ const discoverPublicSubnets = async (vpcId, excludeIds, azs) => {
379
+ const { Subnets: allSubnets } = await getClient().send(
297
380
  new DescribeSubnetsCommand({
298
381
  Filters: [{ Name: "vpc-id", Values: [vpcId] }],
299
382
  }),
300
383
  );
301
384
 
302
- const { RouteTables } = await client.send(
385
+ const { RouteTables } = await getClient().send(
303
386
  new DescribeRouteTablesCommand({
304
387
  Filters: [{ Name: "vpc-id", Values: [vpcId] }],
305
388
  }),
@@ -334,10 +417,8 @@ const discoverPublicSubnets = async (client, vpcId, excludeIds, azs) => {
334
417
  };
335
418
 
336
419
  module.exports.ensurePrivateSubnets = async () => {
337
- const client = new EC2Client({});
338
-
339
420
  // Get default VPC
340
- const { Vpcs } = await client.send(
421
+ const { Vpcs } = await getClient().send(
341
422
  new DescribeVpcsCommand({
342
423
  Filters: [{ Name: "isDefault", Values: ["true"] }],
343
424
  }),
@@ -351,7 +432,7 @@ module.exports.ensurePrivateSubnets = async () => {
351
432
  const vpcId = vpc.VpcId;
352
433
 
353
434
  // Preflight: check all private networking resources
354
- const { Subnets: existingSubnets } = await client.send(
435
+ const { Subnets: existingSubnets } = await getClient().send(
355
436
  new DescribeSubnetsCommand({
356
437
  Filters: [
357
438
  { Name: "vpc-id", Values: [vpcId] },
@@ -360,7 +441,7 @@ module.exports.ensurePrivateSubnets = async () => {
360
441
  }),
361
442
  );
362
443
 
363
- const { NatGateways: allNats } = await client.send(
444
+ const { NatGateways: allNats } = await getClient().send(
364
445
  new DescribeNatGatewaysCommand({
365
446
  Filter: [
366
447
  { Name: "vpc-id", Values: [vpcId] },
@@ -372,7 +453,7 @@ module.exports.ensurePrivateSubnets = async () => {
372
453
  (n) => n.State !== "deleted" && n.State !== "deleting",
373
454
  );
374
455
 
375
- const { RouteTables: existingRtbs } = await client.send(
456
+ const { RouteTables: existingRtbs } = await getClient().send(
376
457
  new DescribeRouteTablesCommand({
377
458
  Filters: [
378
459
  { Name: "vpc-id", Values: [vpcId] },
@@ -409,7 +490,6 @@ module.exports.ensurePrivateSubnets = async () => {
409
490
  // All resources exist — nothing to do
410
491
  if (!needsSubnets && !needsNats && !needsRtbs) {
411
492
  const publicSubnetIds = await discoverPublicSubnets(
412
- client,
413
493
  vpcId,
414
494
  existingSubnets.map((s) => s.SubnetId),
415
495
  existingSubnets.slice(0, 2).map((s) => s.AvailabilityZone),
@@ -430,7 +510,7 @@ module.exports.ensurePrivateSubnets = async () => {
430
510
  azs = existingSubnets.slice(0, 2).map((s) => s.AvailabilityZone);
431
511
  } else {
432
512
  spinner.ora.text = "Discovering availability zones";
433
- const { AvailabilityZones } = await client.send(
513
+ const { AvailabilityZones } = await getClient().send(
434
514
  new DescribeAvailabilityZonesCommand({
435
515
  Filters: [{ Name: "state", Values: ["available"] }],
436
516
  }),
@@ -446,7 +526,7 @@ module.exports.ensurePrivateSubnets = async () => {
446
526
  let privateSubnetIds;
447
527
  if (needsSubnets) {
448
528
  spinner.ora.text = "Discovering available CIDR blocks";
449
- const { Subnets: allSubnets } = await client.send(
529
+ const { Subnets: allSubnets } = await getClient().send(
450
530
  new DescribeSubnetsCommand({
451
531
  Filters: [{ Name: "vpc-id", Values: [vpcId] }],
452
532
  }),
@@ -504,7 +584,7 @@ module.exports.ensurePrivateSubnets = async () => {
504
584
  privateSubnetIds = [];
505
585
  for (let i = 0; i < 2; i++) {
506
586
  spinner.ora.text = `Creating private subnet in ${azs[i]}`;
507
- const subnet = await client.send(
587
+ const subnet = await getClient().send(
508
588
  new CreateSubnetCommand({
509
589
  VpcId: vpcId,
510
590
  CidrBlock: availableBlocks[i],
@@ -534,7 +614,7 @@ module.exports.ensurePrivateSubnets = async () => {
534
614
  if (needsNats) {
535
615
  // Find public subnets per AZ
536
616
  spinner.ora.text = "Identifying public subnets";
537
- const { InternetGateways } = await client.send(
617
+ const { InternetGateways } = await getClient().send(
538
618
  new DescribeInternetGatewaysCommand({
539
619
  Filters: [{ Name: "attachment.vpc-id", Values: [vpcId] }],
540
620
  }),
@@ -544,13 +624,13 @@ module.exports.ensurePrivateSubnets = async () => {
544
624
  throw new Error("No Internet Gateway found on default VPC");
545
625
  }
546
626
 
547
- const { Subnets: vpcSubnets } = await client.send(
627
+ const { Subnets: vpcSubnets } = await getClient().send(
548
628
  new DescribeSubnetsCommand({
549
629
  Filters: [{ Name: "vpc-id", Values: [vpcId] }],
550
630
  }),
551
631
  );
552
632
 
553
- const { RouteTables } = await client.send(
633
+ const { RouteTables } = await getClient().send(
554
634
  new DescribeRouteTablesCommand({
555
635
  Filters: [{ Name: "vpc-id", Values: [vpcId] }],
556
636
  }),
@@ -597,7 +677,7 @@ module.exports.ensurePrivateSubnets = async () => {
597
677
  natGatewayIds = [];
598
678
  for (let i = 0; i < 2; i++) {
599
679
  spinner.ora.text = `Allocating Elastic IP for NAT gateway in ${azs[i]}`;
600
- const eip = await client.send(
680
+ const eip = await getClient().send(
601
681
  new AllocateAddressCommand({
602
682
  Domain: "vpc",
603
683
  TagSpecifications: [
@@ -616,7 +696,7 @@ module.exports.ensurePrivateSubnets = async () => {
616
696
  );
617
697
 
618
698
  spinner.ora.text = `Creating NAT gateway in ${azs[i]}`;
619
- const nat = await client.send(
699
+ const nat = await getClient().send(
620
700
  new CreateNatGatewayCommand({
621
701
  SubnetId: publicSubnetsByAz[azs[i]],
622
702
  AllocationId: eip.AllocationId,
@@ -642,7 +722,7 @@ module.exports.ensurePrivateSubnets = async () => {
642
722
  spinner.ora.text = `Waiting for NAT gateway in ${azs[i]} to become available`;
643
723
  await poll(
644
724
  async () => {
645
- const res = await client.send(
725
+ const res = await getClient().send(
646
726
  new DescribeNatGatewaysCommand({
647
727
  NatGatewayIds: [natGatewayIds[i]],
648
728
  }),
@@ -660,7 +740,7 @@ module.exports.ensurePrivateSubnets = async () => {
660
740
  if (needsRtbs) {
661
741
  for (let i = 0; i < 2; i++) {
662
742
  spinner.ora.text = `Creating route table for ${azs[i]}`;
663
- const rt = await client.send(
743
+ const rt = await getClient().send(
664
744
  new CreateRouteTableCommand({
665
745
  VpcId: vpcId,
666
746
  TagSpecifications: [
@@ -679,7 +759,7 @@ module.exports.ensurePrivateSubnets = async () => {
679
759
  );
680
760
 
681
761
  spinner.ora.text = `Adding NAT gateway route for ${azs[i]}`;
682
- await client.send(
762
+ await getClient().send(
683
763
  new CreateRouteCommand({
684
764
  RouteTableId: rt.RouteTable.RouteTableId,
685
765
  DestinationCidrBlock: "0.0.0.0/0",
@@ -688,7 +768,7 @@ module.exports.ensurePrivateSubnets = async () => {
688
768
  );
689
769
 
690
770
  spinner.ora.text = `Associating route table with private subnet in ${azs[i]}`;
691
- await client.send(
771
+ await getClient().send(
692
772
  new AssociateRouteTableCommand({
693
773
  RouteTableId: rt.RouteTable.RouteTableId,
694
774
  SubnetId: privateSubnetIds[i],
@@ -700,7 +780,6 @@ module.exports.ensurePrivateSubnets = async () => {
700
780
  spinner.ora.succeed("Private subnets configured");
701
781
 
702
782
  const publicSubnetIds = await discoverPublicSubnets(
703
- client,
704
783
  vpcId,
705
784
  privateSubnetIds,
706
785
  azs,
@@ -0,0 +1,159 @@
1
+ const {
2
+ ElastiCacheClient,
3
+ CreateCacheSubnetGroupCommand,
4
+ CreateReplicationGroupCommand,
5
+ DeleteReplicationGroupCommand,
6
+ DescribeCacheSubnetGroupsCommand,
7
+ DescribeReplicationGroupsCommand,
8
+ } = require("@aws-sdk/client-elasticache");
9
+ const { Spinner, poll } = require("../../libs/utilities");
10
+ const { awsClientConfig } = require("../../libs/vars");
11
+
12
+ const getClient = () => new ElastiCacheClient(awsClientConfig);
13
+
14
+ module.exports.ensureCacheSubnetGroup = async (privateSubnetIds) => {
15
+ const name = "fw-auto-private-cache-subnet-group";
16
+
17
+ try {
18
+ await getClient().send(
19
+ new DescribeCacheSubnetGroupsCommand({
20
+ CacheSubnetGroupName: name,
21
+ }),
22
+ );
23
+
24
+ return name;
25
+ } catch (e) {
26
+ if (e.name !== "CacheSubnetGroupNotFoundFault") {
27
+ throw e;
28
+ }
29
+ }
30
+
31
+ await Spinner.prototype.simple(
32
+ `Creating cache subnet group ${name}`,
33
+ () => {
34
+ return getClient().send(
35
+ new CreateCacheSubnetGroupCommand({
36
+ CacheSubnetGroupName: name,
37
+ CacheSubnetGroupDescription:
38
+ "Private subnets for lab-env ElastiCache clusters",
39
+ SubnetIds: privateSubnetIds,
40
+ }),
41
+ );
42
+ },
43
+ );
44
+
45
+ return name;
46
+ };
47
+
48
+ module.exports.createCacheCluster = async (
49
+ name,
50
+ securityGroupId,
51
+ subnetGroupName,
52
+ authToken,
53
+ tags = [],
54
+ ) => {
55
+ const res = await Spinner.prototype.simple(
56
+ `Creating ElastiCache cluster ${name}`,
57
+ () => {
58
+ return getClient().send(
59
+ new CreateReplicationGroupCommand({
60
+ ReplicationGroupId: name,
61
+ ReplicationGroupDescription: `lab-env Valkey cluster for ${name}`,
62
+ Engine: "valkey",
63
+ EngineVersion: "8.0",
64
+ CacheNodeType: "cache.t3.small",
65
+ NumCacheClusters: 1,
66
+ Port: 6380,
67
+ TransitEncryptionEnabled: true,
68
+ AuthToken: authToken,
69
+ CacheSubnetGroupName: subnetGroupName,
70
+ SecurityGroupIds: [securityGroupId],
71
+ Tags: [
72
+ { Key: "client", Value: process.env.AWS_PROFILE },
73
+ ].concat(tags),
74
+ }),
75
+ );
76
+ },
77
+ );
78
+
79
+ return res;
80
+ };
81
+
82
+ module.exports.waitForCacheCluster = async (name) => {
83
+ const res = await Spinner.prototype.simple(
84
+ `Waiting for ElastiCache cluster to become available`,
85
+ (spinner) => {
86
+ return poll(
87
+ async () => {
88
+ const { ReplicationGroups } = await getClient().send(
89
+ new DescribeReplicationGroupsCommand({
90
+ ReplicationGroupId: name,
91
+ }),
92
+ );
93
+ return ReplicationGroups[0];
94
+ },
95
+ (group) => group.Status !== "available",
96
+ undefined,
97
+ spinner,
98
+ );
99
+ },
100
+ );
101
+
102
+ return res;
103
+ };
104
+
105
+ module.exports.describeCacheCluster = async (name) => {
106
+ const res = await Spinner.prototype.simple(
107
+ `Describing ElastiCache cluster ${name}`,
108
+ () => {
109
+ return getClient().send(
110
+ new DescribeReplicationGroupsCommand({
111
+ ReplicationGroupId: name,
112
+ }),
113
+ );
114
+ },
115
+ );
116
+
117
+ return res.ReplicationGroups[0];
118
+ };
119
+
120
+ module.exports.deleteCacheCluster = async (name) => {
121
+ await Spinner.prototype.simple(
122
+ `Deleting ElastiCache cluster ${name}`,
123
+ () => {
124
+ return getClient().send(
125
+ new DeleteReplicationGroupCommand({
126
+ ReplicationGroupId: name,
127
+ }),
128
+ );
129
+ },
130
+ );
131
+ };
132
+
133
+ module.exports.waitForCacheDeletion = async (name) => {
134
+ await Spinner.prototype.simple(
135
+ `Waiting for ElastiCache cluster to be deleted`,
136
+ (spinner) => {
137
+ return poll(
138
+ async () => {
139
+ try {
140
+ const { ReplicationGroups } = await getClient().send(
141
+ new DescribeReplicationGroupsCommand({
142
+ ReplicationGroupId: name,
143
+ }),
144
+ );
145
+ return ReplicationGroups[0];
146
+ } catch (e) {
147
+ if (e.name === "ReplicationGroupNotFoundFault") {
148
+ return { deleted: true };
149
+ }
150
+ throw e;
151
+ }
152
+ },
153
+ (group) => !group.deleted,
154
+ undefined,
155
+ spinner,
156
+ );
157
+ },
158
+ );
159
+ };