Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
1263735
Added methods for cf and s3 files and init UI
hnnasit Jun 21, 2021
ba47369
Added unit tests for utils methods and s3_uploader
hnnasit Jun 23, 2021
d77f7c4
Removed s3_bucket and s3_prefix click options
hnnasit Jun 24, 2021
d506648
chore: Increase awareness of same file warning during package (#2946)
mndeveci Jun 24, 2021
698de67
fix: Allow the base64Encoded field in REST Api, skip validation of un…
moelasmar Jun 24, 2021
3f6f070
Fixed lint errors and added few unit tests
hnnasit Jun 25, 2021
af2f929
Make black happy
hnnasit Jun 25, 2021
481ef8a
Merge pull request #1 from hnnasit/delete-cf-s3-methods
hnnasit Jun 28, 2021
1d70155
Added methods for deleting template artifacts
hnnasit Jun 28, 2021
e3f7872
Wait method added for delete cf api
hnnasit Jun 28, 2021
99f7db4
Added LOG statements
hnnasit Jun 29, 2021
e7304ec
Added and updated changes based on CR
hnnasit Jun 29, 2021
c2e43db
Fixed the unit tests in artifact_exporter.py
hnnasit Jun 29, 2021
dd35d53
Update HELP_TEXT in delete/command.py
hnnasit Jun 29, 2021
f43e763
Updated code based on Chris' comments
hnnasit Jun 30, 2021
5779cd3
Added condition for resources that have deletionpolicy specified
hnnasit Jun 30, 2021
bc9db14
Small changes and fixes based on the comments
hnnasit Jun 30, 2021
401a950
Removed region prompt
hnnasit Jun 30, 2021
0a38340
Added unit tests for ecr delete method and typing for methods
hnnasit Jul 3, 2021
8977c6a
Merge branch 'delete-template-artifacts' into delete-cf-s3-methods
hnnasit Jul 3, 2021
99c80e1
Merge pull request #2 from hnnasit/delete-cf-s3-methods
hnnasit Jul 3, 2021
aaa1b05
Reformatted delete_context and added option to skip user prompts
hnnasit Jul 5, 2021
e2e85a9
Removed return type from artifact_exporter for delete method
hnnasit Jul 5, 2021
c98e6ee
Added unit tests for artifact_exporter and delete_context
hnnasit Jul 5, 2021
17a427a
Added more unit tests for delete_context and artifact_exporter
hnnasit Jul 5, 2021
e577d7f
Added more unit tests for delete_context and artifact_exporter
hnnasit Jul 6, 2021
70eac98
Merged feat/sam_delete changes into delete_template_artifacts repo
hnnasit Jul 6, 2021
58ead71
Added docs and comments for artifact_exporter and ecr_uploader
hnnasit Jul 6, 2021
45ee66f
Added log statements in delete_context and some updates in unit tests
hnnasit Jul 7, 2021
a7eda90
Init integration tests methods for sam delete
hnnasit Jul 8, 2021
6dea705
Added more template files as a list for sam delete integration testing
hnnasit Jul 8, 2021
d151b01
Changed force to no-prompts and updated ecr delete method error hand…
hnnasit Jul 8, 2021
6f54240
Created a separate function for parsing ecr url in ecr_uploader
hnnasit Jul 9, 2021
30e3c02
Reformatted Template class init to pass template_str and init templat…
hnnasit Jul 9, 2021
b8a2591
Changed how s3 url is obtained for resource_zip edge-case: aws:glue:job
hnnasit Jul 9, 2021
7292353
Fixed edge case where resource artifact points to a path style url
hnnasit Jul 9, 2021
6ff2e22
run Make black
hnnasit Jul 9, 2021
01f485c
Added 2 more integrations tests no_stack_deployed and delete for imag…
hnnasit Jul 9, 2021
6c9a060
Made the parse s3 url funcs protected and defined a parent method and…
hnnasit Jul 10, 2021
49e0967
Added methods to extract s3 info from cf template
hnnasit Jul 11, 2021
4450886
Added testing for get_s3_info method for artifact_exporter and s3_upl…
hnnasit Jul 12, 2021
57cdb89
Added few more integration tests for delete
hnnasit Jul 12, 2021
dfb9cb3
Merge remote-tracking branch 'origin/delete-template-artifacts' into …
hnnasit Jul 12, 2021
6fad0ad
Merged delete-template-artifacts branch code and updated this branch …
hnnasit Jul 12, 2021
c0babab
Added tests for no prefix present for zip and image
hnnasit Jul 13, 2021
5098e3c
Merged feat/sam_delete branch into get-s3-info-cf-template branch
hnnasit Jul 14, 2021
9cfc443
Added a check to confirm input stack is deleted for all the integrati…
hnnasit Jul 15, 2021
29add30
Removed commented code and updated method docstring
hnnasit Jul 16, 2021
308f859
Better error handling for s3 delete artifacts and fixed bug for getti…
hnnasit Jul 17, 2021
4275e34
Changed get_s3_info to get_property_value and changed output text for…
hnnasit Jul 19, 2021
16128a0
Merge remote-tracking branch 'origin/get-s3-info-cf-template' into sa…
hnnasit Jul 19, 2021
b8c2fcb
Merge remote-tracking branch 'upstream/feat/sam_delete' into sam-dele…
hnnasit Jul 19, 2021
ed8ac1d
Added integration test for deleting nested stacks
hnnasit Jul 19, 2021
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
Empty file.
41 changes: 41 additions & 0 deletions tests/integration/delete/delete_integ_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import os
from unittest import TestCase


class DeleteIntegBase(TestCase):
@classmethod
def setUpClass(cls):
pass

def setUp(self):
super().setUp()

def tearDown(self):
super().tearDown()

def base_command(self):
command = "sam"
if os.getenv("SAM_CLI_DEV"):
command = "samdev"

return command

def get_delete_command_list(
self, stack_name=None, region=None, config_file=None, config_env=None, profile=None, no_prompts=None
):
command_list = [self.base_command(), "delete"]

if stack_name:
command_list += ["--stack-name", str(stack_name)]
if region:
command_list += ["--region", str(region)]
if config_file:
command_list += ["--config-file", str(config_file)]
if config_env:
command_list += ["--config-env", str(config_env)]
if profile:
command_list += ["--profile", str(profile)]
if no_prompts:
command_list += ["--no-prompts"]

return command_list
334 changes: 334 additions & 0 deletions tests/integration/delete/test_delete_command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,334 @@
import os
import shutil
import tempfile
import time
import uuid
from pathlib import Path
from unittest import skipIf
import logging
import boto3
import docker
from botocore.config import Config
from parameterized import parameterized
from botocore.exceptions import ClientError

from samcli.lib.bootstrap.bootstrap import SAM_CLI_STACK_NAME
from samcli.lib.config.samconfig import DEFAULT_CONFIG_FILE_NAME
from tests.integration.deploy.deploy_integ_base import DeployIntegBase
from tests.integration.delete.delete_integ_base import DeleteIntegBase
from tests.integration.package.package_integ_base import PackageIntegBase
from tests.testing_utils import RUNNING_ON_CI, RUNNING_TEST_FOR_MASTER_ON_CI, RUN_BY_CANARY
from tests.testing_utils import run_command, run_command_with_input

# Delete tests require credentials and CI/CD will only add credentials to the env if the PR is from the same repo.
# This is to restrict package tests to run outside of CI/CD, when the branch is not master or tests are not run by Canary
SKIP_DELETE_TESTS = RUNNING_ON_CI and RUNNING_TEST_FOR_MASTER_ON_CI and not RUN_BY_CANARY
CFN_SLEEP = 3
TIMEOUT = 300
CFN_PYTHON_VERSION_SUFFIX = os.environ.get("PYTHON_VERSION", "0.0.0").replace(".", "-")
LOG = logging.getLogger(__name__)


@skipIf(SKIP_DELETE_TESTS, "Skip delete tests in CI/CD only")
class TestDelete(PackageIntegBase, DeployIntegBase, DeleteIntegBase):
@classmethod
def setUpClass(cls):
cls.docker_client = docker.from_env()
cls.local_images = [
("alpine", "latest"),
]
# setup some images locally by pulling them.
for repo, tag in cls.local_images:
cls.docker_client.api.pull(repository=repo, tag=tag)

PackageIntegBase.setUpClass()
DeployIntegBase.setUpClass()
DeleteIntegBase.setUpClass()

def setUp(self):
self.cf_client = boto3.client("cloudformation")
self.sns_arn = os.environ.get("AWS_SNS")
time.sleep(CFN_SLEEP)
super().setUp()

def test_delete_command_no_stack_deployed(self):

stack_name = self._method_to_stack_name(self.id())

delete_command_list = self.get_delete_command_list(stack_name=stack_name, no_prompts=True)

delete_process_execute = run_command(delete_command_list)
self.assertEqual(delete_process_execute.process.returncode, 0)
self.assertIn(
f"Error: The input stack {stack_name} does not exist on Cloudformation", str(delete_process_execute.stdout)
)

@parameterized.expand(
[
"aws-serverless-function.yaml",
"aws-serverless-statemachine.yaml",
"aws-appsync-graphqlschema.yaml",
"aws-appsync-resolver.yaml",
"aws-appsync-functionconfiguration.yaml",
"aws-apigateway-restapi.yaml",
"aws-elasticbeanstalk-applicationversion.yaml",
"aws-cloudformation-moduleversion.yaml",
"aws-cloudformation-resourceversion.yaml",
"aws-cloudformation-stack.yaml",
"aws-serverless-application.yaml",
"aws-lambda-layerversion.yaml",
"aws-serverless-layerversion.yaml",
"aws-glue-job.yaml",
"aws-stepfunctions-statemachine.yaml",
]
)
def test_delete_with_s3_prefix_present_zip(self, template_file):
template_path = self.test_data_path.joinpath(template_file)

stack_name = self._method_to_stack_name(self.id())

config_file_name = stack_name + ".toml"
deploy_command_list = self.get_deploy_command_list(
template_file=template_path, guided=True, config_file=config_file_name
)

deploy_process_execute = run_command_with_input(
deploy_command_list, "{}\n\n\n\n\n\n\n\n\n".format(stack_name).encode()
)

config_file_path = self.test_data_path.joinpath(config_file_name)
delete_command_list = self.get_delete_command_list(
stack_name=stack_name, config_file=config_file_path, no_prompts=True
)

delete_process_execute = run_command(delete_command_list)
self.assertEqual(delete_process_execute.process.returncode, 0)

try:
resp = self.cf_client.describe_stacks(StackName=stack_name)
except ClientError as ex:
self.assertIn(f"Stack with id {stack_name} does not exist", str(ex))

# Remove the local config file created
if os.path.isfile(config_file_path):
os.remove(config_file_path)

@parameterized.expand(
[
"aws-serverless-function-image.yaml",
]
)
def test_delete_with_s3_prefix_present_image(self, template_file):
template_path = self.test_data_path.joinpath(template_file)

stack_name = self._method_to_stack_name(self.id())

config_file_name = stack_name + ".toml"
deploy_command_list = self.get_deploy_command_list(
template_file=template_path, guided=True, config_file=config_file_name
)

deploy_process_execute = run_command_with_input(
deploy_command_list, f"{stack_name}\n\n{self.ecr_repo_name}\n\n\ny\n\n\n\n\n\n".encode()
)

config_file_path = self.test_data_path.joinpath(config_file_name)
delete_command_list = self.get_delete_command_list(
stack_name=stack_name, config_file=config_file_path, no_prompts=True
)

delete_process_execute = run_command(delete_command_list)
self.assertEqual(delete_process_execute.process.returncode, 0)

try:
resp = self.cf_client.describe_stacks(StackName=stack_name)
except ClientError as ex:
self.assertIn(f"Stack with id {stack_name} does not exist", str(ex))

# Remove the local config file created
if os.path.isfile(config_file_path):
os.remove(config_file_path)

@parameterized.expand(
[
"aws-serverless-function.yaml",
]
)
def test_delete_guided_prompts(self, template_file):
template_path = self.test_data_path.joinpath(template_file)

stack_name = self._method_to_stack_name(self.id())

config_file_name = stack_name + ".toml"
deploy_command_list = self.get_deploy_command_list(
template_file=template_path, guided=True, config_file=config_file_name
)

deploy_process_execute = run_command_with_input(
deploy_command_list, "{}\n\n\n\n\n\n\n\n\n".format(stack_name).encode()
)

config_file_path = self.test_data_path.joinpath(config_file_name)
delete_command_list = self.get_delete_command_list(stack_name=stack_name, config_file=config_file_path)

LOG.info(delete_command_list)
delete_process_execute = run_command_with_input(delete_command_list, "y\nn\ny\n".encode())

self.assertEqual(delete_process_execute.process.returncode, 0)

try:
resp = self.cf_client.describe_stacks(StackName=stack_name)
except ClientError as ex:
self.assertIn(f"Stack with id {stack_name} does not exist", str(ex))

# Remove the local config file created
if os.path.isfile(config_file_path):
os.remove(config_file_path)

@parameterized.expand(
[
"aws-serverless-function.yaml",
]
)
def test_delete_no_config_file_zip(self, template_file):
template_path = self.test_data_path.joinpath(template_file)

stack_name = self._method_to_stack_name(self.id())

deploy_command_list = self.get_deploy_command_list(template_file=template_path, guided=True)

deploy_process_execute = run_command_with_input(
deploy_command_list, "{}\n\n\n\n\nn\n\n\n".format(stack_name).encode()
)

delete_command_list = self.get_delete_command_list(stack_name=stack_name, region="us-east-1", no_prompts=True)

delete_process_execute = run_command(delete_command_list)
self.assertEqual(delete_process_execute.process.returncode, 0)

try:
resp = self.cf_client.describe_stacks(StackName=stack_name)
except ClientError as ex:
self.assertIn(f"Stack with id {stack_name} does not exist", str(ex))

@parameterized.expand(
[
"aws-serverless-function.yaml",
]
)
def test_delete_no_s3_prefix_zip(self, template_file):
template_path = self.test_data_path.joinpath(template_file)

stack_name = self._method_to_stack_name(self.id())

deploy_command_list = self.get_deploy_command_list(
template_file=template_path,
stack_name=stack_name,
capabilities="CAPABILITY_IAM",
s3_bucket=self.bucket_name,
force_upload=True,
notification_arns=self.sns_arn,
parameter_overrides="Parameter=Clarity",
kms_key_id=self.kms_key,
no_execute_changeset=False,
tags="integ=true clarity=yes foo_bar=baz",
confirm_changeset=False,
region="us-east-1",
)

deploy_process_execute = run_command(deploy_command_list)

delete_command_list = self.get_delete_command_list(stack_name=stack_name, region="us-east-1", no_prompts=True)

delete_process_execute = run_command(delete_command_list)

self.assertEqual(delete_process_execute.process.returncode, 0)

try:
resp = self.cf_client.describe_stacks(StackName=stack_name)
except ClientError as ex:
self.assertIn(f"Stack with id {stack_name} does not exist", str(ex))

@parameterized.expand(
[
"aws-serverless-function-image.yaml",
]
)
def test_delete_no_s3_prefix_image(self, template_file):
template_path = self.test_data_path.joinpath(template_file)

stack_name = self._method_to_stack_name(self.id())

# Try to deploy to another region.
deploy_command_list = self.get_deploy_command_list(
template_file=template_path,
stack_name=stack_name,
capabilities="CAPABILITY_IAM",
image_repository=self.ecr_repo_name,
s3_bucket=self.bucket_name,
force_upload=True,
notification_arns=self.sns_arn,
parameter_overrides="Parameter=Clarity",
kms_key_id=self.kms_key,
no_execute_changeset=False,
tags="integ=true clarity=yes foo_bar=baz",
confirm_changeset=False,
region="us-east-1",
)

deploy_process_execute = run_command(deploy_command_list)

delete_command_list = self.get_delete_command_list(stack_name=stack_name, region="us-east-1", no_prompts=True)

delete_process_execute = run_command(delete_command_list)

self.assertEqual(delete_process_execute.process.returncode, 0)

try:
resp = self.cf_client.describe_stacks(StackName=stack_name)
except ClientError as ex:
self.assertIn(f"Stack with id {stack_name} does not exist", str(ex))

@parameterized.expand(
[os.path.join("deep-nested", "template.yaml"), os.path.join("deep-nested-image", "template.yaml")]
)
def test_delete_nested_stacks(self, template_file):
template_path = self.test_data_path.joinpath(template_file)

stack_name = self._method_to_stack_name(self.id())

# Package and Deploy in one go without confirming change set.
deploy_command_list = self.get_deploy_command_list(
template_file=template_path,
stack_name=stack_name,
# Note(xinhol): --capabilities does not allow passing multiple, we need to fix it
# here we use samconfig-deep-nested.toml as a workaround
config_file=self.test_data_path.joinpath("samconfig-deep-nested.toml"),
s3_prefix="integ_deploy",
s3_bucket=self.s3_bucket.name,
force_upload=True,
notification_arns=self.sns_arn,
kms_key_id=self.kms_key,
no_execute_changeset=False,
tags="integ=true clarity=yes foo_bar=baz",
confirm_changeset=False,
image_repository=self.ecr_repo_name,
)

deploy_process_execute = run_command(deploy_command_list)

delete_command_list = self.get_delete_command_list(stack_name=stack_name, region="us-east-1", no_prompts=True)

delete_process_execute = run_command(delete_command_list)

self.assertEqual(delete_process_execute.process.returncode, 0)

try:
resp = self.cf_client.describe_stacks(StackName=stack_name)
except ClientError as ex:
self.assertIn(f"Stack with id {stack_name} does not exist", str(ex))

def _method_to_stack_name(self, method_name):
"""Method expects method name which can be a full path. Eg: test.integration.test_deploy_command.method_name"""
method_name = method_name.split(".")[-1]
return f"{method_name.replace('_', '-')}-{CFN_PYTHON_VERSION_SUFFIX}"