# -*- coding: utf-8 -*-
"""
Stack Context Management for AWS CDK Deployments
This module provides utilities for managing stack contexts across multiple
environments, enabling flexible and consistent infrastructure deployment.
"""
import typing as T
import dataclasses
import aws_cdk as cdk
from boto_session_manager import BotoSesManager
from func_args.api import T_KWARGS, REQ, OPT, BaseModel
from .utils import to_camel, to_slug
from .cli.cli_cmd import Synth, Diff, Deploy, Destroy
if T.TYPE_CHECKING: # pragma: no cover
from pathlib_mate import T_PATH_ARG
[docs]
@dataclasses.dataclass
class StackCtx(BaseModel):
"""
Represents the configuration and deployment context for an AWS CDK stack.
This dataclass encapsulates all necessary information for deploying
a stack across different environments, providing a flexible and
reusable approach to infrastructure management.
- `Usage Example <https://github.com/MacHu-GWU/cdk_mate-project/blob/main/cdk_mate/tests/stack_ctx_enum.py>`_
:param construct_id: Unique identifier for the CDK construct
:param stack_name: Descriptive name of the stack (used in AWS CloudFormation)
:param aws_account_id: AWS Account ID where the stack will be deployed
:param aws_region: AWS Region where the stack will be deployed
:param bsm: Optional Boto Session Manager for AWS credentials
"""
construct_id: str = dataclasses.field(default=REQ)
stack_name: str = dataclasses.field(default=REQ)
aws_account_id: str = dataclasses.field(default=REQ)
aws_region: str = dataclasses.field(default=REQ)
bsm: T.Optional["BotoSesManager"] = dataclasses.field(default=None)
[docs]
@classmethod
def make_stack_ctx_kwargs(
cls,
stack_name: str,
bsm: "BotoSesManager",
) -> T_KWARGS:
"""
Create a new StackCtx instance.
"""
return {
"construct_id": to_camel(stack_name),
"stack_name": to_slug(stack_name),
"aws_account_id": bsm.aws_account_id,
"aws_region": bsm.aws_region,
"bsm": bsm,
}
[docs]
@classmethod
def new(
cls,
stack_name: str,
bsm: "BotoSesManager",
):
"""
Create a new StackCtx instance.
"""
return cls(
**cls.make_stack_ctx_kwargs(
stack_name=stack_name,
bsm=bsm,
)
)
[docs]
def to_stack_kwargs(self) -> dict[str, T.Any]:
"""
Generate keyword arguments for CDK stack initialization. Your stack
definition should look like this:
.. code-block:: python
import aws_cdk as cdk
from constructs import Construct
class MyStack(
cdk.Stack,
):
def __init__(
self,
scope: Construct,
id: str,
stack_name: str,
env: cdk.Environment,
...
):
super().__init__(
scope=scope,
id=id,
stack_name=stack_name,
env=env,
...
)
...
"""
return dict(
id=self.construct_id,
stack_name=self.stack_name,
env=cdk.Environment(
account=self.aws_account_id,
region=self.aws_region,
),
)
@property
def stack_console_url(self) -> str:
"""
Generate the AWS CloudFormation console URL for this stack.
"""
return (
f"https://{self.aws_region}.console.aws.amazon.com/cloudformation"
f"/home?region={self.aws_region}#/stacks?"
f"filteringStatus=active&filteringText={self.stack_name}&viewNested=true"
)
[docs]
def cdk_synth(
self,
dir_cdk: T.Optional["T_PATH_ARG"] = None,
**kwargs: T_KWARGS,
): # pragma: no cover
"""
Synthesize the stack using AWS CDK CLI.
- `Usage Example <https://github.com/MacHu-GWU/cdk_mate-project/blob/main/cdk/stack1/stack1_deploy.py>`_
:param dir_cdk: Optional directory path for CDK synthesis context
"""
print("--- Preview in AWS Console ---")
print(f"{self.stack_name}: {self.stack_console_url}")
cmd = Synth(
**kwargs,
)
return cmd.run(bsm=self.bsm, dir_cdk=dir_cdk)
[docs]
def cdk_diff(
self,
dir_cdk: T.Optional["T_PATH_ARG"] = None,
**kwargs: T_KWARGS,
): # pragma: no cover
"""
Show the differences between the current stack and the deployed stack.
- `Usage Example <https://github.com/MacHu-GWU/cdk_mate-project/blob/main/cdk/stack1/stack1_deploy.py>`_
:param dir_cdk: Optional directory path for CDK diff context
"""
print("--- Preview in AWS Console ---")
print(f"{self.stack_name}: {self.stack_console_url}")
cmd = Diff(
stacks=[self.construct_id],
**kwargs,
)
return cmd.run(bsm=self.bsm, dir_cdk=dir_cdk)
[docs]
def cdk_deploy(
self,
dir_cdk: T.Optional["T_PATH_ARG"] = None,
prompt: bool = False,
**kwargs: T_KWARGS,
): # pragma: no cover
"""
Deploy the stack using AWS CDK CLI.
- `Usage Example <https://github.com/MacHu-GWU/cdk_mate-project/blob/main/cdk/stack1/stack1_deploy.py>`_
:param dir_cdk: Optional directory path for CDK deployment context
:param prompt: Whether to prompt for approval before deployment
"""
print("--- Preview in AWS Console ---")
print(f"{self.stack_name}: {self.stack_console_url}")
cmd = Deploy(
stacks=[self.construct_id],
require_approval=OPT if prompt else "never",
**kwargs,
)
return cmd.run(bsm=self.bsm, dir_cdk=dir_cdk)
[docs]
def cdk_destroy(
self,
dir_cdk: T.Optional["T_PATH_ARG"] = None,
prompt: bool = False,
**kwargs: T_KWARGS,
): # pragma: no cover
"""
Destroy the stack using AWS CDK CLI.
- `Usage Example <https://github.com/MacHu-GWU/cdk_mate-project/blob/main/cdk/stack1/stack1_deploy.py>`_
:param dir_cdk: Optional directory path for CDK deployment context
:param prompt: Whether to prompt for approval before destruction
"""
print("--- Preview in AWS Console ---")
print(f"{self.stack_name}: {self.stack_console_url}")
cmd = Destroy(
stacks=[self.construct_id],
force=OPT if prompt else True,
**kwargs,
)
return cmd.run(bsm=self.bsm, dir_cdk=dir_cdk)
[docs]
def cdk_diff_many(
stack_ctx_list: list[StackCtx],
dir_cdk: T.Optional["T_PATH_ARG"] = None,
**kwargs: T_KWARGS,
): # pragma: no cover
"""
Show the differences between multiple stacks and their deployed versions.
- `Usage Example <https://github.com/MacHu-GWU/cdk_mate-project/blob/main/cdk/deploy.py>`_
:param stack_ctx_list: List of stack contexts to compare
:param dir_cdk: Optional directory path for CDK context
"""
print("--- Preview in AWS Console ---")
for stack_ctx in stack_ctx_list:
print(f"{stack_ctx.stack_name}: {stack_ctx.stack_console_url}")
cmd = Diff(
stacks=[stack_ctx.construct_id for stack_ctx in stack_ctx_list],
**kwargs,
)
return cmd.run(bsm=stack_ctx_list[0].bsm, dir_cdk=dir_cdk)
[docs]
def cdk_deploy_many(
stack_ctx_list: list[StackCtx],
dir_cdk: T.Optional["T_PATH_ARG"] = None,
prompt: bool = False,
**kwargs: T_KWARGS,
): # pragma: no cover
"""
Deploy multiple stacks in a single operation.
- `Usage Example <https://github.com/MacHu-GWU/cdk_mate-project/blob/main/cdk/deploy.py>`_
:param stack_ctx_list: List of stack contexts to deploy
:param dir_cdk: Optional directory path for CDK deployment context
:param prompt: Whether to prompt for approval before deployment
"""
print("--- Preview in AWS Console ---")
for stack_ctx in stack_ctx_list:
print(f"{stack_ctx.stack_name}: {stack_ctx.stack_console_url}")
cmd = Deploy(
stacks=[stack_ctx.construct_id for stack_ctx in stack_ctx_list],
require_approval=OPT if prompt else "never",
**kwargs,
)
return cmd.run(bsm=stack_ctx_list[0].bsm, dir_cdk=dir_cdk)
[docs]
def cdk_destroy_many(
stack_ctx_list: list[StackCtx],
dir_cdk: T.Optional["T_PATH_ARG"] = None,
prompt: bool = False,
**kwargs: T_KWARGS,
): # pragma: no cover
"""
Destroy multiple stacks in a single operation.
- `Usage Example <https://github.com/MacHu-GWU/cdk_mate-project/blob/main/cdk/deploy.py>`_
:param stack_ctx_list: List of stack contexts to destroy
:param dir_cdk: Optional directory path for CDK deployment context
:param prompt: Whether to prompt for approval before destruction
"""
print("--- Preview in AWS Console ---")
for stack_ctx in stack_ctx_list:
print(f"{stack_ctx.stack_name}: {stack_ctx.stack_console_url}")
cmd = Destroy(
stacks=[stack_ctx.construct_id for stack_ctx in stack_ctx_list],
force=OPT if prompt else True,
**kwargs,
)
return cmd.run(bsm=stack_ctx_list[0].bsm, dir_cdk=dir_cdk)