BuzzerboyAWSLightsail 0.331.1__tar.gz → 0.332.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 (24) hide show
  1. {buzzerboyawslightsail-0.331.1 → buzzerboyawslightsail-0.332.1}/BuzzerboyAWSLightsail.egg-info/PKG-INFO +91 -1
  2. {buzzerboyawslightsail-0.331.1 → buzzerboyawslightsail-0.332.1}/BuzzerboyAWSLightsail.egg-info/SOURCES.txt +4 -0
  3. buzzerboyawslightsail-0.332.1/BuzzerboyAWSLightsailStack/ArchitectureMaker.py +229 -0
  4. {buzzerboyawslightsail-0.331.1 → buzzerboyawslightsail-0.332.1}/BuzzerboyAWSLightsailStack/LightSailPostDeploy.py +0 -0
  5. {buzzerboyawslightsail-0.331.1 → buzzerboyawslightsail-0.332.1}/BuzzerboyAWSLightsailStack/LightsailAIContainer.py +0 -0
  6. {buzzerboyawslightsail-0.331.1 → buzzerboyawslightsail-0.332.1}/BuzzerboyAWSLightsailStack/LightsailBase.py +0 -0
  7. buzzerboyawslightsail-0.332.1/BuzzerboyAWSLightsailStack/LightsailBaseStandalone.py +318 -0
  8. buzzerboyawslightsail-0.332.1/BuzzerboyAWSLightsailStack/LightsailContainerStandalone.py +385 -0
  9. {buzzerboyawslightsail-0.331.1 → buzzerboyawslightsail-0.332.1}/BuzzerboyAWSLightsailStack/LightsailDatabase.py +0 -0
  10. buzzerboyawslightsail-0.332.1/BuzzerboyAWSLightsailStack/LightsailDatabaseStandalone.py +484 -0
  11. buzzerboyawslightsail-0.332.1/BuzzerboyAWSLightsailStack/__init__.py +1 -0
  12. {buzzerboyawslightsail-0.331.1 → buzzerboyawslightsail-0.332.1}/LICENSE +0 -0
  13. {buzzerboyawslightsail-0.331.1 → buzzerboyawslightsail-0.332.1}/PKG-INFO +91 -1
  14. {buzzerboyawslightsail-0.331.1 → buzzerboyawslightsail-0.332.1}/README.md +107 -17
  15. {buzzerboyawslightsail-0.331.1 → buzzerboyawslightsail-0.332.1}/pyproject.toml +1 -1
  16. buzzerboyawslightsail-0.331.1/BuzzerboyAWSLightsailStack/__init__.py +0 -0
  17. {buzzerboyawslightsail-0.331.1 → buzzerboyawslightsail-0.332.1}/BuzzerboyAWSLightsail.egg-info/dependency_links.txt +0 -0
  18. {buzzerboyawslightsail-0.331.1 → buzzerboyawslightsail-0.332.1}/BuzzerboyAWSLightsail.egg-info/requires.txt +0 -0
  19. {buzzerboyawslightsail-0.331.1 → buzzerboyawslightsail-0.332.1}/BuzzerboyAWSLightsail.egg-info/top_level.txt +0 -0
  20. {buzzerboyawslightsail-0.331.1 → buzzerboyawslightsail-0.332.1}/BuzzerboyAWSLightsailStack/LightsailContainer.py +0 -0
  21. {buzzerboyawslightsail-0.331.1 → buzzerboyawslightsail-0.332.1}/MANIFEST.in +0 -0
  22. {buzzerboyawslightsail-0.331.1 → buzzerboyawslightsail-0.332.1}/requirements.txt +0 -0
  23. {buzzerboyawslightsail-0.331.1 → buzzerboyawslightsail-0.332.1}/setup.cfg +0 -0
  24. {buzzerboyawslightsail-0.331.1 → buzzerboyawslightsail-0.332.1}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: BuzzerboyAWSLightsail
3
- Version: 0.331.1
3
+ Version: 0.332.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
@@ -163,6 +163,96 @@ cdktf deploy
163
163
  cdktf destroy
164
164
  ```
165
165
 
166
+ ## ✅ ArchitectureMaker Usage
167
+
168
+ The preferred entrypoint is `ArchitectureMaker`, which builds stacks from a
169
+ simple definition dict. Examples are mirrored in the samples directory.
170
+
171
+ ### Container + Database (default)
172
+
173
+ ```python
174
+ from BuzzerboyAWSLightsailStack import ArchitectureMaker
175
+
176
+ definition = {
177
+ "product": "bb",
178
+ "name": "sample-container-db",
179
+ "tier": "dev",
180
+ "organization": "buzzerboy",
181
+ "region": "us-east-1",
182
+ }
183
+
184
+ ArchitectureMaker.auto_main(definition, include_compliance=False)
185
+ ```
186
+
187
+ ### Container Only
188
+
189
+ ```python
190
+ from BuzzerboyAWSLightsailStack import ArchitectureMaker
191
+
192
+ definition = {
193
+ "product": "bb",
194
+ "name": "sample-container",
195
+ "tier": "dev",
196
+ "organization": "buzzerboy",
197
+ "region": "us-east-1",
198
+ }
199
+
200
+ ArchitectureMaker.auto_main_container_only(definition, include_compliance=False)
201
+ ```
202
+
203
+ ### Database Only
204
+
205
+ ```python
206
+ from BuzzerboyAWSLightsailStack import ArchitectureMaker
207
+
208
+ definition = {
209
+ "product": "bb",
210
+ "name": "sample-db",
211
+ "tier": "dev",
212
+ "organization": "buzzerboy",
213
+ "region": "us-east-1",
214
+ "databases": ["app_db", "analytics_db", "logging_db", "audit_db"],
215
+ }
216
+
217
+ ArchitectureMaker.auto_stack_db_only(definition, include_compliance=False)
218
+ ```
219
+
220
+ ### Example `cdktf.json`
221
+
222
+ ```json
223
+ {
224
+ "language": "python",
225
+ "app": "python main.py",
226
+ "projectId": "9bad9bb7-b21d-4513-9ce9-74a6e2f7e0d9",
227
+ "sendCrashReports": "true",
228
+ "terraformProviders": [
229
+ "aws@~> 5.0",
230
+ "random@~> 3.5",
231
+ "null@~> 3.2"
232
+ ],
233
+ "terraformModules": [],
234
+ "codeMakerOutput": "imports",
235
+ "context": {}
236
+ }
237
+ ```
238
+
239
+ ### Example `requirements.txt`
240
+
241
+ ```text
242
+ cdktf>=0.17.0,<1.0
243
+ constructs>=10.0.0,<11.0
244
+ cdktf-cdktf-provider-aws>=12.0.0
245
+ cdktf-cdktf-provider-random>=8.0.0
246
+ cdktf-cdktf-provider-null>=9.0.0
247
+ -e ../../BuzzerboyAWSLightsail
248
+ ```
249
+
250
+ ### Sample Paths
251
+
252
+ - `samples/ContainerAndDB`
253
+ - `samples/ContainerOnly`
254
+ - `samples/DBOnly`
255
+
166
256
  ## 🛠 Useful Commands
167
257
 
168
258
  | Command | Description |
@@ -9,9 +9,13 @@ BuzzerboyAWSLightsail.egg-info/SOURCES.txt
9
9
  BuzzerboyAWSLightsail.egg-info/dependency_links.txt
10
10
  BuzzerboyAWSLightsail.egg-info/requires.txt
11
11
  BuzzerboyAWSLightsail.egg-info/top_level.txt
12
+ BuzzerboyAWSLightsailStack/ArchitectureMaker.py
12
13
  BuzzerboyAWSLightsailStack/LightSailPostDeploy.py
13
14
  BuzzerboyAWSLightsailStack/LightsailAIContainer.py
14
15
  BuzzerboyAWSLightsailStack/LightsailBase.py
16
+ BuzzerboyAWSLightsailStack/LightsailBaseStandalone.py
15
17
  BuzzerboyAWSLightsailStack/LightsailContainer.py
18
+ BuzzerboyAWSLightsailStack/LightsailContainerStandalone.py
16
19
  BuzzerboyAWSLightsailStack/LightsailDatabase.py
20
+ BuzzerboyAWSLightsailStack/LightsailDatabaseStandalone.py
17
21
  BuzzerboyAWSLightsailStack/__init__.py
@@ -0,0 +1,229 @@
1
+ """
2
+ ArchitectureMaker
3
+ =================
4
+
5
+ Helper routines to build common Lightsail architectures using CDKTF.
6
+ """
7
+
8
+ from typing import Any, Dict, List
9
+
10
+ from cdktf import App
11
+
12
+ from BuzzerboyAWSLightsailStack.LightsailDatabase import (
13
+ LightsailDatabaseStack,
14
+ )
15
+ from BuzzerboyAWSLightsailStack.LightsailDatabaseStandalone import (
16
+ LightsailDatabaseStandaloneStack,
17
+ )
18
+ from BuzzerboyAWSLightsailStack.LightsailContainer import (
19
+ LightsailContainerStack,
20
+ )
21
+ from BuzzerboyAWSLightsailStack.LightsailContainerStandalone import (
22
+ LightsailContainerStandaloneStack,
23
+ )
24
+ from BuzzerboyArchetypeStack.BuzzerboyArchetype import BuzzerboyArchetype
25
+
26
+
27
+ class ArchitectureMaker:
28
+ """
29
+ Factory utilities for building Lightsail architectures from a definition dict.
30
+ """
31
+
32
+ @staticmethod
33
+ def auto_stack_db_only(definition: Dict[str, Any], include_compliance: bool = False) -> App:
34
+ """
35
+ Create a DB-only Lightsail stack from a definition dictionary.
36
+
37
+ Expected keys in definition:
38
+ - product (str)
39
+ - name or app (str)
40
+ - tier (str)
41
+ - organization (str)
42
+ - region (str)
43
+ - databases (list[str])
44
+ Optional keys:
45
+ - profile (str)
46
+ - db_instance_size (str)
47
+ - master_username (str)
48
+ - flags (list[str])
49
+ """
50
+ if not isinstance(definition, dict):
51
+ raise ValueError("definition must be a dictionary")
52
+
53
+ product = definition.get("product")
54
+ app_name = definition.get("name") or definition.get("app")
55
+ tier = definition.get("tier")
56
+ organization = definition.get("organization")
57
+ region = definition.get("region")
58
+ databases = definition.get("databases", [])
59
+
60
+ missing = [key for key, value in {
61
+ "product": product,
62
+ "name/app": app_name,
63
+ "tier": tier,
64
+ "organization": organization,
65
+ "region": region,
66
+ "databases": databases,
67
+ }.items() if not value]
68
+ if missing:
69
+ raise ValueError(f"definition is missing required keys: {', '.join(missing)}")
70
+
71
+ archetype = BuzzerboyArchetype(
72
+ product=product,
73
+ app=app_name,
74
+ tier=tier,
75
+ organization=organization,
76
+ region=region,
77
+ )
78
+
79
+ flags: List[str] = list(definition.get("flags", []))
80
+ flags = list(dict.fromkeys(flags))
81
+
82
+ app = App()
83
+
84
+ stack_class = LightsailDatabaseStack if include_compliance else LightsailDatabaseStandaloneStack
85
+ stack_class(
86
+ app,
87
+ f"{archetype.get_project_name()}-db-stack",
88
+ project_name=archetype.get_project_name(),
89
+ environment=archetype.get_tier(),
90
+ region=archetype.get_region(),
91
+ secret_name=archetype.get_secret_name(),
92
+ databases=databases,
93
+ profile=definition.get("profile", "default"),
94
+ db_instance_size=definition.get("db_instance_size", "micro_2_0"),
95
+ master_username=definition.get("master_username", "dbmasteruser"),
96
+ flags=flags,
97
+ )
98
+
99
+ app.synth()
100
+ return app
101
+
102
+ @staticmethod
103
+ def auto_main_container_only(definition: Dict[str, Any], include_compliance: bool = False) -> App:
104
+ """
105
+ Create a container-only Lightsail stack from a definition dictionary.
106
+
107
+ Expected keys in definition:
108
+ - product (str)
109
+ - name or app (str)
110
+ - tier (str)
111
+ - organization (str)
112
+ - region (str)
113
+ Optional keys:
114
+ - profile (str)
115
+ - flags (list[str])
116
+ """
117
+ if not isinstance(definition, dict):
118
+ raise ValueError("definition must be a dictionary")
119
+
120
+ product = definition.get("product")
121
+ app_name = definition.get("name") or definition.get("app")
122
+ tier = definition.get("tier")
123
+ organization = definition.get("organization")
124
+ region = definition.get("region")
125
+
126
+ missing = [key for key, value in {
127
+ "product": product,
128
+ "name/app": app_name,
129
+ "tier": tier,
130
+ "organization": organization,
131
+ "region": region,
132
+ }.items() if not value]
133
+ if missing:
134
+ raise ValueError(f"definition is missing required keys: {', '.join(missing)}")
135
+
136
+ archetype = BuzzerboyArchetype(
137
+ product=product,
138
+ app=app_name,
139
+ tier=tier,
140
+ organization=organization,
141
+ region=region,
142
+ )
143
+
144
+ flags: List[str] = list(definition.get("flags", []))
145
+ flags.extend([
146
+ "skip_database",
147
+ "skip_domain",
148
+ ])
149
+ flags = list(dict.fromkeys(flags))
150
+
151
+ app = App()
152
+
153
+ stack_class = LightsailContainerStack if include_compliance else LightsailContainerStandaloneStack
154
+ stack_class(
155
+ app,
156
+ f"{archetype.get_project_name()}-stack",
157
+ project_name=archetype.get_project_name(),
158
+ environment=archetype.get_tier(),
159
+ region=archetype.get_region(),
160
+ secret_name=archetype.get_secret_name(),
161
+ profile=definition.get("profile", "default"),
162
+ flags=flags,
163
+ )
164
+
165
+ app.synth()
166
+ return app
167
+
168
+ @staticmethod
169
+ def auto_main(definition: Dict[str, Any], include_compliance: bool = False) -> App:
170
+ """
171
+ Create a container + database Lightsail stack from a definition dictionary.
172
+
173
+ Expected keys in definition:
174
+ - product (str)
175
+ - name or app (str)
176
+ - tier (str)
177
+ - organization (str)
178
+ - region (str)
179
+ Optional keys:
180
+ - profile (str)
181
+ - flags (list[str])
182
+ """
183
+ if not isinstance(definition, dict):
184
+ raise ValueError("definition must be a dictionary")
185
+
186
+ product = definition.get("product")
187
+ app_name = definition.get("name") or definition.get("app")
188
+ tier = definition.get("tier")
189
+ organization = definition.get("organization")
190
+ region = definition.get("region")
191
+
192
+ missing = [key for key, value in {
193
+ "product": product,
194
+ "name/app": app_name,
195
+ "tier": tier,
196
+ "organization": organization,
197
+ "region": region,
198
+ }.items() if not value]
199
+ if missing:
200
+ raise ValueError(f"definition is missing required keys: {', '.join(missing)}")
201
+
202
+ archetype = BuzzerboyArchetype(
203
+ product=product,
204
+ app=app_name,
205
+ tier=tier,
206
+ organization=organization,
207
+ region=region,
208
+ )
209
+
210
+ flags: List[str] = list(definition.get("flags", []))
211
+ flags.append("skip_domain")
212
+ flags = list(dict.fromkeys(flags))
213
+
214
+ app = App()
215
+
216
+ stack_class = LightsailContainerStack if include_compliance else LightsailContainerStandaloneStack
217
+ stack_class(
218
+ app,
219
+ f"{archetype.get_project_name()}-stack",
220
+ project_name=archetype.get_project_name(),
221
+ environment=archetype.get_tier(),
222
+ region=archetype.get_region(),
223
+ secret_name=archetype.get_secret_name(),
224
+ profile=definition.get("profile", "default"),
225
+ flags=flags,
226
+ )
227
+
228
+ app.synth()
229
+ return app
@@ -0,0 +1,318 @@
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
+ import json
12
+ from enum import Enum
13
+ from cdktf import TerraformStack, TerraformOutput
14
+
15
+ from cdktf_cdktf_provider_aws.provider import AwsProvider
16
+ from cdktf_cdktf_provider_aws import (
17
+ iam_group,
18
+ iam_user,
19
+ iam_access_key,
20
+ iam_user_group_membership,
21
+ iam_group_policy,
22
+ )
23
+
24
+ from cdktf_cdktf_provider_random import password
25
+ from cdktf_cdktf_provider_random.provider import RandomProvider
26
+
27
+ from cdktf_cdktf_provider_null.provider import NullProvider
28
+ from cdktf_cdktf_provider_null.resource import Resource as NullResource
29
+
30
+ from cdktf_cdktf_provider_aws.secretsmanager_secret import SecretsmanagerSecret
31
+ from cdktf_cdktf_provider_aws.secretsmanager_secret_version import SecretsmanagerSecretVersion
32
+ from cdktf_cdktf_provider_aws.data_aws_secretsmanager_secret_version import DataAwsSecretsmanagerSecretVersion
33
+
34
+
35
+ class BaseLightsailArchitectureFlags(Enum):
36
+ """
37
+ Base architecture configuration flags for optional components.
38
+
39
+ :param SKIP_DEFAULT_POST_APPLY_SCRIPTS: Skip default post-apply scripts
40
+ :param PRESERVE_EXISTING_SECRETS: Don't overwrite existing secret versions
41
+ :param IGNORE_SECRET_CHANGES: Ignore all changes to secret after initial creation
42
+ """
43
+
44
+ SKIP_DEFAULT_POST_APPLY_SCRIPTS = "skip_default_post_apply_scripts"
45
+ PRESERVE_EXISTING_SECRETS = "preserve_existing_secrets"
46
+ IGNORE_SECRET_CHANGES = "ignore_secret_changes"
47
+
48
+
49
+ class LightsailBaseStandalone(TerraformStack):
50
+ """
51
+ Standalone base class for Lightsail stacks without AWSArchitectureBase.
52
+ """
53
+
54
+ resources = {}
55
+ default_post_apply_scripts = []
56
+
57
+ @staticmethod
58
+ def get_architecture_flags():
59
+ return BaseLightsailArchitectureFlags
60
+
61
+ def __init__(self, scope, id, **kwargs):
62
+ self.region = kwargs.get("region", "us-east-1")
63
+ self.environment = kwargs.get("environment", "dev")
64
+ self.project_name = kwargs.get("project_name")
65
+ self.profile = kwargs.get("profile", "default")
66
+
67
+ if not self.project_name:
68
+ raise ValueError("project_name is required and cannot be empty")
69
+
70
+ super().__init__(scope, id)
71
+
72
+ self.flags = kwargs.get("flags", [])
73
+ self.post_apply_scripts = kwargs.get("postApplyScripts", []) or []
74
+
75
+ default_secret_name = f"{self.project_name}/{self.environment}/credentials"
76
+ self.secret_name = kwargs.get("secret_name", default_secret_name)
77
+ self.default_signature_version = kwargs.get("default_signature_version", "s3v4")
78
+ self.default_extra_secret_env = kwargs.get("default_extra_secret_env", "SECRET_STRING")
79
+
80
+ default_bucket_name = self.properize_s3_bucketname(f"{self.region}-{self.project_name}-tfstate")
81
+ self.state_bucket_name = kwargs.get("state_bucket_name", default_bucket_name)
82
+
83
+ self.secrets = {}
84
+ self.post_terraform_messages = []
85
+ self._post_plan_guidance: list[str] = []
86
+
87
+ self._initialize_providers()
88
+ self._set_default_post_apply_scripts()
89
+ self._create_infrastructure_components()
90
+
91
+ def _initialize_providers(self):
92
+ aws = AwsProvider(self, "aws", region=self.region, profile=self.profile)
93
+ self.resources["aws"] = aws
94
+
95
+ RandomProvider(self, "random")
96
+ self.resources["random"] = RandomProvider
97
+
98
+ NullProvider(self, "null")
99
+ self.resources["null"] = NullProvider
100
+
101
+ def _set_default_post_apply_scripts(self):
102
+ self.default_post_apply_scripts = [
103
+ "echo '============================================='",
104
+ "echo '✅ Deployment Completed Successfully'",
105
+ "echo '============================================='",
106
+ f"echo '🏗️ Project: {self.project_name}'",
107
+ f"echo '🌍 Environment: {self.environment}'",
108
+ f"echo '📍 Region: {self.region}'",
109
+ "echo '============================================='",
110
+ "echo '💻 System Information:'",
111
+ "echo ' - OS: '$(uname -s)",
112
+ "echo ' - Architecture: '$(uname -m)",
113
+ "echo ' - User: '$(whoami)",
114
+ "echo ' - Working Directory: '$(pwd)",
115
+ "echo '============================================='",
116
+ "echo '✅ Post-deployment scripts execution started'",
117
+ ]
118
+
119
+ if BaseLightsailArchitectureFlags.SKIP_DEFAULT_POST_APPLY_SCRIPTS.value in self.flags:
120
+ return
121
+
122
+ self.post_apply_scripts = self.default_post_apply_scripts + self.post_apply_scripts
123
+
124
+ def _create_infrastructure_components(self):
125
+ self.create_iam_resources()
126
+ self.create_lightsail_resources()
127
+ self.create_security_resources()
128
+ self.execute_post_apply_scripts()
129
+ self.create_outputs()
130
+
131
+ def create_lightsail_resources(self):
132
+ raise NotImplementedError("create_lightsail_resources must be implemented by subclasses")
133
+
134
+ def create_outputs(self):
135
+ raise NotImplementedError("create_outputs must be implemented by subclasses")
136
+
137
+ def create_iam_resources(self):
138
+ user_name = f"{self.project_name}-service-user"
139
+ group_name = f"{self.project_name}-group"
140
+
141
+ self.service_group = iam_group.IamGroup(self, "service_group", name=group_name)
142
+ self.service_user = iam_user.IamUser(self, "service_user", name=user_name)
143
+
144
+ iam_user_group_membership.IamUserGroupMembership(
145
+ self,
146
+ "service_user_group_membership",
147
+ user=self.service_user.name,
148
+ groups=[self.service_group.name],
149
+ )
150
+
151
+ self.service_key = iam_access_key.IamAccessKey(
152
+ self, "service_key", user=self.service_user.name
153
+ )
154
+
155
+ try:
156
+ self.service_policy = self.create_iam_policy_from_file()
157
+ self.resources["iam_policy"] = self.service_policy
158
+ except FileNotFoundError:
159
+ pass
160
+
161
+ def create_iam_policy_from_file(self, file_path="iam_policy.json"):
162
+ file_to_open = os.path.join(os.path.dirname(__file__), file_path)
163
+
164
+ with open(file_to_open, "r") as f:
165
+ policy = f.read()
166
+
167
+ return iam_group_policy.IamGroupPolicy(
168
+ self,
169
+ f"{self.project_name}-{self.environment}-service-policy",
170
+ name=f"{self.project_name}-{self.environment}-service-policy",
171
+ group=self.service_group.name,
172
+ policy=policy,
173
+ )
174
+
175
+ def get_extra_secret_env(self, env_var_name=None):
176
+ if env_var_name is None:
177
+ env_var_name = self.default_extra_secret_env
178
+
179
+ extra_secret_env = os.environ.get(env_var_name, None)
180
+
181
+ if extra_secret_env:
182
+ try:
183
+ extra_secret_json = json.loads(extra_secret_env)
184
+ for key, value in extra_secret_json.items():
185
+ if key not in self.secrets:
186
+ self.secrets[key] = value
187
+ except json.JSONDecodeError:
188
+ pass
189
+
190
+ def create_security_resources(self):
191
+ self.secrets_manager_secret = SecretsmanagerSecret(self, self.secret_name, name=f"{self.secret_name}")
192
+ self.resources["secretsmanager_secret"] = self.secrets_manager_secret
193
+
194
+ self.secrets.update({
195
+ "service_user_access_key": self.service_key.id,
196
+ "service_user_secret_key": self.service_key.secret,
197
+ "access_key": self.service_key.id,
198
+ "secret_access_key": self.service_key.secret,
199
+ "region_name": self.region,
200
+ "signature_version": self.default_signature_version
201
+ })
202
+
203
+ self.get_extra_secret_env()
204
+
205
+ if BaseLightsailArchitectureFlags.PRESERVE_EXISTING_SECRETS.value in self.flags:
206
+ self._create_secret_version_conditionally()
207
+ elif BaseLightsailArchitectureFlags.IGNORE_SECRET_CHANGES.value in self.flags:
208
+ self._create_secret_version_with_lifecycle_ignore()
209
+ else:
210
+ SecretsmanagerSecretVersion(
211
+ self,
212
+ self.secret_name + "_version",
213
+ secret_id=self.secrets_manager_secret.id,
214
+ secret_string=(json.dumps(self.secrets, indent=2, sort_keys=True) if self.secrets else None),
215
+ )
216
+
217
+ def _create_secret_version_conditionally(self):
218
+ try:
219
+ DataAwsSecretsmanagerSecretVersion(
220
+ self,
221
+ self.secret_name + "_existing_check",
222
+ secret_id=self.secrets_manager_secret.id,
223
+ version_stage="AWSCURRENT"
224
+ )
225
+
226
+ conditional_secret = SecretsmanagerSecretVersion(
227
+ self,
228
+ self.secret_name + "_version_conditional",
229
+ secret_id=self.secrets_manager_secret.id,
230
+ secret_string=json.dumps(self.secrets, indent=2, sort_keys=True) if self.secrets else None,
231
+ lifecycle={
232
+ "ignore_changes": ["secret_string"],
233
+ "create_before_destroy": False
234
+ }
235
+ )
236
+
237
+ conditional_secret.add_override(
238
+ "count",
239
+ "${length(try(jsondecode(data.aws_secretsmanager_secret_version." +
240
+ self.secret_name.replace("/", "_").replace("-", "_") + "_existing_check.secret_string), {})) == 0 ? 1 : 0}"
241
+ )
242
+
243
+ except Exception:
244
+ SecretsmanagerSecretVersion(
245
+ self,
246
+ self.secret_name + "_version_fallback",
247
+ secret_id=self.secrets_manager_secret.id,
248
+ secret_string=json.dumps(self.secrets, indent=2, sort_keys=True) if self.secrets else None,
249
+ )
250
+
251
+ def _create_secret_version_with_lifecycle_ignore(self):
252
+ secret_version = SecretsmanagerSecretVersion(
253
+ self,
254
+ self.secret_name + "_version_ignored",
255
+ secret_id=self.secrets_manager_secret.id,
256
+ secret_string=json.dumps(self.secrets, indent=2, sort_keys=True) if self.secrets else None,
257
+ )
258
+
259
+ secret_version.add_override("lifecycle", {
260
+ "ignore_changes": ["secret_string"]
261
+ })
262
+
263
+ def execute_post_apply_scripts(self):
264
+ if not self.post_apply_scripts:
265
+ return
266
+
267
+ dependencies = []
268
+ if hasattr(self, "secrets_manager_secret"):
269
+ dependencies.append(self.secrets_manager_secret)
270
+
271
+ for i, script in enumerate(self.post_apply_scripts):
272
+ script_resource = NullResource(
273
+ self,
274
+ f"post_apply_script_{i}",
275
+ depends_on=dependencies if dependencies else None
276
+ )
277
+
278
+ script_resource.add_override("provisioner", [{
279
+ "local-exec": {
280
+ "command": script,
281
+ "on_failure": "continue"
282
+ }
283
+ }])
284
+
285
+ def has_flag(self, flag_value):
286
+ return flag_value in self.flags
287
+
288
+ def clean_hyphens(self, text):
289
+ return text.replace("-", "_")
290
+
291
+ def properize_s3_bucketname(self, bucket_name):
292
+ clean_name = bucket_name.lower().replace("_", "-")
293
+ clean_name = clean_name.strip("-.")
294
+ return clean_name
295
+
296
+ def create_iam_outputs(self):
297
+ TerraformOutput(
298
+ self,
299
+ "iam_user_access_key",
300
+ value=self.service_key.id,
301
+ sensitive=True,
302
+ description="IAM user access key ID (sensitive)",
303
+ )
304
+
305
+ TerraformOutput(
306
+ self,
307
+ "iam_user_secret_key",
308
+ value=self.service_key.secret,
309
+ sensitive=True,
310
+ description="IAM user secret access key (sensitive)",
311
+ )
312
+
313
+ TerraformOutput(
314
+ self,
315
+ "secrets_manager_secret_name",
316
+ value=self.secret_name,
317
+ description="AWS Secrets Manager secret name containing all credentials",
318
+ )