@fishawack/lab-env 5.7.0-beta.1 → 5.7.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/_Test/provision.js +3 -1
- package/commands/create/cmds/deprovision.js +7 -0
- package/commands/create/cmds/provision.js +182 -48
- package/commands/create/cmds/sync-secrets.js +2 -1
- package/commands/create/libs/utilities.js +2 -0
- package/commands/create/libs/vars.js +53 -53
- package/commands/create/services/aws/acm.js +59 -0
- package/commands/create/services/aws/ec2.js +576 -28
- package/commands/create/services/aws/elasticbeanstalk.js +16 -0
- package/commands/create/services/aws/iam.js +254 -0
- package/commands/create/services/aws/index.js +462 -67
- package/commands/create/services/aws/opensearch.js +147 -0
- package/commands/create/services/aws/rds.js +38 -0
- package/commands/create/services/aws/s3.js +41 -7
- package/commands/create/services/aws/secretsmanager.js +16 -0
- package/package.json +3 -1
|
@@ -10,9 +10,13 @@ const {
|
|
|
10
10
|
SecretsManagerClient,
|
|
11
11
|
DescribeSecretCommand,
|
|
12
12
|
} = require("@aws-sdk/client-secrets-manager");
|
|
13
|
+
const {
|
|
14
|
+
OpenSearchClient,
|
|
15
|
+
DescribeDomainCommand,
|
|
16
|
+
} = require("@aws-sdk/client-opensearch");
|
|
13
17
|
|
|
14
18
|
const { nameSafe, Spinner } = require("../../libs/utilities.js");
|
|
15
|
-
const { eb } = require("../../libs/vars.js");
|
|
19
|
+
const { eb, secretGroups } = require("../../libs/vars.js");
|
|
16
20
|
|
|
17
21
|
module.exports.s3 = require("./s3.js");
|
|
18
22
|
module.exports.cloudfront = require("./cloudfront.js");
|
|
@@ -20,7 +24,9 @@ module.exports.iam = require("./iam.js");
|
|
|
20
24
|
module.exports.elasticbeanstalk = require("./elasticbeanstalk.js");
|
|
21
25
|
module.exports.ec2 = require("./ec2.js");
|
|
22
26
|
module.exports.rds = require("./rds.js");
|
|
27
|
+
module.exports.opensearch = require("./opensearch.js");
|
|
23
28
|
module.exports.secretsmanager = require("./secretsmanager.js");
|
|
29
|
+
module.exports.acm = require("./acm.js");
|
|
24
30
|
|
|
25
31
|
module.exports.slug = (repo, client, branch, service = "s3") =>
|
|
26
32
|
nameSafe(`${branch}-${repo}-${client}`, service);
|
|
@@ -29,13 +35,14 @@ module.exports.fullstackPreflight = async (
|
|
|
29
35
|
name,
|
|
30
36
|
repo,
|
|
31
37
|
branch,
|
|
32
|
-
{ storage = false, database = false } = {},
|
|
38
|
+
{ storage = false, database = false, opensearch = false } = {},
|
|
33
39
|
) => {
|
|
34
|
-
const conflicts = [];
|
|
35
40
|
const existing = {
|
|
41
|
+
environment: false,
|
|
36
42
|
storage: false,
|
|
37
43
|
database: false,
|
|
38
|
-
|
|
44
|
+
opensearch: false,
|
|
45
|
+
secrets: { app: null, storage: null, database: null, opensearch: null },
|
|
39
46
|
};
|
|
40
47
|
|
|
41
48
|
const spinner = new Spinner("Running pre-flight checks", true);
|
|
@@ -50,9 +57,7 @@ module.exports.fullstackPreflight = async (
|
|
|
50
57
|
(e) => e.Status !== "Terminated",
|
|
51
58
|
);
|
|
52
59
|
if (active.length) {
|
|
53
|
-
|
|
54
|
-
`Elastic Beanstalk environment "${name}" already exists (status: ${active[0].Status})`,
|
|
55
|
-
);
|
|
60
|
+
existing.environment = active[0];
|
|
56
61
|
}
|
|
57
62
|
} catch {
|
|
58
63
|
/* does not exist */
|
|
@@ -105,8 +110,29 @@ module.exports.fullstackPreflight = async (
|
|
|
105
110
|
}
|
|
106
111
|
}
|
|
107
112
|
|
|
113
|
+
// Check OpenSearch
|
|
114
|
+
if (opensearch) {
|
|
115
|
+
const osSlug = module.exports.slug(
|
|
116
|
+
repo,
|
|
117
|
+
process.env.AWS_PROFILE,
|
|
118
|
+
branch,
|
|
119
|
+
"os",
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
spinner.ora.text = "Checking for existing OpenSearch domain";
|
|
123
|
+
try {
|
|
124
|
+
const { DomainStatus } = await new OpenSearchClient({}).send(
|
|
125
|
+
new DescribeDomainCommand({ DomainName: osSlug }),
|
|
126
|
+
);
|
|
127
|
+
if (DomainStatus && !DomainStatus.Deleted) {
|
|
128
|
+
existing.opensearch = true;
|
|
129
|
+
}
|
|
130
|
+
} catch {
|
|
131
|
+
/* does not exist */
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
108
135
|
// Check for existing secrets
|
|
109
|
-
const secretGroups = ["app", "storage", "database"];
|
|
110
136
|
for (const group of secretGroups) {
|
|
111
137
|
spinner.ora.text = `Checking for existing ${group} secret`;
|
|
112
138
|
try {
|
|
@@ -125,7 +151,7 @@ module.exports.fullstackPreflight = async (
|
|
|
125
151
|
throw e;
|
|
126
152
|
}
|
|
127
153
|
|
|
128
|
-
return {
|
|
154
|
+
return { existing };
|
|
129
155
|
};
|
|
130
156
|
|
|
131
157
|
module.exports.clients = [
|
|
@@ -282,49 +308,104 @@ module.exports.fullstack = async (
|
|
|
282
308
|
availability,
|
|
283
309
|
database = false,
|
|
284
310
|
storage = false,
|
|
285
|
-
|
|
311
|
+
opensearch = false,
|
|
312
|
+
existingSecrets = {
|
|
313
|
+
app: null,
|
|
314
|
+
storage: null,
|
|
315
|
+
database: null,
|
|
316
|
+
opensearch: null,
|
|
317
|
+
},
|
|
286
318
|
snapshot = null,
|
|
319
|
+
elbScheme = "public",
|
|
320
|
+
existingEnvironment = false,
|
|
321
|
+
existingResources = {
|
|
322
|
+
storage: false,
|
|
323
|
+
database: false,
|
|
324
|
+
opensearch: false,
|
|
325
|
+
},
|
|
326
|
+
appName,
|
|
327
|
+
appDomain,
|
|
328
|
+
certArn,
|
|
287
329
|
) => {
|
|
288
330
|
const { platform, language } = eb.configurations[framework];
|
|
331
|
+
const isLaravel = framework === "laravel";
|
|
332
|
+
|
|
333
|
+
// Always compute s3Slug if storage is requested or pre-exists
|
|
334
|
+
// (needed for instance profile S3 access even when bucket creation is skipped)
|
|
335
|
+
const s3Slug =
|
|
336
|
+
storage || existingSecrets.storage
|
|
337
|
+
? module.exports.slug(repo, process.env.AWS_PROFILE, branch, "s3")
|
|
338
|
+
: undefined;
|
|
339
|
+
|
|
340
|
+
// Always compute osSlug if opensearch is requested or pre-exists
|
|
341
|
+
const osSlug =
|
|
342
|
+
opensearch || existingSecrets.opensearch
|
|
343
|
+
? module.exports.slug(repo, process.env.AWS_PROFILE, branch, "os")
|
|
344
|
+
: undefined;
|
|
289
345
|
|
|
290
|
-
let s3Slug;
|
|
291
346
|
let s3Data = {};
|
|
292
347
|
|
|
293
|
-
if (storage) {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
348
|
+
if (storage && !existingResources.storage) {
|
|
349
|
+
if (isLaravel) {
|
|
350
|
+
await module.exports.s3.createS3Bucket(s3Slug, tags, {
|
|
351
|
+
BlockPublicAcls: true,
|
|
352
|
+
IgnorePublicAcls: true,
|
|
353
|
+
BlockPublicPolicy: false,
|
|
354
|
+
RestrictPublicBuckets: false,
|
|
355
|
+
});
|
|
300
356
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
s3Slug,
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
357
|
+
await module.exports.s3.setPublicReadPolicy(s3Slug, "public/*");
|
|
358
|
+
} else {
|
|
359
|
+
await module.exports.iam.createIAMUser(s3Slug, tags);
|
|
360
|
+
await module.exports.iam.attachIAMPolicy(
|
|
361
|
+
s3Slug,
|
|
362
|
+
"arn:aws:iam::aws:policy/AmazonS3FullAccess",
|
|
363
|
+
);
|
|
364
|
+
const {
|
|
365
|
+
AccessKey: { AccessKeyId },
|
|
366
|
+
AccessKey: { SecretAccessKey },
|
|
367
|
+
} = await module.exports.iam.createAccessKey(s3Slug);
|
|
310
368
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
369
|
+
await module.exports.s3.createS3Bucket(
|
|
370
|
+
s3Slug,
|
|
371
|
+
tags,
|
|
372
|
+
false,
|
|
373
|
+
"BucketOwnerPreferred",
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
s3Data = { s3Slug, AccessKeyId, SecretAccessKey };
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (storage && !s3Data.s3Slug) {
|
|
381
|
+
s3Data = { s3Slug };
|
|
382
|
+
}
|
|
317
383
|
|
|
318
|
-
|
|
384
|
+
let instanceProfileName;
|
|
385
|
+
let opensearchDomainArn;
|
|
386
|
+
|
|
387
|
+
if (osSlug) {
|
|
388
|
+
opensearchDomainArn = `arn:aws:es:${process.env.AWS_REGION}:*:domain/${osSlug}`;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (isLaravel) {
|
|
392
|
+
instanceProfileName =
|
|
393
|
+
await module.exports.iam.ensureEnvironmentInstanceProfile(
|
|
394
|
+
name,
|
|
395
|
+
s3Slug,
|
|
396
|
+
opensearchDomainArn,
|
|
397
|
+
);
|
|
398
|
+
} else {
|
|
399
|
+
await module.exports.iam.ensureEBInstanceProfileExists();
|
|
319
400
|
}
|
|
320
401
|
|
|
321
|
-
await module.exports.iam.ensureEBInstanceProfileExists();
|
|
322
402
|
await module.exports.iam.ensureEBManagedUpdateProfileExists();
|
|
403
|
+
const vpcData = await module.exports.ec2.ensurePrivateSubnets();
|
|
323
404
|
|
|
324
405
|
// Create decoupled RDS instance if database is required
|
|
325
406
|
let rdsData = {};
|
|
326
407
|
|
|
327
|
-
if (database) {
|
|
408
|
+
if (database && !existingResources.database) {
|
|
328
409
|
const rdsSlug = module.exports.slug(
|
|
329
410
|
repo,
|
|
330
411
|
process.env.AWS_PROFILE,
|
|
@@ -342,12 +423,17 @@ module.exports.fullstack = async (
|
|
|
342
423
|
const rdsSecurityGroupId =
|
|
343
424
|
await module.exports.ec2.createRDSSecurityGroup(rdsSlug, tags);
|
|
344
425
|
|
|
426
|
+
const dbSubnetGroupName = await module.exports.rds.ensureDBSubnetGroup(
|
|
427
|
+
vpcData.privateSubnetIds,
|
|
428
|
+
);
|
|
429
|
+
|
|
345
430
|
if (snapshot) {
|
|
346
431
|
await module.exports.rds.restoreRDSInstanceFromSnapshot(
|
|
347
432
|
rdsSlug,
|
|
348
433
|
snapshot,
|
|
349
434
|
rdsSecurityGroupId,
|
|
350
435
|
tags,
|
|
436
|
+
dbSubnetGroupName,
|
|
351
437
|
);
|
|
352
438
|
|
|
353
439
|
await module.exports.rds.waitForRDSInstance(rdsSlug);
|
|
@@ -359,6 +445,7 @@ module.exports.fullstack = async (
|
|
|
359
445
|
tags,
|
|
360
446
|
dbPassword,
|
|
361
447
|
rdsSecurityGroupId,
|
|
448
|
+
dbSubnetGroupName,
|
|
362
449
|
);
|
|
363
450
|
}
|
|
364
451
|
|
|
@@ -370,6 +457,92 @@ module.exports.fullstack = async (
|
|
|
370
457
|
RDS_PORT: String(rdsInstance.Endpoint.Port),
|
|
371
458
|
RDS_PASSWORD: dbPassword,
|
|
372
459
|
};
|
|
460
|
+
} else if (
|
|
461
|
+
database &&
|
|
462
|
+
existingResources.database &&
|
|
463
|
+
!existingSecrets.database
|
|
464
|
+
) {
|
|
465
|
+
// Recovery: RDS exists but secret was lost — reset password and populate data
|
|
466
|
+
const rdsSlug = module.exports.slug(
|
|
467
|
+
repo,
|
|
468
|
+
process.env.AWS_PROFILE,
|
|
469
|
+
branch,
|
|
470
|
+
"rds",
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
const dbPassword = generator.generate({
|
|
474
|
+
length: 10,
|
|
475
|
+
numbers: true,
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
await module.exports.rds.modifyRDSPassword(rdsSlug, dbPassword);
|
|
479
|
+
const rdsInstance =
|
|
480
|
+
await module.exports.rds.waitForRDSInstance(rdsSlug);
|
|
481
|
+
|
|
482
|
+
rdsData = {
|
|
483
|
+
RDS_HOSTNAME: rdsInstance.Endpoint.Address,
|
|
484
|
+
RDS_PORT: String(rdsInstance.Endpoint.Port),
|
|
485
|
+
RDS_PASSWORD: dbPassword,
|
|
486
|
+
};
|
|
487
|
+
} else if (
|
|
488
|
+
database &&
|
|
489
|
+
existingResources.database &&
|
|
490
|
+
existingSecrets.database
|
|
491
|
+
) {
|
|
492
|
+
// Existing RDS + secret — read secret values for template interpolation
|
|
493
|
+
const secretValue = await module.exports.secretsmanager.getSecretValue(
|
|
494
|
+
`${name}-database`,
|
|
495
|
+
);
|
|
496
|
+
rdsData = {
|
|
497
|
+
RDS_HOSTNAME: secretValue.RDS_HOSTNAME,
|
|
498
|
+
RDS_PORT: secretValue.RDS_PORT,
|
|
499
|
+
RDS_PASSWORD: secretValue.RDS_PASSWORD,
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Create decoupled OpenSearch domain if requested
|
|
504
|
+
let osData = {};
|
|
505
|
+
|
|
506
|
+
if (opensearch && !existingResources.opensearch) {
|
|
507
|
+
await module.exports.iam.ensureOpenSearchServiceRole();
|
|
508
|
+
|
|
509
|
+
const roleArn = instanceProfileName
|
|
510
|
+
? (await module.exports.iam.getRole(instanceProfileName)).Role.Arn
|
|
511
|
+
: undefined;
|
|
512
|
+
|
|
513
|
+
const osSecurityGroupId =
|
|
514
|
+
await module.exports.ec2.createOpenSearchSecurityGroup(
|
|
515
|
+
osSlug,
|
|
516
|
+
tags,
|
|
517
|
+
);
|
|
518
|
+
|
|
519
|
+
await module.exports.opensearch.createOpenSearchDomain(
|
|
520
|
+
osSlug,
|
|
521
|
+
osSecurityGroupId,
|
|
522
|
+
vpcData.privateSubnetIds,
|
|
523
|
+
roleArn,
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
const osDomain =
|
|
527
|
+
await module.exports.opensearch.waitForOpenSearchDomain(osSlug);
|
|
528
|
+
|
|
529
|
+
const osEndpoint =
|
|
530
|
+
osDomain.Endpoint || (osDomain.Endpoints && osDomain.Endpoints.vpc);
|
|
531
|
+
|
|
532
|
+
osData = {
|
|
533
|
+
SEARCH_HOST: `https://${osEndpoint}`,
|
|
534
|
+
};
|
|
535
|
+
} else if (opensearch && existingResources.opensearch) {
|
|
536
|
+
// Existing domain — describe to populate osData for secret payloads
|
|
537
|
+
const osDomain =
|
|
538
|
+
await module.exports.opensearch.describeOpenSearchDomain(osSlug);
|
|
539
|
+
|
|
540
|
+
const osEndpoint =
|
|
541
|
+
osDomain.Endpoint || (osDomain.Endpoints && osDomain.Endpoints.vpc);
|
|
542
|
+
|
|
543
|
+
osData = {
|
|
544
|
+
SEARCH_HOST: `https://${osEndpoint}`,
|
|
545
|
+
};
|
|
373
546
|
}
|
|
374
547
|
|
|
375
548
|
try {
|
|
@@ -395,15 +568,51 @@ module.exports.fullstack = async (
|
|
|
395
568
|
|
|
396
569
|
// Build secret payloads and create/reuse secrets
|
|
397
570
|
const payloads = eb.buildSecretPayloads(
|
|
398
|
-
{
|
|
399
|
-
|
|
571
|
+
{
|
|
572
|
+
framework,
|
|
573
|
+
availability,
|
|
574
|
+
platform,
|
|
575
|
+
language,
|
|
576
|
+
database,
|
|
577
|
+
storage,
|
|
578
|
+
opensearch,
|
|
579
|
+
},
|
|
580
|
+
{
|
|
581
|
+
...s3Data,
|
|
582
|
+
...rdsData,
|
|
583
|
+
...osData,
|
|
584
|
+
APP_NAME: appName,
|
|
585
|
+
APP_DOMAIN: appDomain,
|
|
586
|
+
},
|
|
400
587
|
);
|
|
401
588
|
|
|
402
589
|
const secretArns = {};
|
|
403
590
|
|
|
404
|
-
for (const group of
|
|
591
|
+
for (const group of secretGroups) {
|
|
405
592
|
if (existingSecrets[group]) {
|
|
406
593
|
secretArns[`${group}SecretArn`] = existingSecrets[group];
|
|
594
|
+
|
|
595
|
+
// Merge new keys into existing secret if payload has additions
|
|
596
|
+
if (Object.keys(payloads[group]).length) {
|
|
597
|
+
const currentValue =
|
|
598
|
+
await module.exports.secretsmanager.getSecretValue(
|
|
599
|
+
`${name}-${group}`,
|
|
600
|
+
);
|
|
601
|
+
const newKeys = Object.keys(payloads[group]).filter(
|
|
602
|
+
(k) => !(k in currentValue),
|
|
603
|
+
);
|
|
604
|
+
|
|
605
|
+
if (newKeys.length) {
|
|
606
|
+
const merged = { ...currentValue };
|
|
607
|
+
for (const k of newKeys) {
|
|
608
|
+
merged[k] = payloads[group][k];
|
|
609
|
+
}
|
|
610
|
+
await module.exports.secretsmanager.updateSecretValue(
|
|
611
|
+
`${name}-${group}`,
|
|
612
|
+
merged,
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
407
616
|
} else if (Object.keys(payloads[group]).length) {
|
|
408
617
|
const res = await module.exports.secretsmanager.createSecret(
|
|
409
618
|
`${name}-${group}`,
|
|
@@ -417,7 +626,7 @@ module.exports.fullstack = async (
|
|
|
417
626
|
// Auto-generate environmentsecrets entries from secret keys + ARNs
|
|
418
627
|
const secretSettings = [];
|
|
419
628
|
|
|
420
|
-
for (const group of
|
|
629
|
+
for (const group of secretGroups) {
|
|
421
630
|
const arn = secretArns[`${group}SecretArn`];
|
|
422
631
|
if (!arn) continue;
|
|
423
632
|
|
|
@@ -443,39 +652,129 @@ module.exports.fullstack = async (
|
|
|
443
652
|
}
|
|
444
653
|
}
|
|
445
654
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
655
|
+
let environment;
|
|
656
|
+
|
|
657
|
+
if (existingEnvironment) {
|
|
658
|
+
// Re-provision: sync secrets to existing EB environment
|
|
659
|
+
const SECRETS_NAMESPACE =
|
|
660
|
+
"aws:elasticbeanstalk:application:environmentsecrets";
|
|
661
|
+
const currentSettings =
|
|
662
|
+
await module.exports.elasticbeanstalk.describeConfigurationSettings(
|
|
663
|
+
repo,
|
|
664
|
+
name,
|
|
665
|
+
);
|
|
666
|
+
const currentSecrets = currentSettings.filter(
|
|
667
|
+
(s) => s.Namespace === SECRETS_NAMESPACE,
|
|
668
|
+
);
|
|
458
669
|
|
|
459
|
-
|
|
670
|
+
const currentKeys = new Set(currentSecrets.map((s) => s.OptionName));
|
|
671
|
+
const expectedKeys = new Set(secretSettings.map((s) => s.OptionName));
|
|
460
672
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
673
|
+
const toAdd = secretSettings.filter(
|
|
674
|
+
(s) => !currentKeys.has(s.OptionName),
|
|
675
|
+
);
|
|
676
|
+
const toUpdate = secretSettings.filter((s) => {
|
|
677
|
+
const current = currentSecrets.find(
|
|
678
|
+
(c) => c.OptionName === s.OptionName,
|
|
679
|
+
);
|
|
680
|
+
return current && current.Value !== s.Value;
|
|
466
681
|
});
|
|
467
|
-
|
|
682
|
+
const toRemove = currentSecrets
|
|
683
|
+
.filter((s) => !expectedKeys.has(s.OptionName))
|
|
684
|
+
.map((s) => ({
|
|
685
|
+
OptionName: s.OptionName,
|
|
686
|
+
Namespace: SECRETS_NAMESPACE,
|
|
687
|
+
}));
|
|
688
|
+
|
|
689
|
+
const optionSettings = [...toAdd, ...toUpdate];
|
|
690
|
+
|
|
691
|
+
if (optionSettings.length || toRemove.length) {
|
|
692
|
+
await module.exports.elasticbeanstalk.updateEnvironmentSettings(
|
|
693
|
+
name,
|
|
694
|
+
optionSettings,
|
|
695
|
+
toRemove,
|
|
696
|
+
);
|
|
697
|
+
}
|
|
468
698
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
699
|
+
environment = existingEnvironment;
|
|
700
|
+
} else {
|
|
701
|
+
const OptionSettings = eb.merge(
|
|
702
|
+
"environments",
|
|
703
|
+
{
|
|
704
|
+
framework,
|
|
705
|
+
availability,
|
|
706
|
+
platform,
|
|
707
|
+
language,
|
|
708
|
+
database,
|
|
709
|
+
storage,
|
|
710
|
+
opensearch,
|
|
711
|
+
vpcId: vpcData.vpcId,
|
|
712
|
+
privateSubnetIds: vpcData.privateSubnetIds.join(","),
|
|
713
|
+
publicSubnetIds: vpcData.publicSubnetIds.join(","),
|
|
714
|
+
elbScheme,
|
|
715
|
+
instanceProfileName,
|
|
716
|
+
},
|
|
717
|
+
{
|
|
718
|
+
...s3Data,
|
|
719
|
+
...rdsData,
|
|
720
|
+
...osData,
|
|
721
|
+
APP_NAME: appName,
|
|
722
|
+
APP_DOMAIN: appDomain,
|
|
723
|
+
},
|
|
477
724
|
);
|
|
478
725
|
|
|
726
|
+
// HTTPS listener via ACM cert
|
|
727
|
+
if (certArn) {
|
|
728
|
+
OptionSettings.push(
|
|
729
|
+
{
|
|
730
|
+
OptionName: "ListenerEnabled",
|
|
731
|
+
Value: "true",
|
|
732
|
+
Namespace: "aws:elbv2:listener:443",
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
OptionName: "Protocol",
|
|
736
|
+
Value: "HTTPS",
|
|
737
|
+
Namespace: "aws:elbv2:listener:443",
|
|
738
|
+
},
|
|
739
|
+
{
|
|
740
|
+
OptionName: "SSLCertificateArns",
|
|
741
|
+
Value: certArn,
|
|
742
|
+
Namespace: "aws:elbv2:listener:443",
|
|
743
|
+
},
|
|
744
|
+
{
|
|
745
|
+
OptionName: "SSLPolicy",
|
|
746
|
+
Value: "ELBSecurityPolicy-FS-1-2-Res-2020-10",
|
|
747
|
+
Namespace: "aws:elbv2:listener:443",
|
|
748
|
+
},
|
|
749
|
+
{
|
|
750
|
+
OptionName: "DefaultProcess",
|
|
751
|
+
Value: "default",
|
|
752
|
+
Namespace: "aws:elbv2:listener:443",
|
|
753
|
+
},
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
OptionSettings.push(...secretSettings);
|
|
758
|
+
|
|
759
|
+
if (hasKeyPair) {
|
|
760
|
+
OptionSettings.push({
|
|
761
|
+
OptionName: "EC2KeyName",
|
|
762
|
+
Value: "id_rsa_prev",
|
|
763
|
+
Namespace: "aws:autoscaling:launchconfiguration",
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
environment =
|
|
768
|
+
await module.exports.elasticbeanstalk.createElasticBeanstalkEnvironment(
|
|
769
|
+
name,
|
|
770
|
+
{ framework, availability, platform, language },
|
|
771
|
+
repo,
|
|
772
|
+
OptionSettings,
|
|
773
|
+
CNAMEPrefix,
|
|
774
|
+
tags,
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
|
|
479
778
|
// Add RDS security group ingress rule from EB security group
|
|
480
779
|
const rdsSlugForIngress = module.exports.slug(
|
|
481
780
|
repo,
|
|
@@ -508,6 +807,33 @@ module.exports.fullstack = async (
|
|
|
508
807
|
/* empty */
|
|
509
808
|
}
|
|
510
809
|
|
|
810
|
+
// Add OpenSearch security group ingress rule from EB security group
|
|
811
|
+
if (osSlug) {
|
|
812
|
+
try {
|
|
813
|
+
const osSg = await module.exports.ec2.findSecurityGroupByName(
|
|
814
|
+
`${osSlug}-os`,
|
|
815
|
+
);
|
|
816
|
+
const ebSgForOs = await module.exports.ec2.findSecurityGroupByTag([
|
|
817
|
+
{
|
|
818
|
+
Name: "tag:elasticbeanstalk:environment-name",
|
|
819
|
+
Values: [name],
|
|
820
|
+
},
|
|
821
|
+
{
|
|
822
|
+
Name: "tag:aws:cloudformation:logical-id",
|
|
823
|
+
Values: ["AWSEBSecurityGroup"],
|
|
824
|
+
},
|
|
825
|
+
]);
|
|
826
|
+
if (osSg && ebSgForOs) {
|
|
827
|
+
await module.exports.ec2.authorizeOpenSearchIngress(
|
|
828
|
+
osSg.GroupId,
|
|
829
|
+
ebSgForOs.GroupId,
|
|
830
|
+
);
|
|
831
|
+
}
|
|
832
|
+
} catch {
|
|
833
|
+
/* empty */
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
511
837
|
const configurations = eb.merge("configurations", {
|
|
512
838
|
framework,
|
|
513
839
|
availability,
|
|
@@ -561,6 +887,7 @@ module.exports.fullstackTerminate = async (
|
|
|
561
887
|
{
|
|
562
888
|
deleteStorage = false,
|
|
563
889
|
deleteDatabase = false,
|
|
890
|
+
deleteOpenSearch = false,
|
|
564
891
|
deleteAppSecret = false,
|
|
565
892
|
} = {},
|
|
566
893
|
) => {
|
|
@@ -623,6 +950,38 @@ module.exports.fullstackTerminate = async (
|
|
|
623
950
|
/* empty */
|
|
624
951
|
}
|
|
625
952
|
|
|
953
|
+
// Remove OpenSearch inbound rule referencing EB security group before terminating EB
|
|
954
|
+
const osSlug = module.exports.slug(
|
|
955
|
+
repo,
|
|
956
|
+
process.env.AWS_PROFILE,
|
|
957
|
+
branch,
|
|
958
|
+
"os",
|
|
959
|
+
);
|
|
960
|
+
|
|
961
|
+
try {
|
|
962
|
+
const osSg = await module.exports.ec2.findSecurityGroupByName(
|
|
963
|
+
`${osSlug}-os`,
|
|
964
|
+
);
|
|
965
|
+
const ebSgForOs = await module.exports.ec2.findSecurityGroupByTag([
|
|
966
|
+
{
|
|
967
|
+
Name: "tag:elasticbeanstalk:environment-name",
|
|
968
|
+
Values: [name],
|
|
969
|
+
},
|
|
970
|
+
{
|
|
971
|
+
Name: "tag:aws:cloudformation:logical-id",
|
|
972
|
+
Values: ["AWSEBSecurityGroup"],
|
|
973
|
+
},
|
|
974
|
+
]);
|
|
975
|
+
if (osSg && ebSgForOs) {
|
|
976
|
+
await module.exports.ec2.revokeOpenSearchIngress(
|
|
977
|
+
osSg.GroupId,
|
|
978
|
+
ebSgForOs.GroupId,
|
|
979
|
+
);
|
|
980
|
+
}
|
|
981
|
+
} catch {
|
|
982
|
+
/* empty */
|
|
983
|
+
}
|
|
984
|
+
|
|
626
985
|
try {
|
|
627
986
|
await module.exports.elasticbeanstalk.removeElasticBeanstalkEnvironment(
|
|
628
987
|
name,
|
|
@@ -639,6 +998,12 @@ module.exports.fullstackTerminate = async (
|
|
|
639
998
|
/* empty */
|
|
640
999
|
}
|
|
641
1000
|
|
|
1001
|
+
try {
|
|
1002
|
+
await module.exports.iam.removeEnvironmentInstanceProfile(name);
|
|
1003
|
+
} catch {
|
|
1004
|
+
/* empty */
|
|
1005
|
+
}
|
|
1006
|
+
|
|
642
1007
|
if (deleteDatabase) {
|
|
643
1008
|
try {
|
|
644
1009
|
await module.exports.rds.deleteRDSInstance(rdsSlug);
|
|
@@ -659,6 +1024,26 @@ module.exports.fullstackTerminate = async (
|
|
|
659
1024
|
}
|
|
660
1025
|
}
|
|
661
1026
|
|
|
1027
|
+
if (deleteOpenSearch) {
|
|
1028
|
+
try {
|
|
1029
|
+
await module.exports.opensearch.deleteOpenSearchDomain(osSlug);
|
|
1030
|
+
await module.exports.opensearch.waitForOpenSearchDeletion(osSlug);
|
|
1031
|
+
} catch {
|
|
1032
|
+
/* empty */
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
try {
|
|
1036
|
+
const sg = await module.exports.ec2.findSecurityGroupByName(
|
|
1037
|
+
`${osSlug}-os`,
|
|
1038
|
+
);
|
|
1039
|
+
if (sg) {
|
|
1040
|
+
await module.exports.ec2.deleteSecurityGroup(sg.GroupId);
|
|
1041
|
+
}
|
|
1042
|
+
} catch {
|
|
1043
|
+
/* empty */
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
|
|
662
1047
|
// Delete secrets
|
|
663
1048
|
if (deleteStorage) {
|
|
664
1049
|
try {
|
|
@@ -678,6 +1063,16 @@ module.exports.fullstackTerminate = async (
|
|
|
678
1063
|
}
|
|
679
1064
|
}
|
|
680
1065
|
|
|
1066
|
+
if (deleteOpenSearch) {
|
|
1067
|
+
try {
|
|
1068
|
+
await module.exports.secretsmanager.deleteSecret(
|
|
1069
|
+
`${name}-opensearch`,
|
|
1070
|
+
);
|
|
1071
|
+
} catch {
|
|
1072
|
+
/* empty */
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
|
|
681
1076
|
if (deleteAppSecret) {
|
|
682
1077
|
try {
|
|
683
1078
|
await module.exports.secretsmanager.deleteSecret(`${name}-app`);
|