BuzzerboyAWSLightsail 0.332.1__tar.gz → 0.334.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.
Files changed (30) hide show
  1. {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.334.1}/BuzzerboyAWSLightsail.egg-info/PKG-INFO +1 -1
  2. {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.334.1}/BuzzerboyAWSLightsail.egg-info/SOURCES.txt +2 -0
  3. {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.334.1}/BuzzerboyAWSLightsailStack/LightsailBase.py +4 -331
  4. buzzerboyawslightsail-0.334.1/BuzzerboyAWSLightsailStack/LightsailBaseStandalone.py +154 -0
  5. buzzerboyawslightsail-0.334.1/BuzzerboyAWSLightsailStack/LightsailContainer.py +145 -0
  6. buzzerboyawslightsail-0.334.1/BuzzerboyAWSLightsailStack/LightsailContainerStandalone.py +149 -0
  7. buzzerboyawslightsail-0.334.1/BuzzerboyAWSLightsailStack/LightsailDatabase.py +121 -0
  8. buzzerboyawslightsail-0.334.1/BuzzerboyAWSLightsailStack/LightsailDatabaseStandalone.py +121 -0
  9. buzzerboyawslightsail-0.334.1/BuzzerboyAWSLightsailStack/LightsailFlags.py +38 -0
  10. buzzerboyawslightsail-0.334.1/BuzzerboyAWSLightsailStack/LightsailMixins.py +542 -0
  11. {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.334.1}/PKG-INFO +1 -1
  12. {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.334.1}/pyproject.toml +1 -1
  13. buzzerboyawslightsail-0.332.1/BuzzerboyAWSLightsailStack/LightsailBaseStandalone.py +0 -318
  14. buzzerboyawslightsail-0.332.1/BuzzerboyAWSLightsailStack/LightsailContainer.py +0 -381
  15. buzzerboyawslightsail-0.332.1/BuzzerboyAWSLightsailStack/LightsailContainerStandalone.py +0 -385
  16. buzzerboyawslightsail-0.332.1/BuzzerboyAWSLightsailStack/LightsailDatabase.py +0 -484
  17. buzzerboyawslightsail-0.332.1/BuzzerboyAWSLightsailStack/LightsailDatabaseStandalone.py +0 -484
  18. {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.334.1}/BuzzerboyAWSLightsail.egg-info/dependency_links.txt +0 -0
  19. {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.334.1}/BuzzerboyAWSLightsail.egg-info/requires.txt +0 -0
  20. {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.334.1}/BuzzerboyAWSLightsail.egg-info/top_level.txt +0 -0
  21. {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.334.1}/BuzzerboyAWSLightsailStack/ArchitectureMaker.py +0 -0
  22. {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.334.1}/BuzzerboyAWSLightsailStack/LightSailPostDeploy.py +0 -0
  23. {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.334.1}/BuzzerboyAWSLightsailStack/LightsailAIContainer.py +0 -0
  24. {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.334.1}/BuzzerboyAWSLightsailStack/__init__.py +0 -0
  25. {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.334.1}/LICENSE +0 -0
  26. {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.334.1}/MANIFEST.in +0 -0
  27. {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.334.1}/README.md +0 -0
  28. {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.334.1}/requirements.txt +0 -0
  29. {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.334.1}/setup.cfg +0 -0
  30. {buzzerboyawslightsail-0.332.1 → buzzerboyawslightsail-0.334.1}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: BuzzerboyAWSLightsail
3
- Version: 0.332.1
3
+ Version: 0.334.1
4
4
  Summary: Buzzerboy Architecture for Deploying Web Applications on AWS LightSail
5
5
  Home-page: https://www.buzzerboy.com/
6
6
  Author: Buzzerboy Inc
@@ -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
- #region Random Provider and Resources
50
- from cdktf_cdktf_provider_random import password
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
- # Null Provider for local-exec provisioner
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