BuzzerboyAWSLightsail 0.332.1__tar.gz → 0.333.1__tar.gz
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.
- {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.333.1}/BuzzerboyAWSLightsail.egg-info/PKG-INFO +1 -1
- {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.333.1}/BuzzerboyAWSLightsail.egg-info/SOURCES.txt +2 -0
- {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.333.1}/BuzzerboyAWSLightsailStack/LightsailBase.py +4 -331
- buzzerboyawslightsail-0.333.1/BuzzerboyAWSLightsailStack/LightsailBaseStandalone.py +154 -0
- buzzerboyawslightsail-0.333.1/BuzzerboyAWSLightsailStack/LightsailContainer.py +145 -0
- buzzerboyawslightsail-0.333.1/BuzzerboyAWSLightsailStack/LightsailContainerStandalone.py +149 -0
- buzzerboyawslightsail-0.333.1/BuzzerboyAWSLightsailStack/LightsailDatabase.py +121 -0
- buzzerboyawslightsail-0.333.1/BuzzerboyAWSLightsailStack/LightsailDatabaseStandalone.py +121 -0
- buzzerboyawslightsail-0.333.1/BuzzerboyAWSLightsailStack/LightsailFlags.py +38 -0
- buzzerboyawslightsail-0.333.1/BuzzerboyAWSLightsailStack/LightsailMixins.py +542 -0
- {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.333.1}/PKG-INFO +1 -1
- {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.333.1}/pyproject.toml +1 -1
- buzzerboyawslightsail-0.332.1/BuzzerboyAWSLightsailStack/LightsailBaseStandalone.py +0 -318
- buzzerboyawslightsail-0.332.1/BuzzerboyAWSLightsailStack/LightsailContainer.py +0 -381
- buzzerboyawslightsail-0.332.1/BuzzerboyAWSLightsailStack/LightsailContainerStandalone.py +0 -385
- buzzerboyawslightsail-0.332.1/BuzzerboyAWSLightsailStack/LightsailDatabase.py +0 -484
- buzzerboyawslightsail-0.332.1/BuzzerboyAWSLightsailStack/LightsailDatabaseStandalone.py +0 -484
- {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.333.1}/BuzzerboyAWSLightsail.egg-info/dependency_links.txt +0 -0
- {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.333.1}/BuzzerboyAWSLightsail.egg-info/requires.txt +0 -0
- {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.333.1}/BuzzerboyAWSLightsail.egg-info/top_level.txt +0 -0
- {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.333.1}/BuzzerboyAWSLightsailStack/ArchitectureMaker.py +0 -0
- {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.333.1}/BuzzerboyAWSLightsailStack/LightSailPostDeploy.py +0 -0
- {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.333.1}/BuzzerboyAWSLightsailStack/LightsailAIContainer.py +0 -0
- {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.333.1}/BuzzerboyAWSLightsailStack/__init__.py +0 -0
- {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.333.1}/LICENSE +0 -0
- {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.333.1}/MANIFEST.in +0 -0
- {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.333.1}/README.md +0 -0
- {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.333.1}/requirements.txt +0 -0
- {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.333.1}/setup.cfg +0 -0
- {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.333.1}/setup.py +0 -0
|
@@ -18,4 +18,6 @@ BuzzerboyAWSLightsailStack/LightsailContainer.py
|
|
|
18
18
|
BuzzerboyAWSLightsailStack/LightsailContainerStandalone.py
|
|
19
19
|
BuzzerboyAWSLightsailStack/LightsailDatabase.py
|
|
20
20
|
BuzzerboyAWSLightsailStack/LightsailDatabaseStandalone.py
|
|
21
|
+
BuzzerboyAWSLightsailStack/LightsailFlags.py
|
|
22
|
+
BuzzerboyAWSLightsailStack/LightsailMixins.py
|
|
21
23
|
BuzzerboyAWSLightsailStack/__init__.py
|
|
@@ -24,11 +24,8 @@ This class should be extended by specific Lightsail implementations such as:
|
|
|
24
24
|
#region specific imports
|
|
25
25
|
|
|
26
26
|
import os
|
|
27
|
-
import json
|
|
28
27
|
from abc import ABC, abstractmethod
|
|
29
|
-
from enum import Enum
|
|
30
28
|
from constructs import Construct
|
|
31
|
-
from cdktf import TerraformOutput
|
|
32
29
|
|
|
33
30
|
# Import from the correct base architecture package
|
|
34
31
|
import sys
|
|
@@ -39,47 +36,14 @@ from AWSArchitectureBase.AWSArchitectureBaseStack.AWSArchitectureBase import AWS
|
|
|
39
36
|
|
|
40
37
|
#region AWS Provider and Resources
|
|
41
38
|
from cdktf_cdktf_provider_aws.provider import AwsProvider
|
|
42
|
-
from cdktf_cdktf_provider_aws import
|
|
43
|
-
iam_user,
|
|
44
|
-
iam_access_key,
|
|
45
|
-
iam_user_policy,
|
|
46
|
-
)
|
|
39
|
+
from cdktf_cdktf_provider_aws import iam_access_key, iam_user, iam_user_policy
|
|
47
40
|
#endregion
|
|
48
41
|
|
|
49
|
-
|
|
50
|
-
from
|
|
42
|
+
from .LightsailFlags import BaseLightsailArchitectureFlags
|
|
43
|
+
from .LightsailMixins import LightsailBaseMixin
|
|
51
44
|
|
|
52
|
-
# AWS Secrets Manager
|
|
53
|
-
from cdktf_cdktf_provider_aws.secretsmanager_secret import SecretsmanagerSecret
|
|
54
|
-
from cdktf_cdktf_provider_aws.secretsmanager_secret_version import SecretsmanagerSecretVersion
|
|
55
|
-
from cdktf_cdktf_provider_aws.data_aws_secretsmanager_secret_version import DataAwsSecretsmanagerSecretVersion
|
|
56
45
|
|
|
57
|
-
|
|
58
|
-
from cdktf_cdktf_provider_null.resource import Resource as NullResource
|
|
59
|
-
|
|
60
|
-
#endregion
|
|
61
|
-
|
|
62
|
-
#region Base ArchitectureFlags
|
|
63
|
-
class BaseLightsailArchitectureFlags(Enum):
|
|
64
|
-
"""
|
|
65
|
-
Base architecture configuration flags for optional components.
|
|
66
|
-
|
|
67
|
-
These flags are common to all Lightsail implementations and can be
|
|
68
|
-
extended by specific implementations with additional flags.
|
|
69
|
-
|
|
70
|
-
:param SKIP_DEFAULT_POST_APPLY_SCRIPTS: Skip default post-apply scripts
|
|
71
|
-
:param PRESERVE_EXISTING_SECRETS: Don't overwrite existing secret versions (smart detection)
|
|
72
|
-
:param IGNORE_SECRET_CHANGES: Ignore all changes to secret after initial creation
|
|
73
|
-
"""
|
|
74
|
-
|
|
75
|
-
SKIP_DEFAULT_POST_APPLY_SCRIPTS = "skip_default_post_apply_scripts"
|
|
76
|
-
PRESERVE_EXISTING_SECRETS = "preserve_existing_secrets"
|
|
77
|
-
IGNORE_SECRET_CHANGES = "ignore_secret_changes"
|
|
78
|
-
|
|
79
|
-
#endregion
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
class LightsailBase(AWSArchitectureBase):
|
|
46
|
+
class LightsailBase(LightsailBaseMixin, AWSArchitectureBase):
|
|
83
47
|
"""
|
|
84
48
|
Abstract base class for AWS Lightsail Infrastructure Stacks.
|
|
85
49
|
|
|
@@ -377,294 +341,3 @@ class LightsailBase(AWSArchitectureBase):
|
|
|
377
341
|
user=self.service_user.name,
|
|
378
342
|
policy=policy,
|
|
379
343
|
)
|
|
380
|
-
|
|
381
|
-
def get_extra_secret_env(self, env_var_name=None):
|
|
382
|
-
"""
|
|
383
|
-
Load additional secrets from environment variable.
|
|
384
|
-
|
|
385
|
-
Attempts to load and parse a JSON string from the environment variable
|
|
386
|
-
specified in default_extra_secret_env. Any valid JSON key-value pairs
|
|
387
|
-
are added to the secrets dictionary if they don't already exist.
|
|
388
|
-
|
|
389
|
-
:param env_var_name: Environment variable name to load secrets from
|
|
390
|
-
:raises: No exceptions - silently continues if JSON parsing fails
|
|
391
|
-
"""
|
|
392
|
-
if env_var_name is None:
|
|
393
|
-
env_var_name = self.default_extra_secret_env
|
|
394
|
-
|
|
395
|
-
extra_secret_env = os.environ.get(env_var_name, None)
|
|
396
|
-
|
|
397
|
-
if extra_secret_env:
|
|
398
|
-
try:
|
|
399
|
-
extra_secret_json = json.loads(extra_secret_env)
|
|
400
|
-
for key, value in extra_secret_json.items():
|
|
401
|
-
if key not in self.secrets:
|
|
402
|
-
self.secrets[key] = value
|
|
403
|
-
except json.JSONDecodeError:
|
|
404
|
-
# Silently continue if JSON parsing fails
|
|
405
|
-
pass
|
|
406
|
-
|
|
407
|
-
def create_security_resources(self):
|
|
408
|
-
"""
|
|
409
|
-
Create AWS Secrets Manager resources for credential storage.
|
|
410
|
-
|
|
411
|
-
Creates:
|
|
412
|
-
* Secrets Manager secret for storing application credentials
|
|
413
|
-
* Secret version with JSON-formatted credential data (conditionally)
|
|
414
|
-
|
|
415
|
-
**Secret Management Strategy:**
|
|
416
|
-
|
|
417
|
-
If PRESERVE_EXISTING_SECRETS flag is set:
|
|
418
|
-
- Checks if secret already exists with content
|
|
419
|
-
- Only creates new version if secret is empty or doesn't exist
|
|
420
|
-
- Preserves manual secret updates and rotations
|
|
421
|
-
|
|
422
|
-
**Stored Credentials:**
|
|
423
|
-
|
|
424
|
-
* IAM access keys for service authentication
|
|
425
|
-
* AWS region and signature version configuration
|
|
426
|
-
* Any additional secrets from environment variables
|
|
427
|
-
* Subclass-specific credentials (added by create_lightsail_resources)
|
|
428
|
-
|
|
429
|
-
.. note::
|
|
430
|
-
All secrets are stored as a single JSON document in Secrets Manager
|
|
431
|
-
for easy retrieval by applications.
|
|
432
|
-
"""
|
|
433
|
-
# Create Secrets Manager secret
|
|
434
|
-
self.secrets_manager_secret = SecretsmanagerSecret(self, self.secret_name, name=f"{self.secret_name}")
|
|
435
|
-
self.resources["secretsmanager_secret"] = self.secrets_manager_secret
|
|
436
|
-
|
|
437
|
-
# Populate IAM and AWS configuration secrets
|
|
438
|
-
self.secrets.update({
|
|
439
|
-
"service_user_access_key": self.service_key.id,
|
|
440
|
-
"service_user_secret_key": self.service_key.secret,
|
|
441
|
-
"access_key": self.service_key.id,
|
|
442
|
-
"secret_access_key": self.service_key.secret,
|
|
443
|
-
"region_name": self.region,
|
|
444
|
-
"signature_version": self.default_signature_version
|
|
445
|
-
})
|
|
446
|
-
|
|
447
|
-
# Load additional secrets from environment
|
|
448
|
-
self.get_extra_secret_env()
|
|
449
|
-
|
|
450
|
-
# Conditional secret version creation
|
|
451
|
-
if self.has_flag(BaseLightsailArchitectureFlags.PRESERVE_EXISTING_SECRETS.value):
|
|
452
|
-
self._create_secret_version_conditionally()
|
|
453
|
-
elif self.has_flag(BaseLightsailArchitectureFlags.IGNORE_SECRET_CHANGES.value):
|
|
454
|
-
self._create_secret_version_with_lifecycle_ignore()
|
|
455
|
-
else:
|
|
456
|
-
# Create secret version with all credentials (original behavior)
|
|
457
|
-
SecretsmanagerSecretVersion(
|
|
458
|
-
self,
|
|
459
|
-
self.secret_name + "_version",
|
|
460
|
-
secret_id=self.secrets_manager_secret.id,
|
|
461
|
-
secret_string=(json.dumps(self.secrets, indent=2, sort_keys=True) if self.secrets else None),
|
|
462
|
-
)
|
|
463
|
-
|
|
464
|
-
def _create_secret_version_conditionally(self):
|
|
465
|
-
"""
|
|
466
|
-
Create secret version only if one doesn't already exist or is empty.
|
|
467
|
-
|
|
468
|
-
This method implements smart secret management:
|
|
469
|
-
1. Attempts to read existing secret using data source
|
|
470
|
-
2. Only creates new version if secret is empty or doesn't exist
|
|
471
|
-
3. Preserves manual updates and rotations made outside Terraform
|
|
472
|
-
|
|
473
|
-
**Use Cases:**
|
|
474
|
-
- Initial deployment when no secret exists
|
|
475
|
-
- Secret exists but has no content (empty)
|
|
476
|
-
- Avoid overwriting manually rotated credentials
|
|
477
|
-
- Preserve additional keys added through AWS console/CLI
|
|
478
|
-
"""
|
|
479
|
-
try:
|
|
480
|
-
# Try to read existing secret version to check if it has content
|
|
481
|
-
existing_secret = DataAwsSecretsmanagerSecretVersion(
|
|
482
|
-
self,
|
|
483
|
-
self.secret_name + "_existing_check",
|
|
484
|
-
secret_id=self.secrets_manager_secret.id,
|
|
485
|
-
version_stage="AWSCURRENT"
|
|
486
|
-
)
|
|
487
|
-
|
|
488
|
-
# Create a conditional secret version
|
|
489
|
-
conditional_secret = SecretsmanagerSecretVersion(
|
|
490
|
-
self,
|
|
491
|
-
self.secret_name + "_version_conditional",
|
|
492
|
-
secret_id=self.secrets_manager_secret.id,
|
|
493
|
-
secret_string=json.dumps(self.secrets, indent=2, sort_keys=True) if self.secrets else None,
|
|
494
|
-
lifecycle={
|
|
495
|
-
"ignore_changes": ["secret_string"],
|
|
496
|
-
"create_before_destroy": False
|
|
497
|
-
}
|
|
498
|
-
)
|
|
499
|
-
|
|
500
|
-
# Add dependency to ensure secret exists before checking
|
|
501
|
-
conditional_secret.add_override("count",
|
|
502
|
-
"${length(try(jsondecode(data.aws_secretsmanager_secret_version." +
|
|
503
|
-
self.secret_name.replace("/", "_").replace("-", "_") + "_existing_check.secret_string), {})) == 0 ? 1 : 0}"
|
|
504
|
-
)
|
|
505
|
-
|
|
506
|
-
except Exception:
|
|
507
|
-
# If data source fails (secret doesn't exist), create the version
|
|
508
|
-
SecretsmanagerSecretVersion(
|
|
509
|
-
self,
|
|
510
|
-
self.secret_name + "_version_fallback",
|
|
511
|
-
secret_id=self.secrets_manager_secret.id,
|
|
512
|
-
secret_string=json.dumps(self.secrets, indent=2, sort_keys=True) if self.secrets else None,
|
|
513
|
-
)
|
|
514
|
-
|
|
515
|
-
def _create_secret_version_with_lifecycle_ignore(self):
|
|
516
|
-
"""
|
|
517
|
-
Create secret version with lifecycle rule to ignore future changes.
|
|
518
|
-
|
|
519
|
-
This is a simpler approach that:
|
|
520
|
-
1. Creates the secret version with initial values on first deployment
|
|
521
|
-
2. Ignores all future changes to the secret_string
|
|
522
|
-
3. Allows manual updates in AWS console/CLI to persist
|
|
523
|
-
|
|
524
|
-
**Pros:**
|
|
525
|
-
- Simple implementation
|
|
526
|
-
- Reliable behavior
|
|
527
|
-
- Preserves manual changes after initial creation
|
|
528
|
-
|
|
529
|
-
**Cons:**
|
|
530
|
-
- Cannot update secrets through Terraform after initial deployment
|
|
531
|
-
- Requires manual secret management for infrastructure changes
|
|
532
|
-
"""
|
|
533
|
-
secret_version = SecretsmanagerSecretVersion(
|
|
534
|
-
self,
|
|
535
|
-
self.secret_name + "_version_ignored",
|
|
536
|
-
secret_id=self.secrets_manager_secret.id,
|
|
537
|
-
secret_string=json.dumps(self.secrets, indent=2, sort_keys=True) if self.secrets else None,
|
|
538
|
-
)
|
|
539
|
-
|
|
540
|
-
# Add lifecycle rule to ignore changes to secret_string
|
|
541
|
-
secret_version.add_override("lifecycle", {
|
|
542
|
-
"ignore_changes": ["secret_string"]
|
|
543
|
-
})
|
|
544
|
-
|
|
545
|
-
def execute_post_apply_scripts(self):
|
|
546
|
-
"""
|
|
547
|
-
Execute post-apply scripts using local-exec provisioners.
|
|
548
|
-
|
|
549
|
-
Creates a null resource with local-exec provisioner for each script
|
|
550
|
-
in the post_apply_scripts list. Scripts are executed sequentially
|
|
551
|
-
after all other infrastructure resources are created.
|
|
552
|
-
|
|
553
|
-
**Script Execution:**
|
|
554
|
-
|
|
555
|
-
* Each script runs as a separate null resource
|
|
556
|
-
* Scripts execute in the order they appear in the list
|
|
557
|
-
* Failures in scripts don't prevent deployment completion
|
|
558
|
-
* All scripts depend on core infrastructure being ready
|
|
559
|
-
|
|
560
|
-
**Error Handling:**
|
|
561
|
-
|
|
562
|
-
* Scripts use "on_failure: continue" to prevent deployment failures
|
|
563
|
-
* Failed scripts are logged but don't halt the deployment process
|
|
564
|
-
* Manual intervention may be required if critical scripts fail
|
|
565
|
-
|
|
566
|
-
.. note::
|
|
567
|
-
Post-apply scripts can be provided via the postApplyScripts parameter
|
|
568
|
-
during stack initialization. If no scripts are provided, this method
|
|
569
|
-
returns without creating any resources.
|
|
570
|
-
|
|
571
|
-
.. warning::
|
|
572
|
-
Scripts have access to the local environment where Terraform runs.
|
|
573
|
-
Ensure scripts are safe and don't expose sensitive information.
|
|
574
|
-
"""
|
|
575
|
-
if not self.post_apply_scripts:
|
|
576
|
-
return
|
|
577
|
-
|
|
578
|
-
# Collect dependencies for post-apply scripts
|
|
579
|
-
dependencies = []
|
|
580
|
-
if hasattr(self, 'secrets_manager_secret'):
|
|
581
|
-
dependencies.append(self.secrets_manager_secret)
|
|
582
|
-
|
|
583
|
-
# Create a null resource for each post-apply script
|
|
584
|
-
for i, script in enumerate(self.post_apply_scripts):
|
|
585
|
-
script_resource = NullResource(
|
|
586
|
-
self,
|
|
587
|
-
f"post_apply_script_{i}",
|
|
588
|
-
depends_on=dependencies if dependencies else None
|
|
589
|
-
)
|
|
590
|
-
|
|
591
|
-
# Add provisioner using override
|
|
592
|
-
script_resource.add_override("provisioner", [{
|
|
593
|
-
"local-exec": {
|
|
594
|
-
"command": script,
|
|
595
|
-
"on_failure": "continue"
|
|
596
|
-
}
|
|
597
|
-
}])
|
|
598
|
-
|
|
599
|
-
# ==================== UTILITY METHODS ====================
|
|
600
|
-
|
|
601
|
-
def has_flag(self, flag_value):
|
|
602
|
-
"""
|
|
603
|
-
Check if a specific flag is set in the configuration.
|
|
604
|
-
|
|
605
|
-
:param flag_value: The flag value to check for
|
|
606
|
-
:type flag_value: str
|
|
607
|
-
:returns: True if the flag is set, False otherwise
|
|
608
|
-
:rtype: bool
|
|
609
|
-
"""
|
|
610
|
-
return flag_value in self.flags
|
|
611
|
-
|
|
612
|
-
def clean_hyphens(self, text):
|
|
613
|
-
"""
|
|
614
|
-
Remove hyphens from text for database/resource naming.
|
|
615
|
-
|
|
616
|
-
:param text: Text to clean
|
|
617
|
-
:type text: str
|
|
618
|
-
:returns: Text with hyphens replaced by underscores
|
|
619
|
-
:rtype: str
|
|
620
|
-
"""
|
|
621
|
-
return text.replace("-", "_")
|
|
622
|
-
|
|
623
|
-
def properize_s3_bucketname(self, bucket_name):
|
|
624
|
-
"""
|
|
625
|
-
Ensure S3 bucket name follows AWS naming conventions.
|
|
626
|
-
|
|
627
|
-
:param bucket_name: Proposed bucket name
|
|
628
|
-
:type bucket_name: str
|
|
629
|
-
:returns: Properly formatted bucket name
|
|
630
|
-
:rtype: str
|
|
631
|
-
"""
|
|
632
|
-
# Convert to lowercase and replace invalid characters
|
|
633
|
-
clean_name = bucket_name.lower().replace("_", "-")
|
|
634
|
-
# Ensure it starts and ends with alphanumeric characters
|
|
635
|
-
clean_name = clean_name.strip("-.")
|
|
636
|
-
return clean_name
|
|
637
|
-
|
|
638
|
-
# ==================== SHARED OUTPUT HELPERS ====================
|
|
639
|
-
|
|
640
|
-
def create_iam_outputs(self):
|
|
641
|
-
"""
|
|
642
|
-
Create standard IAM-related Terraform outputs.
|
|
643
|
-
|
|
644
|
-
This helper method can be called by subclasses to create
|
|
645
|
-
consistent IAM outputs across all Lightsail implementations.
|
|
646
|
-
"""
|
|
647
|
-
# IAM credentials (sensitive)
|
|
648
|
-
TerraformOutput(
|
|
649
|
-
self,
|
|
650
|
-
"iam_user_access_key",
|
|
651
|
-
value=self.service_key.id,
|
|
652
|
-
sensitive=True,
|
|
653
|
-
description="IAM user access key ID (sensitive)",
|
|
654
|
-
)
|
|
655
|
-
|
|
656
|
-
TerraformOutput(
|
|
657
|
-
self,
|
|
658
|
-
"iam_user_secret_key",
|
|
659
|
-
value=self.service_key.secret,
|
|
660
|
-
sensitive=True,
|
|
661
|
-
description="IAM user secret access key (sensitive)",
|
|
662
|
-
)
|
|
663
|
-
|
|
664
|
-
# Secret name for reference
|
|
665
|
-
TerraformOutput(
|
|
666
|
-
self,
|
|
667
|
-
"secrets_manager_secret_name",
|
|
668
|
-
value=self.secret_name,
|
|
669
|
-
description="AWS Secrets Manager secret name containing all credentials",
|
|
670
|
-
)
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AWS Lightsail Base Standalone Infrastructure Stack
|
|
3
|
+
==================================================
|
|
4
|
+
|
|
5
|
+
This module provides a standalone base class for AWS Lightsail infrastructure
|
|
6
|
+
stacks using CDKTF. It avoids AWSArchitectureBase and its compliance bootstrap
|
|
7
|
+
resources, while keeping IAM/Secrets/post-apply helpers used by Lightsail stacks.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from cdktf import TerraformStack
|
|
12
|
+
|
|
13
|
+
from cdktf_cdktf_provider_aws.provider import AwsProvider
|
|
14
|
+
from cdktf_cdktf_provider_aws import (
|
|
15
|
+
iam_group,
|
|
16
|
+
iam_user,
|
|
17
|
+
iam_access_key,
|
|
18
|
+
iam_user_group_membership,
|
|
19
|
+
iam_group_policy,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
from cdktf_cdktf_provider_random.provider import RandomProvider
|
|
23
|
+
|
|
24
|
+
from cdktf_cdktf_provider_null.provider import NullProvider
|
|
25
|
+
|
|
26
|
+
from .LightsailFlags import BaseLightsailArchitectureFlags
|
|
27
|
+
from .LightsailMixins import LightsailBaseMixin
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class LightsailBaseStandalone(LightsailBaseMixin, TerraformStack):
|
|
31
|
+
"""
|
|
32
|
+
Standalone base class for Lightsail stacks without AWSArchitectureBase.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
resources = {}
|
|
36
|
+
default_post_apply_scripts = []
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def get_architecture_flags():
|
|
40
|
+
return BaseLightsailArchitectureFlags
|
|
41
|
+
|
|
42
|
+
def __init__(self, scope, id, **kwargs):
|
|
43
|
+
self.region = kwargs.get("region", "us-east-1")
|
|
44
|
+
self.environment = kwargs.get("environment", "dev")
|
|
45
|
+
self.project_name = kwargs.get("project_name")
|
|
46
|
+
self.profile = kwargs.get("profile", "default")
|
|
47
|
+
|
|
48
|
+
if not self.project_name:
|
|
49
|
+
raise ValueError("project_name is required and cannot be empty")
|
|
50
|
+
|
|
51
|
+
super().__init__(scope, id)
|
|
52
|
+
|
|
53
|
+
self.flags = kwargs.get("flags", [])
|
|
54
|
+
self.post_apply_scripts = kwargs.get("postApplyScripts", []) or []
|
|
55
|
+
|
|
56
|
+
default_secret_name = f"{self.project_name}/{self.environment}/credentials"
|
|
57
|
+
self.secret_name = kwargs.get("secret_name", default_secret_name)
|
|
58
|
+
self.default_signature_version = kwargs.get("default_signature_version", "s3v4")
|
|
59
|
+
self.default_extra_secret_env = kwargs.get("default_extra_secret_env", "SECRET_STRING")
|
|
60
|
+
|
|
61
|
+
default_bucket_name = self.properize_s3_bucketname(f"{self.region}-{self.project_name}-tfstate")
|
|
62
|
+
self.state_bucket_name = kwargs.get("state_bucket_name", default_bucket_name)
|
|
63
|
+
|
|
64
|
+
self.secrets = {}
|
|
65
|
+
self.post_terraform_messages = []
|
|
66
|
+
self._post_plan_guidance: list[str] = []
|
|
67
|
+
|
|
68
|
+
self._initialize_providers()
|
|
69
|
+
self._set_default_post_apply_scripts()
|
|
70
|
+
self._create_infrastructure_components()
|
|
71
|
+
|
|
72
|
+
def _initialize_providers(self):
|
|
73
|
+
aws = AwsProvider(self, "aws", region=self.region, profile=self.profile)
|
|
74
|
+
self.resources["aws"] = aws
|
|
75
|
+
|
|
76
|
+
RandomProvider(self, "random")
|
|
77
|
+
self.resources["random"] = RandomProvider
|
|
78
|
+
|
|
79
|
+
NullProvider(self, "null")
|
|
80
|
+
self.resources["null"] = NullProvider
|
|
81
|
+
|
|
82
|
+
def _set_default_post_apply_scripts(self):
|
|
83
|
+
self.default_post_apply_scripts = [
|
|
84
|
+
"echo '============================================='",
|
|
85
|
+
"echo '✅ Deployment Completed Successfully'",
|
|
86
|
+
"echo '============================================='",
|
|
87
|
+
f"echo '🏗️ Project: {self.project_name}'",
|
|
88
|
+
f"echo '🌍 Environment: {self.environment}'",
|
|
89
|
+
f"echo '📍 Region: {self.region}'",
|
|
90
|
+
"echo '============================================='",
|
|
91
|
+
"echo '💻 System Information:'",
|
|
92
|
+
"echo ' - OS: '$(uname -s)",
|
|
93
|
+
"echo ' - Architecture: '$(uname -m)",
|
|
94
|
+
"echo ' - User: '$(whoami)",
|
|
95
|
+
"echo ' - Working Directory: '$(pwd)",
|
|
96
|
+
"echo '============================================='",
|
|
97
|
+
"echo '✅ Post-deployment scripts execution started'",
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
if BaseLightsailArchitectureFlags.SKIP_DEFAULT_POST_APPLY_SCRIPTS.value in self.flags:
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
self.post_apply_scripts = self.default_post_apply_scripts + self.post_apply_scripts
|
|
104
|
+
|
|
105
|
+
def _create_infrastructure_components(self):
|
|
106
|
+
self.create_iam_resources()
|
|
107
|
+
self.create_lightsail_resources()
|
|
108
|
+
self.create_security_resources()
|
|
109
|
+
self.execute_post_apply_scripts()
|
|
110
|
+
self.create_outputs()
|
|
111
|
+
|
|
112
|
+
def create_lightsail_resources(self):
|
|
113
|
+
raise NotImplementedError("create_lightsail_resources must be implemented by subclasses")
|
|
114
|
+
|
|
115
|
+
def create_outputs(self):
|
|
116
|
+
raise NotImplementedError("create_outputs must be implemented by subclasses")
|
|
117
|
+
|
|
118
|
+
def create_iam_resources(self):
|
|
119
|
+
user_name = f"{self.project_name}-service-user"
|
|
120
|
+
group_name = f"{self.project_name}-group"
|
|
121
|
+
|
|
122
|
+
self.service_group = iam_group.IamGroup(self, "service_group", name=group_name)
|
|
123
|
+
self.service_user = iam_user.IamUser(self, "service_user", name=user_name)
|
|
124
|
+
|
|
125
|
+
iam_user_group_membership.IamUserGroupMembership(
|
|
126
|
+
self,
|
|
127
|
+
"service_user_group_membership",
|
|
128
|
+
user=self.service_user.name,
|
|
129
|
+
groups=[self.service_group.name],
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
self.service_key = iam_access_key.IamAccessKey(
|
|
133
|
+
self, "service_key", user=self.service_user.name
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
self.service_policy = self.create_iam_policy_from_file()
|
|
138
|
+
self.resources["iam_policy"] = self.service_policy
|
|
139
|
+
except FileNotFoundError:
|
|
140
|
+
pass
|
|
141
|
+
|
|
142
|
+
def create_iam_policy_from_file(self, file_path="iam_policy.json"):
|
|
143
|
+
file_to_open = os.path.join(os.path.dirname(__file__), file_path)
|
|
144
|
+
|
|
145
|
+
with open(file_to_open, "r") as f:
|
|
146
|
+
policy = f.read()
|
|
147
|
+
|
|
148
|
+
return iam_group_policy.IamGroupPolicy(
|
|
149
|
+
self,
|
|
150
|
+
f"{self.project_name}-{self.environment}-service-policy",
|
|
151
|
+
name=f"{self.project_name}-{self.environment}-service-policy",
|
|
152
|
+
group=self.service_group.name,
|
|
153
|
+
policy=policy,
|
|
154
|
+
)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AWS Lightsail Mini Infrastructure Stack
|
|
3
|
+
======================================
|
|
4
|
+
|
|
5
|
+
This module provides a comprehensive AWS Lightsail infrastructure deployment stack
|
|
6
|
+
using CDKTF (Cloud Development Kit for Terraform) with Python.
|
|
7
|
+
|
|
8
|
+
The stack includes:
|
|
9
|
+
* Lightsail Container Service with automatic custom domain attachment
|
|
10
|
+
* PostgreSQL Database (optional)
|
|
11
|
+
* DNS management with CNAME records
|
|
12
|
+
* SSL certificate management with automatic validation
|
|
13
|
+
* IAM resources for service access
|
|
14
|
+
* S3 bucket for application data
|
|
15
|
+
* Secrets Manager for credential storage
|
|
16
|
+
|
|
17
|
+
:author: Generated with GitHub Copilot
|
|
18
|
+
:version: 1.0.0
|
|
19
|
+
:license: MIT
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
#region specific imports
|
|
24
|
+
|
|
25
|
+
from constructs import Construct
|
|
26
|
+
|
|
27
|
+
# Import the base class
|
|
28
|
+
from .LightsailBase import LightsailBase
|
|
29
|
+
from .LightsailFlags import ContainerArchitectureFlags
|
|
30
|
+
from .LightsailMixins import LightsailContainerMixin
|
|
31
|
+
|
|
32
|
+
#endregion
|
|
33
|
+
|
|
34
|
+
#region AWS Provider and Resources
|
|
35
|
+
from cdktf_cdktf_provider_aws.provider import AwsProvider
|
|
36
|
+
from cdktf_cdktf_provider_aws import (
|
|
37
|
+
cloudfront_distribution,
|
|
38
|
+
)
|
|
39
|
+
#endregion
|
|
40
|
+
|
|
41
|
+
# AWS WAF (currently unused but imported for future use)
|
|
42
|
+
from cdktf_cdktf_provider_aws.wafv2_web_acl import (
|
|
43
|
+
Wafv2WebAcl,
|
|
44
|
+
Wafv2WebAclDefaultAction,
|
|
45
|
+
Wafv2WebAclRule,
|
|
46
|
+
Wafv2WebAclVisibilityConfig,
|
|
47
|
+
Wafv2WebAclDefaultActionAllow,
|
|
48
|
+
Wafv2WebAclRuleOverrideAction,
|
|
49
|
+
Wafv2WebAclRuleOverrideActionNone,
|
|
50
|
+
Wafv2WebAclRuleOverrideActionCount,
|
|
51
|
+
Wafv2WebAclRuleVisibilityConfig,
|
|
52
|
+
)
|
|
53
|
+
from cdktf_cdktf_provider_aws.wafv2_web_acl_association import Wafv2WebAclAssociation
|
|
54
|
+
from cdktf_cdktf_provider_aws.wafv2_rule_group import Wafv2RuleGroupRuleVisibilityConfig
|
|
55
|
+
|
|
56
|
+
#endregion
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
ArchitectureFlags = ContainerArchitectureFlags
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class LightsailContainerStack(LightsailContainerMixin, LightsailBase):
|
|
64
|
+
"""
|
|
65
|
+
AWS Lightsail Mini Infrastructure Stack.
|
|
66
|
+
|
|
67
|
+
A comprehensive infrastructure stack that deploys:
|
|
68
|
+
* Lightsail Container Service with custom domain support
|
|
69
|
+
* PostgreSQL database (optional)
|
|
70
|
+
* IAM resources and S3 storage
|
|
71
|
+
|
|
72
|
+
:param scope: The construct scope
|
|
73
|
+
:param id: The construct ID
|
|
74
|
+
:param kwargs: Configuration parameters including region, domains, flags, etc.
|
|
75
|
+
|
|
76
|
+
Example:
|
|
77
|
+
>>> stack = LightsailContainerStack(
|
|
78
|
+
... app, "my-stack",
|
|
79
|
+
... region="ca-central-1",
|
|
80
|
+
... domains=["app.example.com"],
|
|
81
|
+
... project_name="my-app",
|
|
82
|
+
... postApplyScripts=[
|
|
83
|
+
... "echo 'Deployment completed'",
|
|
84
|
+
... "curl -X POST https://webhook.example.com/notify"
|
|
85
|
+
... ]
|
|
86
|
+
... )
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
@staticmethod
|
|
90
|
+
def get_architecture_flags():
|
|
91
|
+
"""
|
|
92
|
+
Get the ArchitectureFlags enum for configuration.
|
|
93
|
+
|
|
94
|
+
:returns: ArchitectureFlags enum class
|
|
95
|
+
:rtype: type[ArchitectureFlags]
|
|
96
|
+
"""
|
|
97
|
+
return ContainerArchitectureFlags
|
|
98
|
+
|
|
99
|
+
def __init__(self, scope, id, **kwargs):
|
|
100
|
+
"""
|
|
101
|
+
Initialize the AWS Lightsail Mini Infrastructure Stack.
|
|
102
|
+
|
|
103
|
+
:param scope: The construct scope
|
|
104
|
+
:param id: Unique identifier for this stack
|
|
105
|
+
:param kwargs: Configuration parameters
|
|
106
|
+
|
|
107
|
+
**Configuration Parameters:**
|
|
108
|
+
|
|
109
|
+
:param region: AWS region (default: "us-east-1")
|
|
110
|
+
:param environment: Environment name (default: "dev")
|
|
111
|
+
:param project_name: Project identifier (default: "bb-aws-lightsail-mini-v1a-app")
|
|
112
|
+
:param domain_name: Primary domain name
|
|
113
|
+
:param domains: List of custom domains to configure
|
|
114
|
+
:param flags: List of ArchitectureFlags to modify behavior
|
|
115
|
+
:param profile: AWS profile to use (default: "default")
|
|
116
|
+
:param postApplyScripts: List of shell commands to execute after deployment
|
|
117
|
+
|
|
118
|
+
.. warning::
|
|
119
|
+
Lightsail domain operations must use us-east-1 region regardless of
|
|
120
|
+
the main stack region.
|
|
121
|
+
"""
|
|
122
|
+
# Set container-specific defaults
|
|
123
|
+
if "project_name" not in kwargs:
|
|
124
|
+
kwargs["project_name"] = "bb-aws-lightsail-mini-v1a-app"
|
|
125
|
+
|
|
126
|
+
# Call parent constructor which handles all the base initialization
|
|
127
|
+
super().__init__(scope, id, **kwargs)
|
|
128
|
+
|
|
129
|
+
# ===== Container-Specific Configuration =====
|
|
130
|
+
self.domains = kwargs.get("domains", []) or []
|
|
131
|
+
|
|
132
|
+
# ===== Database Configuration =====
|
|
133
|
+
self.default_db_name = kwargs.get("default_db_name", self.project_name)
|
|
134
|
+
self.default_db_username = kwargs.get("default_db_username", "dbadmin")
|
|
135
|
+
|
|
136
|
+
def _initialize_providers(self):
|
|
137
|
+
"""Initialize all required Terraform providers."""
|
|
138
|
+
# Call parent class to initialize base providers
|
|
139
|
+
super()._initialize_providers()
|
|
140
|
+
|
|
141
|
+
# Add Lightsail-specific provider for domain operations (must be us-east-1)
|
|
142
|
+
self.aws_domain_provider = AwsProvider(
|
|
143
|
+
self, "aws_domain", region="us-east-1", profile=self.profile, alias="domain"
|
|
144
|
+
)
|
|
145
|
+
self.resources["aws_domain"] = self.aws_domain_provider
|