Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 35 additions & 16 deletions samcli/commands/delete/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,21 @@
import logging

import click
from samcli.cli.cli_config_file import TomlProvider, configuration_option
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."
SHORT_HELP = "Delete an AWS SAM application and the artifacts created by sam deploy."

HELP_TEXT = """The sam delete command deletes a Cloudformation Stack and deletes all your resources which were created.
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 --stack-name sam-app --region us-east-1
e.g. sam delete

\b
"""

CONFIG_SECTION = "parameters"
CONFIG_COMMAND = "deploy"
LOG = logging.getLogger(__name__)


Expand All @@ -31,41 +29,62 @@
context_settings={"ignore_unknown_options": False, "allow_interspersed_args": True, "allow_extra_args": True},
help=HELP_TEXT,
)
@configuration_option(provider=TomlProvider(section=CONFIG_SECTION, cmd_names=[CONFIG_COMMAND]))
@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,
config_file,
config_env,
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, ctx.region, ctx.profile) # pragma: no cover
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, region, profile):
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

ctx = click.get_current_context()
s3_bucket = ctx.default_map.get("s3_bucket", None)
s3_prefix = ctx.default_map.get("s3_prefix", None)
with DeleteContext(
stack_name=stack_name, region=region, s3_bucket=s3_bucket, s3_prefix=s3_prefix, profile=profile
stack_name=stack_name, region=region, profile=profile, config_file=config_file, config_env=config_env
) as delete_context:
delete_context.run()
153 changes: 91 additions & 62 deletions samcli/commands/delete/delete_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,60 +4,128 @@

import boto3


import docker
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.delete.utils import get_cf_template_name
from samcli.lib.package.s3_uploader import S3Uploader
from samcli.lib.package.artifact_exporter import mktempfile, get_cf_template_name

from samcli.yamlhelper import yaml_parse

from samcli.lib.package.artifact_exporter import Template
from samcli.lib.package.ecr_uploader import ECRUploader
from samcli.lib.package.uploaders import Uploaders

CONFIG_COMMAND = "deploy"
CONFIG_SECTION = "parameters"
TEMPLATE_STAGE = "Original"

class DeleteContext:
def __init__(self, stack_name, region, s3_bucket, s3_prefix, profile):
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.s3_bucket = s3_bucket
self.s3_prefix = s3_prefix
self.config_file = config_file
self.config_env = config_env
self.s3_bucket = None
self.s3_prefix = None
self.cf_utils = None
self.start_bold = "\033[1m"
self.end_bold = "\033[0m"
self.s3_uploader = None
self.uploaders = 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 run(self):
def parse_config_file(self):
"""
Delete the stack based on the argument provided by customers and samconfig.toml.
Read the provided config file if it exists and assign the options values.
"""
if not self.stack_name:
self.stack_name = prompt(
f"\t{self.start_bold}Enter stack name you want to delete{self.end_bold}", type=click.STRING
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 self.stack_name == config_options["stack_name"]:
if not self.region:
self.region = config_options.get("region", None)
if not self.profile:
self.profile = config_options.get("profile", None)
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)
template_dict = yaml_parse(template_str)

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, template_str, "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)
self.cf_utils.wait_for_delete(self.stack_name)

click.echo(f"\n\t- Deleting Cloudformation stack {self.stack_name}")

# Delete the artifacts
template = Template(None, None, self.uploaders, None)
template.delete(template_dict)

# 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()

if not self.region:
self.region = prompt(
f"\t{self.start_bold}Enter region you want to delete from{self.end_bold}", type=click.STRING
)
def run(self):
"""
Delete the stack based on the argument provided by customers and samconfig.toml.
"""
delete_stack = confirm(
f"\t{self.start_bold}Are you sure you want to delete the stack {self.stack_name}?{self.end_bold}",
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
Expand All @@ -76,53 +144,14 @@ def run(self):

docker_client = docker.from_env()
ecr_uploader = ECRUploader(docker_client, ecr_client, None, None)


self.uploaders = Uploaders(self.s3_uploader, ecr_uploader)
self.cf_utils = CfUtils(cloudformation_client)

is_deployed = self.cf_utils.has_stack(self.stack_name)
is_deployed = self.cf_utils.has_stack(stack_name=self.stack_name)

if is_deployed:
template_str = self.cf_utils.get_stack_template(self.stack_name, "Original")

template_dict = yaml_parse(template_str)

if self.s3_bucket and self.s3_prefix:
self.delete_artifacts_folder = confirm(
f"\t{self.start_bold}Are you sure you want to delete the folder"
+ f" {self.s3_prefix} in S3 which contains the artifacts?{self.end_bold}",
default=False,
)
if not self.delete_artifacts_folder:
self.cf_template_file_name = get_cf_template_name(template_str, "template")
self.delete_cf_template_file = confirm(
f"\t{self.start_bold}Do you want to delete the template file"
+ f" {self.cf_template_file_name} in S3?{self.end_bold}",
default=False,
)

click.echo("\n")
# Delete the primary stack
click.echo("- deleting Cloudformation stack {0}".format(self.stack_name))
self.cf_utils.delete_stack(self.stack_name)
self.cf_utils.wait_for_delete(self.stack_name)


# Delete the artifacts
self.uploaders = Uploaders(self.s3_uploader, ecr_uploader)
template = Template(None, None, self.uploaders, None)
template.delete(template_dict)

# Delete the CF template file in S3
if self.delete_cf_template_file:
self.s3_uploader.delete_artifact(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()

# Delete the ECR companion stack

click.echo("\n")
click.echo("delete complete")
self.delete()
click.echo("\nDeleted successfully")
else:
click.echo("Error: The input stack {0} does not exist on Cloudformation".format(self.stack_name))
click.echo(f"Error: The input stack {self.stack_name} does not exist on Cloudformation")
10 changes: 10 additions & 0 deletions samcli/commands/delete/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,13 @@ def __init__(self, stack_name, 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))
Loading