-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Delete methods for CF stacks and S3 files (#2981)
* Added methods for cf and s3 files and init UI * Added unit tests for utils methods and s3_uploader * Removed s3_bucket and s3_prefix click options * Fixed lint errors and added few unit tests * Make black happy * Added LOG statements * Added and updated changes based on CR * Fixed the unit tests in artifact_exporter.py * Update HELP_TEXT in delete/command.py Co-authored-by: Chris Rehn <[email protected]> * Updated code based on Chris' comments * Small changes and fixes based on the comments * Removed region prompt * Update SAM context values for profile and region in delete_context.py * Added typing for get_cf_template_name method * Added stack_name prompt if the stack_name is not present in samconfig file * Replace [] with get() for stack-name in delete_context.py Co-authored-by: Chris Rehn <[email protected]>
- Loading branch information
Showing
19 changed files
with
642 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
""" | ||
`sam delete` command | ||
""" | ||
|
||
# Expose the cli object here | ||
from .command import cli # noqa |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
""" | ||
CLI command for "delete" command | ||
""" | ||
|
||
import logging | ||
|
||
import click | ||
from samcli.cli.main import aws_creds_options, common_options, pass_context, print_cmdline_args | ||
|
||
from samcli.lib.utils.version_checker import check_newer_version | ||
|
||
SHORT_HELP = "Delete an AWS SAM application and the artifacts created by sam deploy." | ||
|
||
HELP_TEXT = """The sam delete command deletes the CloudFormation | ||
stack and all the artifacts which were created using sam deploy. | ||
\b | ||
e.g. sam delete | ||
\b | ||
""" | ||
|
||
LOG = logging.getLogger(__name__) | ||
|
||
|
||
@click.command( | ||
"delete", | ||
short_help=SHORT_HELP, | ||
context_settings={"ignore_unknown_options": False, "allow_interspersed_args": True, "allow_extra_args": True}, | ||
help=HELP_TEXT, | ||
) | ||
@click.option( | ||
"--stack-name", | ||
required=False, | ||
help="The name of the AWS CloudFormation stack you want to delete. ", | ||
) | ||
@click.option( | ||
"--config-file", | ||
help=( | ||
"The path and file name of the configuration file containing default parameter values to use. " | ||
"Its default value is 'samconfig.toml' in project directory. For more information about configuration files, " | ||
"see: " | ||
"https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html." | ||
), | ||
type=click.STRING, | ||
default="samconfig.toml", | ||
show_default=True, | ||
) | ||
@click.option( | ||
"--config-env", | ||
help=( | ||
"The environment name specifying the default parameter values in the configuration file to use. " | ||
"Its default value is 'default'. For more information about configuration files, see: " | ||
"https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html." | ||
), | ||
type=click.STRING, | ||
default="default", | ||
show_default=True, | ||
) | ||
@aws_creds_options | ||
@common_options | ||
@pass_context | ||
@check_newer_version | ||
@print_cmdline_args | ||
def cli( | ||
ctx, | ||
stack_name: str, | ||
config_file: str, | ||
config_env: str, | ||
): | ||
""" | ||
`sam delete` command entry point | ||
""" | ||
|
||
# All logic must be implemented in the ``do_cli`` method. This helps with easy unit testing | ||
do_cli( | ||
stack_name=stack_name, region=ctx.region, config_file=config_file, config_env=config_env, profile=ctx.profile | ||
) # pragma: no cover | ||
|
||
|
||
def do_cli(stack_name: str, region: str, config_file: str, config_env: str, profile: str): | ||
""" | ||
Implementation of the ``cli`` method | ||
""" | ||
from samcli.commands.delete.delete_context import DeleteContext | ||
|
||
with DeleteContext( | ||
stack_name=stack_name, region=region, profile=profile, config_file=config_file, config_env=config_env | ||
) as delete_context: | ||
delete_context.run() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
""" | ||
Delete a SAM stack | ||
""" | ||
|
||
import boto3 | ||
|
||
import click | ||
from click import confirm | ||
from click import prompt | ||
from samcli.cli.cli_config_file import TomlProvider | ||
from samcli.lib.utils.botoconfig import get_boto_config_with_user_agent | ||
from samcli.lib.delete.cf_utils import CfUtils | ||
from samcli.lib.package.s3_uploader import S3Uploader | ||
from samcli.lib.package.artifact_exporter import mktempfile, get_cf_template_name | ||
|
||
CONFIG_COMMAND = "deploy" | ||
CONFIG_SECTION = "parameters" | ||
TEMPLATE_STAGE = "Original" | ||
|
||
|
||
class DeleteContext: | ||
def __init__(self, stack_name: str, region: str, profile: str, config_file: str, config_env: str): | ||
self.stack_name = stack_name | ||
self.region = region | ||
self.profile = profile | ||
self.config_file = config_file | ||
self.config_env = config_env | ||
self.s3_bucket = None | ||
self.s3_prefix = None | ||
self.cf_utils = None | ||
self.s3_uploader = None | ||
self.cf_template_file_name = None | ||
self.delete_artifacts_folder = None | ||
self.delete_cf_template_file = None | ||
|
||
def __enter__(self): | ||
self.parse_config_file() | ||
if not self.stack_name: | ||
self.stack_name = prompt( | ||
click.style("\tEnter stack name you want to delete:", bold=True), type=click.STRING | ||
) | ||
|
||
return self | ||
|
||
def __exit__(self, *args): | ||
pass | ||
|
||
def parse_config_file(self): | ||
""" | ||
Read the provided config file if it exists and assign the options values. | ||
""" | ||
toml_provider = TomlProvider(CONFIG_SECTION, [CONFIG_COMMAND]) | ||
config_options = toml_provider( | ||
config_path=self.config_file, config_env=self.config_env, cmd_names=[CONFIG_COMMAND] | ||
) | ||
if config_options: | ||
if not self.stack_name: | ||
self.stack_name = config_options.get("stack_name", None) | ||
|
||
# If the stack_name is same as the one present in samconfig file, | ||
# get the information about parameters if not specified by customer. | ||
if self.stack_name and self.stack_name == config_options.get("stack_name", None): | ||
if not self.region: | ||
self.region = config_options.get("region", None) | ||
click.get_current_context().region = self.region | ||
if not self.profile: | ||
self.profile = config_options.get("profile", None) | ||
click.get_current_context().profile = self.profile | ||
self.s3_bucket = config_options.get("s3_bucket", None) | ||
self.s3_prefix = config_options.get("s3_prefix", None) | ||
|
||
def delete(self): | ||
""" | ||
Delete method calls for Cloudformation stacks and S3 and ECR artifacts | ||
""" | ||
template = self.cf_utils.get_stack_template(self.stack_name, TEMPLATE_STAGE) | ||
template_str = template.get("TemplateBody", None) | ||
|
||
if self.s3_bucket and self.s3_prefix and template_str: | ||
self.delete_artifacts_folder = confirm( | ||
click.style( | ||
"\tAre you sure you want to delete the folder" | ||
+ f" {self.s3_prefix} in S3 which contains the artifacts?", | ||
bold=True, | ||
), | ||
default=False, | ||
) | ||
if not self.delete_artifacts_folder: | ||
with mktempfile() as temp_file: | ||
self.cf_template_file_name = get_cf_template_name( | ||
temp_file=temp_file, template_str=template_str, extension="template" | ||
) | ||
self.delete_cf_template_file = confirm( | ||
click.style( | ||
"\tDo you want to delete the template file" + f" {self.cf_template_file_name} in S3?", bold=True | ||
), | ||
default=False, | ||
) | ||
|
||
# Delete the primary stack | ||
self.cf_utils.delete_stack(stack_name=self.stack_name) | ||
|
||
click.echo(f"\n\t- Deleting Cloudformation stack {self.stack_name}") | ||
|
||
# Delete the CF template file in S3 | ||
if self.delete_cf_template_file: | ||
self.s3_uploader.delete_artifact(remote_path=self.cf_template_file_name) | ||
|
||
# Delete the folder of artifacts if s3_bucket and s3_prefix provided | ||
elif self.delete_artifacts_folder: | ||
self.s3_uploader.delete_prefix_artifacts() | ||
|
||
def run(self): | ||
""" | ||
Delete the stack based on the argument provided by customers and samconfig.toml. | ||
""" | ||
delete_stack = confirm( | ||
click.style( | ||
f"\tAre you sure you want to delete the stack {self.stack_name}" + f" in the region {self.region} ?", | ||
bold=True, | ||
), | ||
default=False, | ||
) | ||
# Fetch the template using the stack-name | ||
if delete_stack and self.region: | ||
boto_config = get_boto_config_with_user_agent() | ||
|
||
# Define cf_client based on the region as different regions can have same stack-names | ||
cloudformation_client = boto3.client( | ||
"cloudformation", region_name=self.region if self.region else None, config=boto_config | ||
) | ||
|
||
s3_client = boto3.client("s3", region_name=self.region if self.region else None, config=boto_config) | ||
|
||
self.s3_uploader = S3Uploader(s3_client=s3_client, bucket_name=self.s3_bucket, prefix=self.s3_prefix) | ||
self.cf_utils = CfUtils(cloudformation_client) | ||
|
||
is_deployed = self.cf_utils.has_stack(stack_name=self.stack_name) | ||
|
||
if is_deployed: | ||
self.delete() | ||
click.echo("\nDeleted successfully") | ||
else: | ||
click.echo(f"Error: The input stack {self.stack_name} does not exist on Cloudformation") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
""" | ||
Exceptions that are raised by sam delete | ||
""" | ||
from samcli.commands.exceptions import UserException | ||
|
||
|
||
class DeleteFailedError(UserException): | ||
def __init__(self, stack_name, msg): | ||
self.stack_name = stack_name | ||
self.msg = msg | ||
|
||
message_fmt = "Failed to delete the stack: {stack_name}, {msg}" | ||
|
||
super().__init__(message=message_fmt.format(stack_name=self.stack_name, msg=msg)) | ||
|
||
|
||
class FetchTemplateFailedError(UserException): | ||
def __init__(self, stack_name, msg): | ||
self.stack_name = stack_name | ||
self.msg = msg | ||
|
||
message_fmt = "Failed to fetch the template for the stack: {stack_name}, {msg}" | ||
|
||
super().__init__(message=message_fmt.format(stack_name=self.stack_name, msg=msg)) |
Empty file.
Oops, something went wrong.