Path: blob/develop/tests/unit/customizations/cloudformation/test_deployer.py
1569 views
import botocore.session12from botocore.stub import Stubber3from awscli.testutils import mock, unittest4from awscli.customizations.cloudformation.deployer import Deployer, ChangeSetResult5from awscli.customizations.cloudformation import exceptions678class TestDeployer(unittest.TestCase):910def setUp(self):11client = botocore.session.get_session().create_client('cloudformation',12region_name="us-east-1")13self.stub_client = Stubber(client)1415self.changeset_prefix = "some-changeset-prefix"16self.deployer = Deployer(client, self.changeset_prefix)1718def test_has_stack_success(self):19stack_name = "stack_name"2021expected_params = {22"StackName": stack_name23}2425response = {26"Stacks": [27make_stack_obj(stack_name)28]29}3031self.stub_client.add_response('describe_stacks', response,32expected_params)3334with self.stub_client:35response = self.deployer.has_stack(stack_name)36self.assertTrue(response)3738def test_has_stack_no_stack(self):39stack_name = "stack_name"40expected_params = {41"StackName": stack_name42}4344# Response contains NO stack45no_stack_response = {46"Stacks": []47}48self.stub_client.add_response('describe_stacks', no_stack_response,49expected_params)50with self.stub_client:51response = self.deployer.has_stack(stack_name)52self.assertFalse(response)5354# Response is a ClientError with a message that the stack does not exist55self.stub_client.add_client_error('describe_stacks', "ClientError",56"Stack with id {0} does not exist"57.format(stack_name))58with self.stub_client:59response = self.deployer.has_stack(stack_name)60self.assertFalse(response)6162def test_has_stack_review_in_progress(self):63stack_name = "stack_name"64expected_params = {65"StackName": stack_name66}6768# Response contains NO stack69review_in_progress_response = {70"Stacks": [make_stack_obj(stack_name, "REVIEW_IN_PROGRESS")]71}72self.stub_client.add_response('describe_stacks',73review_in_progress_response,74expected_params)75with self.stub_client:76response = self.deployer.has_stack(stack_name)77self.assertFalse(response)7879def test_has_stack_exception(self):80self.stub_client.add_client_error('describe_stacks', "ValidationError",81"Service is bad")82with self.stub_client:83with self.assertRaises(botocore.exceptions.ClientError):84self.deployer.has_stack("stack_name")8586def test_create_changeset_success(self):87stack_name = "stack_name"88template = "template"89parameters = [90{"ParameterKey": "Key1", "ParameterValue": "Value"},91{"ParameterKey": "Key2", "UsePreviousValue": True},92{"ParameterKey": "Key3", "UsePreviousValue": False},93]94# Parameters that Use Previous Value will be removed on stack creation95# to either force CloudFormation to use the Default value, or ask user to specify a parameter96filtered_parameters = [97{"ParameterKey": "Key1", "ParameterValue": "Value"},98{"ParameterKey": "Key3", "UsePreviousValue": False},99]100capabilities = ["capabilities"]101role_arn = "arn:aws:iam::1234567890:role"102notification_arns = ["arn:aws:sns:region:1234567890:notify"]103s3_uploader = None104105tags = [{"Key":"key1", "Value": "val1"}]106107# Case 1: Stack DOES NOT exist108self.deployer.has_stack = mock.Mock()109self.deployer.has_stack.return_value = False110111expected_params = {112"ChangeSetName": botocore.stub.ANY,113"StackName": stack_name,114"TemplateBody": template,115"ChangeSetType": "CREATE",116"Parameters": filtered_parameters,117"Capabilities": capabilities,118"Description": botocore.stub.ANY,119"RoleARN": role_arn,120"NotificationARNs": notification_arns,121"Tags": tags122}123124response = {125"Id": "changeset ID"126}127128self.stub_client.add_response("create_change_set", response,129expected_params)130with self.stub_client:131result = self.deployer.create_changeset(132stack_name, template, parameters, capabilities, role_arn,133notification_arns, s3_uploader, tags)134self.assertEqual(response["Id"], result.changeset_id)135self.assertEqual("CREATE", result.changeset_type)136137# Case 2: Stack exists. We are updating it138self.deployer.has_stack.return_value = True139self.stub_client.add_response("get_template_summary",140{"Parameters": [{"ParameterKey": parameter["ParameterKey"]}141for parameter in parameters]},142{"StackName": stack_name})143expected_params["ChangeSetType"] = "UPDATE"144expected_params["Parameters"] = parameters145self.stub_client.add_response("create_change_set", response,146expected_params)147# template has new parameter but should not be included in148# expected_params as no previous value149parameters = list(parameters) + \150[{"ParameterKey": "New", "UsePreviousValue": True}]151with self.stub_client:152result = self.deployer.create_changeset(153stack_name, template, parameters, capabilities, role_arn,154notification_arns, s3_uploader, tags)155self.assertEqual(response["Id"], result.changeset_id)156self.assertEqual("UPDATE", result.changeset_type)157158def test_create_changeset_success_s3_bucket(self):159stack_name = "stack_name"160template = "template"161template_url = "https://s3.amazonaws.com/bucket/file"162parameters = [163{"ParameterKey": "Key1", "ParameterValue": "Value"},164{"ParameterKey": "Key2", "UsePreviousValue": True},165{"ParameterKey": "Key3", "UsePreviousValue": False},166]167# Parameters that Use Previous Value will be removed on stack creation168# to either force CloudFormation to use the Default value, or ask user to specify a parameter169filtered_parameters = [170{"ParameterKey": "Key1", "ParameterValue": "Value"},171{"ParameterKey": "Key3", "UsePreviousValue": False},172]173capabilities = ["capabilities"]174role_arn = "arn:aws:iam::1234567890:role"175notification_arns = ["arn:aws:sns:region:1234567890:notify"]176177s3_uploader = mock.Mock()178def to_path_style_s3_url(some_string, Version=None):179return "https://s3.amazonaws.com/bucket/file"180s3_uploader.to_path_style_s3_url = to_path_style_s3_url181def upload_with_dedup(filename,extension):182return "s3://bucket/file"183s3_uploader.upload_with_dedup = upload_with_dedup184185# Case 1: Stack DOES NOT exist186self.deployer.has_stack = mock.Mock()187self.deployer.has_stack.return_value = False188189expected_params = {190"ChangeSetName": botocore.stub.ANY,191"StackName": stack_name,192"TemplateURL": template_url,193"ChangeSetType": "CREATE",194"Parameters": filtered_parameters,195"Capabilities": capabilities,196"Description": botocore.stub.ANY,197"RoleARN": role_arn,198"Tags": [],199"NotificationARNs": notification_arns200}201202response = {203"Id": "changeset ID"204}205206self.stub_client.add_response("create_change_set", response,207expected_params)208with self.stub_client:209result = self.deployer.create_changeset(210stack_name, template, parameters, capabilities, role_arn,211notification_arns, s3_uploader, [])212self.assertEqual(response["Id"], result.changeset_id)213self.assertEqual("CREATE", result.changeset_type)214215# Case 2: Stack exists. We are updating it216self.deployer.has_stack.return_value = True217self.stub_client.add_response("get_template_summary",218{"Parameters": [{"ParameterKey": parameter["ParameterKey"]}219for parameter in parameters]},220{"StackName": stack_name})221expected_params["ChangeSetType"] = "UPDATE"222expected_params["Parameters"] = parameters223# template has new parameter but should not be included in224# expected_params as no previous value225parameters = list(parameters) + \226[{"ParameterKey": "New", "UsePreviousValue": True}]227self.stub_client.add_response("create_change_set", response,228expected_params)229with self.stub_client:230result = self.deployer.create_changeset(231stack_name, template, parameters, capabilities, role_arn,232notification_arns, s3_uploader, [])233self.assertEqual(response["Id"], result.changeset_id)234self.assertEqual("UPDATE", result.changeset_type)235236def test_create_changeset_exception(self):237stack_name = "stack_name"238template = "template"239parameters = [{"ParameterKey": "Key1", "ParameterValue": "Value",240"UsePreviousValue": True}]241capabilities = ["capabilities"]242role_arn = "arn:aws:iam::1234567890:role"243notification_arns = ["arn:aws:sns:region:1234567890:notify"]244s3_uploader = None245tags = [{"Key":"key1", "Value": "val1"}]246247self.deployer.has_stack = mock.Mock()248self.deployer.has_stack.return_value = False249250self.stub_client.add_client_error(251'create_change_set', "Somethign is wrong", "Service is bad")252with self.stub_client:253with self.assertRaises(botocore.exceptions.ClientError):254self.deployer.create_changeset(stack_name, template, parameters,255capabilities, role_arn, notification_arns, None, tags)256257def test_execute_changeset(self):258stack_name = "stack_name"259changeset_id = "changeset_id"260261expected_params = {262"ChangeSetName": changeset_id,263"StackName": stack_name,264"DisableRollback": False265}266267self.stub_client.add_response("execute_change_set", {}, expected_params)268with self.stub_client:269self.deployer.execute_changeset(changeset_id, stack_name)270271def test_execute_changeset_disable_rollback(self):272stack_name = "stack_name"273changeset_id = "changeset_id"274disable_rollback = True275276expected_params = {277"ChangeSetName": changeset_id,278"StackName": stack_name,279"DisableRollback": disable_rollback280}281282self.stub_client.add_response("execute_change_set", {}, expected_params)283with self.stub_client:284self.deployer.execute_changeset(changeset_id, stack_name,285disable_rollback)286287def test_execute_changeset_exception(self):288stack_name = "stack_name"289changeset_id = "changeset_id"290291self.stub_client.add_client_error(292'execute_change_set', "Somethign is wrong", "Service is bad")293with self.stub_client:294with self.assertRaises(botocore.exceptions.ClientError):295self.deployer.execute_changeset(changeset_id, stack_name)296297def test_create_and_wait_for_changeset_successful(self):298stack_name = "stack_name"299template = "template"300parameters = [{"ParameterKey": "Key1", "ParameterValue": "Value",301"UsePreviousValue": True}]302capabilities = ["capabilities"]303changeset_id = "changeset id"304changeset_type = "changeset type"305role_arn = "arn:aws:iam::1234567890:role"306notification_arns = ["arn:aws:sns:region:1234567890:notify"]307s3_uploader = None308tags = [{"Key":"key1", "Value": "val1"}]309310self.deployer.create_changeset = mock.Mock()311self.deployer.create_changeset.return_value = ChangeSetResult(changeset_id, changeset_type)312313self.deployer.wait_for_changeset = mock.Mock()314315result = self.deployer.create_and_wait_for_changeset(316stack_name, template, parameters, capabilities, role_arn,317notification_arns, s3_uploader, tags)318self.assertEqual(result.changeset_id, changeset_id)319self.assertEqual(result.changeset_type, changeset_type)320321def test_create_and_wait_for_changeset_error_waiting_for_changeset(self):322stack_name = "stack_name"323template = "template"324parameters = [{"ParameterKey": "Key1", "ParameterValue": "Value",325"UsePreviousValue": True}]326capabilities = ["capabilities"]327changeset_id = "changeset id"328changeset_type = "changeset type"329role_arn = "arn:aws:iam::1234567890:role"330notification_arns = ["arn:aws:sns:region:1234567890:notify"]331s3_uploader = None332tags = [{"Key":"key1", "Value": "val1"}]333334self.deployer.create_changeset = mock.Mock()335self.deployer.create_changeset.return_value = ChangeSetResult(changeset_id, changeset_type)336337self.deployer.wait_for_changeset = mock.Mock()338self.deployer.wait_for_changeset.side_effect = RuntimeError339340with self.assertRaises(RuntimeError):341result = self.deployer.create_and_wait_for_changeset(342stack_name, template, parameters, capabilities, role_arn,343notification_arns, s3_uploader, tags)344345def test_wait_for_changeset_no_changes(self):346stack_name = "stack_name"347changeset_id = "changeset-id"348349mock_client = mock.Mock()350mock_deployer = Deployer(mock_client)351mock_waiter = mock.Mock()352mock_client.get_waiter.return_value = mock_waiter353354response = {355"Status": "FAILED",356"StatusReason": "The submitted information didn't contain changes."357}358359waiter_error = botocore.exceptions.WaiterError(name="name",360reason="reason",361last_response=response)362mock_waiter.wait.side_effect = waiter_error363364with self.assertRaises(exceptions.ChangeEmptyError):365mock_deployer.wait_for_changeset(changeset_id, stack_name)366367waiter_config = {'Delay': 5}368mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id,369StackName=stack_name,370WaiterConfig=waiter_config)371372mock_client.get_waiter.assert_called_once_with(373"change_set_create_complete")374375def test_wait_for_changeset_no_changes_with_another_error_msg(self):376stack_name = "stack_name"377changeset_id = "changeset-id"378379mock_client = mock.Mock()380mock_deployer = Deployer(mock_client)381mock_waiter = mock.Mock()382mock_client.get_waiter.return_value = mock_waiter383384response = {385"Status": "FAILED",386"StatusReason": "No updates are to be performed"387}388389waiter_error = botocore.exceptions.WaiterError(name="name",390reason="reason",391last_response=response)392mock_waiter.wait.side_effect = waiter_error393394with self.assertRaises(exceptions.ChangeEmptyError):395mock_deployer.wait_for_changeset(changeset_id, stack_name)396397waiter_config = {'Delay': 5}398mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id,399StackName=stack_name,400WaiterConfig=waiter_config)401402mock_client.get_waiter.assert_called_once_with(403"change_set_create_complete")404405def test_wait_for_changeset_failed_to_create_changeset(self):406stack_name = "stack_name"407changeset_id = "changeset-id"408409mock_client = mock.Mock()410mock_deployer = Deployer(mock_client)411mock_waiter = mock.Mock()412mock_client.get_waiter.return_value = mock_waiter413414response = {415"Status": "FAILED",416"StatusReason": "some reason"417}418419waiter_error = botocore.exceptions.WaiterError(name="name",420reason="reason",421last_response=response)422mock_waiter.wait.side_effect = waiter_error423424with self.assertRaises(RuntimeError):425mock_deployer.wait_for_changeset(changeset_id, stack_name)426427waiter_config = {'Delay': 5}428mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id,429StackName=stack_name,430WaiterConfig=waiter_config)431432mock_client.get_waiter.assert_called_once_with(433"change_set_create_complete")434435def test_wait_for_execute_no_changes(self):436stack_name = "stack_name"437changeset_type = "CREATE"438439mock_client = mock.Mock()440mock_deployer = Deployer(mock_client)441mock_waiter = mock.Mock()442mock_client.get_waiter.return_value = mock_waiter443444waiter_error = botocore.exceptions.WaiterError(name="name",445reason="reason",446last_response={})447mock_waiter.wait.side_effect = waiter_error448449with self.assertRaises(exceptions.DeployFailedError):450mock_deployer.wait_for_execute(stack_name, changeset_type)451452waiter_config = {453'Delay': 30,454'MaxAttempts': 120,455}456mock_waiter.wait.assert_called_once_with(StackName=stack_name,457WaiterConfig=waiter_config)458459mock_client.get_waiter.assert_called_once_with(460"stack_create_complete")461462463def make_stack_obj(stack_name, status="CREATE_COMPLETE"):464return {465"StackId": stack_name,466"StackName": stack_name,467"CreationTime": "2013-08-23T01:02:15.422Z",468"StackStatus": status469}470471472