BuzzerboyAWSLightsail 0.331.1__py3-none-any.whl → 0.332.1__py3-none-any.whl

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.
@@ -0,0 +1,385 @@
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
+ import os
26
+ import json
27
+ from enum import Enum
28
+ from constructs import Construct
29
+ from cdktf import TerraformOutput
30
+
31
+ # Import the base class
32
+ from .LightsailBaseStandalone import LightsailBaseStandalone, BaseLightsailArchitectureFlags
33
+
34
+ #endregion
35
+
36
+ #region AWS Provider and Resources
37
+ from cdktf_cdktf_provider_aws.provider import AwsProvider
38
+ from cdktf_cdktf_provider_aws import (
39
+ lightsail_container_service,
40
+ lightsail_database,
41
+ cloudfront_distribution,
42
+ s3_bucket,
43
+ )
44
+ #endregion
45
+
46
+ #region Random Provider and Resources
47
+ from cdktf_cdktf_provider_random import password
48
+
49
+ # AWS WAF (currently unused but imported for future use)
50
+ from cdktf_cdktf_provider_aws.wafv2_web_acl import (
51
+ Wafv2WebAcl,
52
+ Wafv2WebAclDefaultAction,
53
+ Wafv2WebAclRule,
54
+ Wafv2WebAclVisibilityConfig,
55
+ Wafv2WebAclDefaultActionAllow,
56
+ Wafv2WebAclRuleOverrideAction,
57
+ Wafv2WebAclRuleOverrideActionNone,
58
+ Wafv2WebAclRuleOverrideActionCount,
59
+ Wafv2WebAclRuleVisibilityConfig,
60
+ )
61
+ from cdktf_cdktf_provider_aws.wafv2_web_acl_association import Wafv2WebAclAssociation
62
+ from cdktf_cdktf_provider_aws.wafv2_rule_group import Wafv2RuleGroupRuleVisibilityConfig
63
+
64
+ #endregion
65
+
66
+
67
+
68
+ #region ArchitectureFlags
69
+ class ArchitectureFlags(Enum):
70
+ """
71
+ Architecture configuration flags for optional components.
72
+
73
+ Includes both base flags and container-specific flags.
74
+
75
+ Base flags:
76
+ :param SKIP_DEFAULT_POST_APPLY_SCRIPTS: Skip default post-apply scripts
77
+ :param PRESERVE_EXISTING_SECRETS: Don't overwrite existing secret versions (smart detection)
78
+ :param IGNORE_SECRET_CHANGES: Ignore all changes to secret after initial creation
79
+
80
+ Container-specific flags:
81
+ :param SKIP_DATABASE: Skip database creation
82
+ :param SKIP_DOMAIN: Skip domain and DNS configuration
83
+ """
84
+
85
+ # Base flags from BaseLightsailArchitectureFlags
86
+ SKIP_DEFAULT_POST_APPLY_SCRIPTS = "skip_default_post_apply_scripts"
87
+ PRESERVE_EXISTING_SECRETS = "preserve_existing_secrets"
88
+ IGNORE_SECRET_CHANGES = "ignore_secret_changes"
89
+
90
+ # Container-specific flags
91
+ SKIP_DATABASE = "skip_database"
92
+ SKIP_DOMAIN = "skip_domain"
93
+
94
+ #endregion
95
+
96
+
97
+ class LightsailContainerStandaloneStack(LightsailBaseStandalone):
98
+ """
99
+ AWS Lightsail Mini Infrastructure Stack.
100
+
101
+ A comprehensive infrastructure stack that deploys:
102
+ * Lightsail Container Service with custom domain support
103
+ * PostgreSQL database (optional)
104
+ * IAM resources and S3 storage
105
+
106
+ :param scope: The construct scope
107
+ :param id: The construct ID
108
+ :param kwargs: Configuration parameters including region, domains, flags, etc.
109
+
110
+ Example:
111
+ >>> stack = LightsailContainerStandaloneStack(
112
+ ... app, "my-stack",
113
+ ... region="ca-central-1",
114
+ ... domains=["app.example.com"],
115
+ ... project_name="my-app",
116
+ ... postApplyScripts=[
117
+ ... "echo 'Deployment completed'",
118
+ ... "curl -X POST https://webhook.example.com/notify"
119
+ ... ]
120
+ ... )
121
+ """
122
+
123
+ @staticmethod
124
+ def get_architecture_flags():
125
+ """
126
+ Get the ArchitectureFlags enum for configuration.
127
+
128
+ :returns: ArchitectureFlags enum class
129
+ :rtype: type[ArchitectureFlags]
130
+ """
131
+ return ArchitectureFlags
132
+
133
+ def __init__(self, scope, id, **kwargs):
134
+ """
135
+ Initialize the AWS Lightsail Mini Infrastructure Stack.
136
+
137
+ :param scope: The construct scope
138
+ :param id: Unique identifier for this stack
139
+ :param kwargs: Configuration parameters
140
+
141
+ **Configuration Parameters:**
142
+
143
+ :param region: AWS region (default: "us-east-1")
144
+ :param environment: Environment name (default: "dev")
145
+ :param project_name: Project identifier (default: "bb-aws-lightsail-mini-v1a-app")
146
+ :param domain_name: Primary domain name
147
+ :param domains: List of custom domains to configure
148
+ :param flags: List of ArchitectureFlags to modify behavior
149
+ :param profile: AWS profile to use (default: "default")
150
+ :param postApplyScripts: List of shell commands to execute after deployment
151
+
152
+ .. warning::
153
+ Lightsail domain operations must use us-east-1 region regardless of
154
+ the main stack region.
155
+ """
156
+ # Set container-specific defaults
157
+ if "project_name" not in kwargs:
158
+ kwargs["project_name"] = "bb-aws-lightsail-mini-v1a-app"
159
+
160
+ # Set database defaults before base initialization
161
+ self.default_db_name = kwargs.get("default_db_name", kwargs["project_name"])
162
+ self.default_db_username = kwargs.get("default_db_username", "dbadmin")
163
+
164
+ # Call parent constructor which handles all the base initialization
165
+ super().__init__(scope, id, **kwargs)
166
+
167
+ # ===== Container-Specific Configuration =====
168
+ self.domains = kwargs.get("domains", []) or []
169
+
170
+ # ===== Database Configuration =====
171
+ self.default_db_name = kwargs.get("default_db_name", self.project_name)
172
+ self.default_db_username = kwargs.get("default_db_username", "dbadmin")
173
+
174
+ def _initialize_providers(self):
175
+ """Initialize all required Terraform providers."""
176
+ # Call parent class to initialize base providers
177
+ super()._initialize_providers()
178
+
179
+ # Add Lightsail-specific provider for domain operations (must be us-east-1)
180
+ self.aws_domain_provider = AwsProvider(
181
+ self, "aws_domain", region="us-east-1", profile=self.profile, alias="domain"
182
+ )
183
+ self.resources["aws_domain"] = self.aws_domain_provider
184
+
185
+ def _set_default_post_apply_scripts(self):
186
+ """
187
+ Set default post-apply scripts specific to container deployments.
188
+ """
189
+ # Call parent method for base scripts
190
+ super()._set_default_post_apply_scripts()
191
+
192
+ # Skip if flag is set
193
+ if BaseLightsailArchitectureFlags.SKIP_DEFAULT_POST_APPLY_SCRIPTS.value in self.flags:
194
+ return
195
+
196
+ # Add container-specific scripts
197
+ container_scripts = [
198
+ f"echo '🚀 Container Service URL: https://{self.project_name}.{self.region}.cs.amazonlightsail.com'",
199
+ ]
200
+
201
+ # Insert container-specific scripts before the final "execution started" message
202
+ if self.post_apply_scripts:
203
+ # Find the index of the last script and insert before it
204
+ insert_index = len(self.post_apply_scripts) - 1
205
+ for script in reversed(container_scripts):
206
+ self.post_apply_scripts.insert(insert_index, script)
207
+
208
+ def create_lightsail_resources(self):
209
+ """
210
+ Create core Lightsail resources.
211
+
212
+ Creates:
213
+ * Lightsail Container Service with nano power and scale of 1
214
+ * Random password for database authentication (if database not skipped)
215
+
216
+ .. note::
217
+ Custom domains are configured separately through DNS records and
218
+ post-deployment automation rather than the public_domain_names parameter
219
+ due to CDKTF type complexity.
220
+ """
221
+ # Lightsail Container Service
222
+ self.container_service = lightsail_container_service.LightsailContainerService(
223
+ self,
224
+ "app_container",
225
+ name=f"{self.project_name}",
226
+ power="nano",
227
+ region=self.region,
228
+ scale=1,
229
+ is_disabled=False,
230
+ # Note: Custom domains are configured separately via DNS records
231
+ # The public_domain_names parameter has complex type requirements
232
+ tags={"Environment": self.environment, "Project": self.project_name, "Stack": self.__class__.__name__},
233
+ )
234
+ self.container_service_url = self.get_lightsail_container_service_domain()
235
+
236
+ # Create Lightsail database if not skipped
237
+ if not self.has_flag(ArchitectureFlags.SKIP_DATABASE.value):
238
+ self.create_lightsail_database()
239
+
240
+ # Create S3 bucket for application data
241
+ self.create_s3_bucket()
242
+
243
+ self.resources["lightsail_container_service"] = self.container_service
244
+
245
+ def create_lightsail_database(self):
246
+ """
247
+ Create Lightsail PostgreSQL database (optional).
248
+
249
+ Creates a micro PostgreSQL 14 database instance if the SKIP_DATABASE flag
250
+ is not set. Also populates the secrets dictionary with database connection
251
+ information for use in Secrets Manager.
252
+
253
+ Database Configuration:
254
+ * Engine: PostgreSQL 14
255
+ * Size: micro_2_0
256
+ * Final snapshot: Disabled (skip_final_snapshot=True)
257
+
258
+ .. note::
259
+ Database creation can be skipped by including ArchitectureFlags.SKIP_DATABASE
260
+ in the flags parameter during stack initialization.
261
+ """
262
+ # Database Password Generation
263
+ self.db_password = password.Password(
264
+ self, "db_password", length=16, special=True, override_special="!#$%&*()-_=+[]{}<>:?"
265
+ )
266
+
267
+ self.database = lightsail_database.LightsailDatabase(
268
+ self,
269
+ "app_database",
270
+ relational_database_name=f"{self.project_name}-db",
271
+ blueprint_id="postgres_14",
272
+ bundle_id="micro_2_0",
273
+ master_database_name=self.clean_hyphens(f"{self.project_name}"),
274
+ master_username=self.default_db_username,
275
+ master_password=self.db_password.result,
276
+ skip_final_snapshot=True,
277
+ tags={"Environment": self.environment, "Project": self.project_name, "Stack": self.__class__.__name__},
278
+ )
279
+
280
+ # Populate secrets for database connection
281
+ self.secrets.update(
282
+ {
283
+ "password": self.db_password.result,
284
+ "username": self.default_db_username,
285
+ "dbname": self.default_db_name,
286
+ "host": self.database.master_endpoint_address,
287
+ "port": self.database.master_endpoint_port,
288
+ }
289
+ )
290
+
291
+ def create_s3_bucket(self, bucket_name=None):
292
+ """
293
+ Create S3 bucket for application data storage.
294
+
295
+ Creates a private S3 bucket with proper tagging for application data storage
296
+ and security configurations:
297
+ - Bucket versioning enabled
298
+ - Server-side encryption with Amazon S3 managed keys (SSE-S3)
299
+ - Bucket key enabled to reduce encryption costs
300
+ - Private ACL
301
+
302
+ The bucket name follows the pattern: {project_name}-s3
303
+
304
+ .. note::
305
+ The ACL parameter is deprecated in favor of aws_s3_bucket_acl resource
306
+ but is retained for backwards compatibility.
307
+ """
308
+ if bucket_name is None:
309
+ bucket_name = self.properize_s3_bucketname(f"{self.project_name}-s3")
310
+
311
+ self.s3_bucket = s3_bucket.S3Bucket(
312
+ self,
313
+ "app_data_bucket",
314
+ bucket=bucket_name,
315
+ acl="private",
316
+ versioning={"enabled": True},
317
+ server_side_encryption_configuration=({
318
+ "rule": ({
319
+ "apply_server_side_encryption_by_default": {
320
+ "sse_algorithm": "AES256"
321
+ },
322
+ "bucket_key_enabled": True
323
+ })
324
+ }),
325
+ tags={"Environment": self.environment, "Project": self.project_name, "Stack": self.__class__.__name__},
326
+ )
327
+
328
+ # Store the S3 bucket in resources registry
329
+ self.resources["s3_bucket"] = self.s3_bucket
330
+ self.bucket_name = bucket_name
331
+
332
+ def get_lightsail_container_service_domain(self):
333
+ """
334
+ Retrieve the actual Lightsail container service domain from AWS.
335
+
336
+ Returns a default format domain since we cannot query AWS at synthesis time.
337
+
338
+ :returns: The public domain URL for the container service
339
+ :rtype: str
340
+ """
341
+ return f"{self.project_name}.{self.region}.cs.amazonlightsail.com"
342
+
343
+ def create_outputs(self):
344
+ """
345
+ Create Terraform outputs for important resource information.
346
+
347
+ Generates outputs for:
348
+ * Container service public URL
349
+ * Database endpoint (if database is enabled)
350
+ * Database password (sensitive, if database is enabled)
351
+ * IAM access keys (sensitive)
352
+
353
+ .. note::
354
+ Sensitive outputs are marked as such and will be hidden in
355
+ Terraform output unless explicitly requested.
356
+ """
357
+ # Container service public URL
358
+ TerraformOutput(
359
+ self,
360
+ "container_service_url",
361
+ value=self.container_service_url,
362
+ description="Public URL of the Lightsail container service",
363
+ )
364
+
365
+ # Database outputs (if database is enabled)
366
+ if not self.has_flag(ArchitectureFlags.SKIP_DATABASE.value) and hasattr(self, 'database'):
367
+ TerraformOutput(
368
+ self,
369
+ "database_endpoint",
370
+ value=f"{self.database.master_endpoint_address}:{self.database.master_endpoint_port}",
371
+ description="Database connection endpoint",
372
+ )
373
+ TerraformOutput(
374
+ self,
375
+ "database_password",
376
+ value=self.database.master_password,
377
+ sensitive=True,
378
+ description="Database master password (sensitive)",
379
+ )
380
+
381
+ # Use the shared IAM output helper
382
+ self.create_iam_outputs()
383
+
384
+ """Placeholder for networking resources creation."""
385
+ pass