@fishawack/lab-env 5.7.0-beta.1 → 5.7.0-beta.3

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 +58 -0
  2. package/_Test/provision.js +4 -9
  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 +36 -0
  8. package/commands/create/cmds/key.js +60 -41
  9. package/commands/create/cmds/provision.js +573 -69
  10. package/commands/create/cmds/rekey.js +218 -0
  11. package/commands/create/cmds/sync-secrets.js +2 -1
  12. package/commands/create/libs/output-credentials.js +59 -0
  13. package/commands/create/libs/parallel-runner.js +204 -0
  14. package/commands/create/libs/resolve-operator.js +59 -0
  15. package/commands/create/libs/utilities.js +73 -8
  16. package/commands/create/libs/vars.js +120 -136
  17. package/commands/create/services/aws/acm.js +61 -0
  18. package/commands/create/services/aws/cloudfront.js +51 -45
  19. package/commands/create/services/aws/ec2.js +675 -48
  20. package/commands/create/services/aws/elasticache.js +159 -0
  21. package/commands/create/services/aws/elasticbeanstalk.js +154 -57
  22. package/commands/create/services/aws/iam.js +402 -82
  23. package/commands/create/services/aws/index.js +1398 -260
  24. package/commands/create/services/aws/opensearch.js +155 -0
  25. package/commands/create/services/aws/rds.js +57 -31
  26. package/commands/create/services/aws/s3.js +52 -31
  27. package/commands/create/services/aws/secretsmanager.js +21 -12
  28. package/commands/create/services/aws/ses.js +195 -0
  29. package/commands/create/templates/elasticbeanstalk/.ebextensions/laravel/cron.config +45 -0
  30. package/commands/create/templates/elasticbeanstalk/.ebextensions/laravel/queue-worker.config +29 -0
  31. package/commands/create/templates/elasticbeanstalk/.ebextensions/laravel/software.config +12 -0
  32. package/commands/create/templates/elasticbeanstalk/.ebextensions/misc/setvars.config +2 -16
  33. package/commands/create/templates/elasticbeanstalk/.platform/confighooks/postdeploy/rewrite-flush.sh +1 -1
  34. package/commands/create/templates/elasticbeanstalk/.platform/confighooks/postdeploy/setvars.sh +19 -0
  35. package/commands/create/templates/elasticbeanstalk/.platform/hooks/postdeploy/restart_queue.sh +4 -0
  36. package/commands/create/templates/elasticbeanstalk/.platform/hooks/prebuild/setvars.sh +19 -0
  37. package/package.json +7 -2
@@ -126,10 +126,6 @@ module.exports.templates = [
126
126
  name: "boilerplate-laravel",
127
127
  type: "boilerplate",
128
128
  },
129
- {
130
- name: "boilerplate-laravel-vue",
131
- type: "boilerplate",
132
- },
133
129
  {
134
130
  name: "boilerplate-drupal",
135
131
  type: "boilerplate",
@@ -178,6 +174,15 @@ module.exports.templates = [
178
174
  },
179
175
  ];
180
176
 
177
+ module.exports.secretGroups = [
178
+ "app",
179
+ "storage",
180
+ "database",
181
+ "opensearch",
182
+ "cache",
183
+ "mail",
184
+ ];
185
+
181
186
  module.exports.frameworks = [
182
187
  {
183
188
  name: "wordpress",
@@ -206,61 +211,12 @@ module.exports.eb = {
206
211
  environments: {
207
212
  python: {
208
213
  shared: [],
209
- low: [],
210
- high: [
211
- {
212
- OptionName: "ELBScheme",
213
- Value: "internal",
214
- Namespace: "aws:ec2:vpc",
215
- },
216
- () => {
217
- const regions = {
218
- "us-east-1": {
219
- VPCId: "vpc-d30bcca8",
220
- Subnets:
221
- "subnet-00bf5eda896fdd5c5,subnet-0aa7e40d4701683ae",
222
- },
223
- "eu-west-1": {
224
- VPCId: "vpc-cc0d9aa9",
225
- Subnets:
226
- "subnet-0e357f06e047e1c10,subnet-0956ffb043535455d",
227
- },
228
- };
229
-
230
- return [
231
- {
232
- OptionName: "VPCId",
233
- Value: regions[process.env.AWS_REGION].VPCId,
234
- Namespace: "aws:ec2:vpc",
235
- },
236
- {
237
- OptionName: "Subnets",
238
- Value: regions[process.env.AWS_REGION].Subnets,
239
- Namespace: "aws:ec2:vpc",
240
- },
241
- {
242
- OptionName: "ELBSubnets",
243
- Value: regions[process.env.AWS_REGION].Subnets,
244
- Namespace: "aws:ec2:vpc",
245
- },
246
- ];
247
- },
248
- {
249
- OptionName: "AssociatePublicIpAddress",
250
- Value: "false",
251
- Namespace: "aws:ec2:vpc",
252
- },
253
- ],
254
214
  },
255
215
  php: {
256
216
  shared: [],
257
- low: [],
258
- high: [],
259
217
  },
260
218
  nginx: {
261
219
  shared: [],
262
- low: [],
263
- high: [],
264
220
  },
265
221
  httpd: {
266
222
  shared: [
@@ -270,33 +226,21 @@ module.exports.eb = {
270
226
  Namespace: "aws:elasticbeanstalk:environment:proxy",
271
227
  },
272
228
  ],
273
- low: [],
274
- high: [],
275
229
  },
276
230
  default: {
277
231
  shared: [],
278
- low: [],
279
- high: [],
280
232
  },
281
233
  wordpress: {
282
234
  shared: [],
283
- low: [],
284
- high: [],
285
235
  },
286
236
  laravel: {
287
237
  shared: [],
288
- low: [],
289
- high: [],
290
238
  },
291
239
  adonis: {
292
240
  shared: [],
293
- low: [],
294
- high: [],
295
241
  },
296
242
  drupal: {
297
243
  shared: [],
298
- low: [],
299
- high: [],
300
244
  },
301
245
  shared: [
302
246
  {
@@ -304,18 +248,16 @@ module.exports.eb = {
304
248
  Value: "lab-env-aws-elasticbeanstalk-service-role",
305
249
  Namespace: "aws:elasticbeanstalk:environment",
306
250
  },
307
- {
251
+ ({ instanceProfileName }) => ({
308
252
  OptionName: "IamInstanceProfile",
309
- Value: "aws-elasticbeanstalk-ec2-role",
253
+ Value: instanceProfileName || "aws-elasticbeanstalk-ec2-role",
310
254
  Namespace: "aws:autoscaling:launchconfiguration",
311
- },
312
- ],
313
- low: [
314
- {
315
- OptionName: "EnvironmentType",
316
- Value: "SingleInstance",
317
- Namespace: "aws:elasticbeanstalk:environment",
318
- },
255
+ }),
256
+ ({ instanceType }) => ({
257
+ OptionName: "InstanceType",
258
+ Value: instanceType || "t3.small",
259
+ Namespace: "aws:autoscaling:launchconfiguration",
260
+ }),
319
261
  ],
320
262
  high: [
321
263
  {
@@ -358,6 +300,59 @@ module.exports.eb = {
358
300
  Value: "application",
359
301
  Namespace: "aws:elasticbeanstalk:environment",
360
302
  },
303
+ ({ vpcId, privateSubnetIds, publicSubnetIds, elbScheme }) => {
304
+ const settings = [
305
+ {
306
+ OptionName: "VPCId",
307
+ Value: vpcId,
308
+ Namespace: "aws:ec2:vpc",
309
+ },
310
+ {
311
+ OptionName: "Subnets",
312
+ Value: privateSubnetIds,
313
+ Namespace: "aws:ec2:vpc",
314
+ },
315
+ {
316
+ OptionName: "ELBSubnets",
317
+ Value:
318
+ elbScheme === "internal"
319
+ ? privateSubnetIds
320
+ : publicSubnetIds,
321
+ Namespace: "aws:ec2:vpc",
322
+ },
323
+ {
324
+ OptionName: "AssociatePublicIpAddress",
325
+ Value: "false",
326
+ Namespace: "aws:ec2:vpc",
327
+ },
328
+ ];
329
+
330
+ if (elbScheme === "internal") {
331
+ settings.push({
332
+ OptionName: "ELBScheme",
333
+ Value: "internal",
334
+ Namespace: "aws:ec2:vpc",
335
+ });
336
+ }
337
+
338
+ return settings;
339
+ },
340
+ {
341
+ OptionName: "ConfigDocument",
342
+ Value: JSON.stringify({
343
+ Version: 1,
344
+ CloudWatchMetrics: { Instance: {}, Environment: {} },
345
+ Rules: {
346
+ Environment: {
347
+ Application: {
348
+ ApplicationRequests4xx: { Enabled: false },
349
+ },
350
+ ELB: { ELBRequests4xx: { Enabled: false } },
351
+ },
352
+ },
353
+ }),
354
+ Namespace: "aws:elasticbeanstalk:healthreporting:system",
355
+ },
361
356
  ],
362
357
  },
363
358
  secretPayloads: {
@@ -372,13 +367,7 @@ module.exports.eb = {
372
367
  MYSQL_ATTR_SSL_CA: "/opt/rds-certs/global-bundle.pem",
373
368
  },
374
369
  },
375
- low: {
376
- app: {
377
- DOMAIN_LINK: "<%= DOMAIN_LINK %>",
378
- EMAIL_LINK: "mike.mellor@fishawack.com",
379
- },
380
- },
381
- high: {},
370
+
382
371
  wordpress: {
383
372
  shared: {
384
373
  app: {
@@ -435,20 +424,43 @@ module.exports.eb = {
435
424
  laravel: {
436
425
  shared: {
437
426
  app: {
427
+ APP_NAME: "<%= APP_NAME %>",
438
428
  APP_ENV: "production",
439
429
  APP_KEY: `base64:${Buffer.from(generator.generate({ length: 32, numbers: true, symbols: true })).toString("base64")}`,
440
430
  APP_DEBUG: "false",
441
- APP_URL: "https://<%= DOMAIN_LINK %>",
431
+ APP_URL: "https://<%= APP_DOMAIN %>",
432
+ SESSION_DOMAIN: "<%= SESSION_DOMAIN %>",
433
+ AWS_DEFAULT_REGION: "<%= AWS_REGION %>",
442
434
  },
443
435
  storage: ({ storage }) =>
444
436
  storage && {
445
437
  AWS_BUCKET: "<%= s3Slug %>",
446
- AWS_DEFAULT_REGION: "<%= AWS_REGION %>",
447
- AWS_ACCESS_KEY_ID: "<%= AccessKeyId %>",
448
- AWS_SECRET_ACCESS_KEY: "<%= SecretAccessKey %>",
438
+ AWS_ACL_VISIBILITY: "private",
449
439
  FILESYSTEM_DISK: "s3",
450
440
  FILESYSTEM_DISK_PUBLIC: "s3-public",
451
441
  },
442
+ opensearch: ({ opensearch }) =>
443
+ opensearch && {
444
+ SEARCH_HOST: "<%= SEARCH_HOST %>",
445
+ },
446
+ cache: ({ cache }) =>
447
+ cache && {
448
+ REDIS_SCHEME: "tls",
449
+ REDIS_HOST: "<%= REDIS_HOST %>",
450
+ REDIS_PORT: "<%= REDIS_PORT %>",
451
+ REDIS_PASSWORD: "<%= REDIS_PASSWORD %>",
452
+ CACHE_STORE: "redis",
453
+ },
454
+ mail: ({ mail }) =>
455
+ mail && {
456
+ MAIL_FROM_ADDRESS: "<%= MAIL_FROM_ADDRESS %>",
457
+ MAIL_FROM_NAME: "<%= MAIL_FROM_NAME %>",
458
+ MAIL_HOST: "email-smtp.us-east-1.amazonaws.com",
459
+ MAIL_MAILER: "smtp",
460
+ MAIL_PASSWORD: "<%= MAIL_PASSWORD %>",
461
+ MAIL_PORT: "587",
462
+ MAIL_USERNAME: "<%= MAIL_USERNAME %>",
463
+ },
452
464
  },
453
465
  high: {
454
466
  app: {
@@ -466,11 +478,11 @@ module.exports.eb = {
466
478
  SESSION_DRIVER: "cookie",
467
479
  PORT: "3333",
468
480
  LOG_LEVEL: "info",
481
+ AWS_DEFAULT_REGION: "<%= AWS_REGION %>",
469
482
  },
470
483
  storage: ({ storage }) =>
471
484
  storage && {
472
485
  AWS_BUCKET: "<%= s3Slug %>",
473
- AWS_DEFAULT_REGION: "<%= AWS_REGION %>",
474
486
  AWS_ACCESS_KEY_ID: "<%= AccessKeyId %>",
475
487
  AWS_SECRET_ACCESS_KEY: "<%= SecretAccessKey %>",
476
488
  FILESYSTEM_DISK: "s3",
@@ -487,23 +499,22 @@ module.exports.eb = {
487
499
  },
488
500
  },
489
501
  buildSecretPayloads(config, data = {}) {
490
- const { framework, availability = "low", platform, language } = config;
491
- const result = { app: {}, storage: {}, database: {} };
502
+ const { framework, platform, language } = config;
503
+ const result = Object.fromEntries(
504
+ module.exports.secretGroups.map((g) => [g, {}]),
505
+ );
492
506
 
493
507
  const sources = [
494
508
  this.secretPayloads.shared,
495
- this.secretPayloads[availability],
496
509
  this.secretPayloads[platform]?.shared,
497
- this.secretPayloads[platform]?.[availability],
498
510
  this.secretPayloads[language]?.shared,
499
- this.secretPayloads[language]?.[availability],
500
511
  this.secretPayloads[framework]?.shared,
501
- this.secretPayloads[framework]?.[availability],
512
+ this.secretPayloads[framework]?.high,
502
513
  ];
503
514
 
504
515
  for (const source of sources) {
505
516
  if (!source) continue;
506
- for (const group of ["app", "storage", "database"]) {
517
+ for (const group of module.exports.secretGroups) {
507
518
  let values = source[group];
508
519
  if (typeof values === "function") {
509
520
  values = values(config, data);
@@ -516,7 +527,7 @@ module.exports.eb = {
516
527
 
517
528
  // Apply lodash template interpolation
518
529
  const templateData = { ...process.env, ...data };
519
- for (const group of ["app", "storage", "database"]) {
530
+ for (const group of module.exports.secretGroups) {
520
531
  if (Object.keys(result[group]).length) {
521
532
  result[group] = JSON.parse(
522
533
  template(JSON.stringify(result[group]))(templateData),
@@ -529,8 +540,6 @@ module.exports.eb = {
529
540
  configurations: {
530
541
  php: {
531
542
  shared: [".ebextensions/php/ini.config"],
532
- low: [],
533
- high: [],
534
543
  },
535
544
  nginx: {
536
545
  shared: [
@@ -538,14 +547,6 @@ module.exports.eb = {
538
547
  ".platform/nginx/conf.d/upload_size.conf",
539
548
  ".platform/nginx/conf.d/security_headers.conf",
540
549
  ".platform/nginx/conf.d/elasticbeanstalk/www-to-nonwww-redirection.conf",
541
- ],
542
- low: [
543
- ".platform/nginx/conf.d/elasticbeanstalk/http-https-redirection.conf",
544
- ".platform/nginx/conf.d/ssl.conf",
545
- ".ebextensions/nginx/auto-ssl.config",
546
- ],
547
- high: [
548
- ".platform/nginx/conf.d/elasticbeanstalk/health.conf",
549
550
  ".ebextensions/nginx/alb-health.config",
550
551
  ],
551
552
  },
@@ -553,14 +554,6 @@ module.exports.eb = {
553
554
  shared: [
554
555
  ".platform/httpd/conf.d/virtualhost-80.conf",
555
556
  ".platform/httpd/conf.d/elasticbeanstalk/80/www-to-nonwww-redirection.conf",
556
- ],
557
- low: [
558
- ".platform/httpd/conf.d/elasticbeanstalk/80/http-https-redirection.conf",
559
- ".platform/httpd/conf.d/virtualhost-443.conf",
560
- ".platform/httpd/conf.d/elasticbeanstalk/443/ssl.conf",
561
- ".ebextensions/httpd/auto-ssl.config",
562
- ],
563
- high: [
564
557
  ".platform/httpd/conf.d/elasticbeanstalk/80/forward-https-proto.conf",
565
558
  ".ebextensions/wordpress/alb-health.config",
566
559
  ],
@@ -569,8 +562,6 @@ module.exports.eb = {
569
562
  platform: "nginx",
570
563
  language: "php",
571
564
  shared: [],
572
- low: [],
573
- high: [],
574
565
  },
575
566
  wordpress: {
576
567
  platform: "httpd",
@@ -583,21 +574,21 @@ module.exports.eb = {
583
574
  ".ebextensions/wordpress/environment.config",
584
575
  ".ebextensions/wordpress/post-deploy.config",
585
576
  ".ebextensions/wordpress/software.config",
577
+ ".platform/hooks/predeploy/deactivate-plugins.sh",
586
578
  ],
587
- low: [".platform/hooks/predeploy/deactivate-plugins-single.sh"],
588
- high: [".platform/hooks/predeploy/deactivate-plugins.sh"],
589
579
  },
590
580
  laravel: {
591
581
  platform: "nginx",
592
582
  language: "php",
593
583
  shared: [
594
584
  ".platform/nginx/conf.d/elasticbeanstalk/laravel.conf",
585
+ ".ebextensions/laravel/cron.config",
595
586
  ".ebextensions/laravel/post-deploy.config",
596
- ".ebextensions/laravel/software.config",
587
+ ".ebextensions/laravel/queue-worker.config",
597
588
  ".ebextensions/laravel/rds-ssl.config",
589
+ ".ebextensions/laravel/software.config",
590
+ ".platform/hooks/postdeploy/restart_queue.sh",
598
591
  ],
599
- low: [],
600
- high: [],
601
592
  },
602
593
  adonis: {
603
594
  platform: "nginx",
@@ -606,34 +597,27 @@ module.exports.eb = {
606
597
  ".ebextensions/adonis/post-deploy.config",
607
598
  ".ebextensions/adonis/software.config",
608
599
  ],
609
- low: [],
610
- high: [],
611
600
  },
612
601
  drupal: {
613
602
  platform: "httpd",
614
603
  language: "php",
615
604
  shared: [],
616
- low: [],
617
- high: [],
618
605
  },
619
606
  python: {
620
607
  platform: "nginx",
621
608
  language: "python",
622
609
  shared: [],
623
- low: [],
624
- high: [],
625
610
  },
626
- shared: [".ebextensions/misc/setvars.config"],
627
- low: [".ebextensions/misc/enable-https.config"],
628
- high: [
611
+ shared: [
612
+ ".platform/hooks/prebuild/setvars.sh",
613
+ ".platform/confighooks/postdeploy/setvars.sh",
614
+ ".ebextensions/misc/asg-self-healing.config",
629
615
  ".ebextensions/misc/alb-http-to-https-redirection.config",
630
616
  ".ebextensions/misc/enable-https-lb.config",
631
- ".ebextensions/misc/asg-self-healing.config",
632
- ".ebextensions/misc/health-ignore-4xx.config",
633
617
  ],
634
618
  },
635
619
  merge(type, config, data = {}) {
636
- const { framework, availability = "low", platform, language } = config;
620
+ const { framework, platform, language } = config;
637
621
 
638
622
  return JSON.parse(
639
623
  template(
@@ -641,13 +625,10 @@ module.exports.eb = {
641
625
  []
642
626
  .concat(
643
627
  this[type].shared,
644
- this[type][availability],
628
+ this[type].high,
645
629
  this[type][platform]?.shared,
646
- this[type][platform]?.[availability],
647
630
  this[type][language]?.shared,
648
- this[type][language]?.[availability],
649
631
  this[type][framework]?.shared,
650
- this[type][framework]?.[availability],
651
632
  )
652
633
  .map((d) =>
653
634
  typeof d === "function" ? d(config, data) : d,
@@ -727,3 +708,6 @@ module.exports.cloudfront = (
727
708
  },
728
709
  },
729
710
  });
711
+
712
+ // AWS SDK client defaults
713
+ module.exports.awsClientConfig = { maxAttempts: 5, retryMode: "adaptive" };
@@ -0,0 +1,61 @@
1
+ const { ACMClient, ListCertificatesCommand } = require("@aws-sdk/client-acm");
2
+ const { Spinner } = require("../../libs/utilities");
3
+ const { awsClientConfig } = require("../../libs/vars");
4
+
5
+ const getClient = () => new ACMClient(awsClientConfig);
6
+
7
+ module.exports.listCertificates = async () => {
8
+ const certs = [];
9
+ let nextToken;
10
+
11
+ do {
12
+ const res = await getClient().send(
13
+ new ListCertificatesCommand({
14
+ CertificateStatuses: ["ISSUED"],
15
+ NextToken: nextToken,
16
+ }),
17
+ );
18
+ certs.push(...(res.CertificateSummaryList || []));
19
+ nextToken = res.NextToken;
20
+ } while (nextToken);
21
+
22
+ return certs;
23
+ };
24
+
25
+ module.exports.findCertificatesForDomain = async (domain) => {
26
+ const certs = await Spinner.prototype.simple(
27
+ `Looking up ACM certificates for ${domain}`,
28
+ () => module.exports.listCertificates(),
29
+ );
30
+
31
+ if (!certs.length) return [];
32
+
33
+ // Extract apex domain (e.g. "test.avh-det.com" → "avh-det.com")
34
+ const parts = domain.split(".");
35
+ const apex = parts.length > 2 ? parts.slice(-2).join(".") : domain;
36
+ const wildcard = `*.${apex}`;
37
+
38
+ // Check all cert domains (DomainName + SANs)
39
+ const getCertDomains = (cert) => {
40
+ const domains = [cert.DomainName];
41
+ if (cert.SubjectAlternativeNameSummaries) {
42
+ domains.push(...cert.SubjectAlternativeNameSummaries);
43
+ }
44
+ return domains;
45
+ };
46
+
47
+ const candidates = [domain, wildcard];
48
+ if (apex !== domain) candidates.push(apex);
49
+
50
+ const matches = certs.filter((c) =>
51
+ getCertDomains(c).some((d) => candidates.includes(d)),
52
+ );
53
+
54
+ // Deduplicate by ARN
55
+ const seen = new Set();
56
+ return matches.filter((c) => {
57
+ if (seen.has(c.CertificateArn)) return false;
58
+ seen.add(c.CertificateArn);
59
+ return true;
60
+ });
61
+ };