def setUp(self): client = botocore.session.get_session().create_client( 'cloudformation', region_name="us-east-1") self.stub_client = Stubber(client) self.changeset_prefix = "some-changeset-prefix" self.deployer = Deployer(client, self.changeset_prefix)
def test_wait_for_changeset_failed_to_create_changeset(self): stack_name = "stack_name" changeset_id = "changeset-id" mock_client = mock.Mock() mock_deployer = Deployer(mock_client) mock_waiter = mock.Mock() mock_client.get_waiter.return_value = mock_waiter response = {"Status": "FAILED", "StatusReason": "some reason"} waiter_error = botocore.exceptions.WaiterError(name="name", reason="reason", last_response=response) mock_waiter.wait.side_effect = waiter_error with self.assertRaises(RuntimeError): mock_deployer.wait_for_changeset(changeset_id, stack_name) waiter_config = {'Delay': 5} mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id, StackName=stack_name, WaiterConfig=waiter_config) mock_client.get_waiter.assert_called_once_with( "change_set_create_complete")
def test_wait_for_changeset_failed_to_create_changeset(self): stack_name = "stack_name" changeset_id = "changeset-id" mock_client = Mock() mock_deployer = Deployer(mock_client) mock_waiter = Mock() mock_client.get_waiter.return_value = mock_waiter response = { "Status": "FAILED", "StatusReason": "some reason" } waiter_error = botocore.exceptions.WaiterError(name="name", reason="reason", last_response=response) mock_waiter.wait.side_effect = waiter_error with self.assertRaises(RuntimeError): mock_deployer.wait_for_changeset(changeset_id, stack_name) mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id, StackName=stack_name) mock_client.get_waiter.assert_called_once_with( "change_set_create_complete")
def test_wait_for_changeset_no_changes(self): stack_name = "stack_name" changeset_id = "changeset-id" mock_client = Mock() mock_deployer = Deployer(mock_client) mock_waiter = Mock() mock_client.get_waiter.return_value = mock_waiter response = { "Status": "FAILED", "StatusReason": "No updates are to be performed" } waiter_error = botocore.exceptions.WaiterError(name="name", reason="reason", last_response=response) mock_waiter.wait.side_effect = waiter_error with self.assertRaises(exceptions.ChangeEmptyError): mock_deployer.wait_for_changeset(changeset_id, stack_name) mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id, StackName=stack_name) mock_client.get_waiter.assert_called_once_with( "change_set_create_complete")
def test_wait_for_execute_no_changes(self): stack_name = "stack_name" changeset_type = "CREATE" mock_client = Mock() mock_deployer = Deployer(mock_client) mock_waiter = Mock() mock_client.get_waiter.return_value = mock_waiter waiter_error = botocore.exceptions.WaiterError(name="name", reason="reason", last_response={}) mock_waiter.wait.side_effect = waiter_error with self.assertRaises(exceptions.DeployFailedError): mock_deployer.wait_for_execute(stack_name, changeset_type) waiter_config = { 'Delay': 5, 'MaxAttempts': 720, } mock_waiter.wait.assert_called_once_with(StackName=stack_name, WaiterConfig=waiter_config) mock_client.get_waiter.assert_called_once_with( "stack_create_complete")
def test_wait_for_changeset_no_changes(self): stack_name = "stack_name" changeset_id = "changeset-id" mock_client = Mock() mock_deployer = Deployer(mock_client) mock_waiter = Mock() mock_client.get_waiter.return_value = mock_waiter response = { "Status": "FAILED", "StatusReason": "No updates are to be performed" } waiter_error = botocore.exceptions.WaiterError(name="name", reason="reason", last_response=response) mock_waiter.wait.side_effect = waiter_error with self.assertRaises(exceptions.ChangeEmptyError): mock_deployer.wait_for_changeset(changeset_id, stack_name) waiter_config = {'Delay': 5} mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id, StackName=stack_name, WaiterConfig=waiter_config) mock_client.get_waiter.assert_called_once_with( "change_set_create_complete")
def test_wait_for_execute_no_changes(self): stack_name = "stack_name" changeset_type = "CREATE" mock_client = mock.Mock() mock_deployer = Deployer(mock_client) mock_waiter = mock.Mock() mock_client.get_waiter.return_value = mock_waiter waiter_error = botocore.exceptions.WaiterError(name="name", reason="reason", last_response={}) mock_waiter.wait.side_effect = waiter_error with self.assertRaises(exceptions.DeployFailedError): mock_deployer.wait_for_execute(stack_name, changeset_type) waiter_config = { 'Delay': 30, 'MaxAttempts': 120, } mock_waiter.wait.assert_called_once_with(StackName=stack_name, WaiterConfig=waiter_config) mock_client.get_waiter.assert_called_once_with("stack_create_complete")
def setUp(self): client = botocore.session.get_session().create_client('cloudformation', region_name="us-east-1") self.stub_client = Stubber(client) self.changeset_prefix = "some-changeset-prefix" self.deployer = Deployer(client, self.changeset_prefix)
def _run_main(self, parsed_args, parsed_globals): cloudformation_client = \ self._session.create_client( 'cloudformation', region_name=parsed_globals.region, endpoint_url=parsed_globals.endpoint_url, verify=parsed_globals.verify_ssl) template_path = parsed_args.template_file if not os.path.isfile(template_path): raise exceptions.InvalidTemplatePathError( template_path=template_path) # Parse parameters with open(template_path, "r") as handle: template_str = handle.read() stack_name = parsed_args.stack_name parameter_overrides = self.parse_parameter_arg( parsed_args.parameter_overrides) template_dict = yaml_parse(template_str) parameters = self.merge_parameters(template_dict, parameter_overrides) deployer = Deployer(cloudformation_client) return self.deploy(deployer, stack_name, template_str, parameters, parsed_args.capabilities, parsed_args.execute_changeset, parsed_args.role_arn, parsed_args.notification_arns, parsed_args.fail_on_empty_changeset)
def deploy(self): """ [option] option: stack-name: CloudFormation stack name template-path: CloudFormation template file path params-path: CloudFormation template parameters file path. allowed file type: json, jsonnet """ client = boto3.client('cloudformation') if not os.path.isfile(self.template_path): raise exceptions.InvalidTemplatePathError(template_path=self.template_path) with open(self.template_path, "r") as f: template_str = f.read() params = self.__load_cfn_params(self.params_path) tags = [] s3_uploader = None deployer = Deployer(client) self.__deploy(deployer, self.stack_name, template_str, params, ['CAPABILITY_IAM', 'CAPABILITY_AUTO_EXPAND'], True, None, None, s3_uploader, tags, False)
def setUp(self): self.session = mock.Mock() self.session.get_scoped_config.return_value = {} self.parsed_args = FakeArgs( template_file='./foo', stack_name="some_stack_name", parameter_overrides=["Key1=Value1", "Key2=Value2"], no_execute_changeset=False, execute_changeset=True, capabilities=None, role_arn=None, notification_arns=[], fail_on_empty_changeset=True, s3_bucket=None, s3_prefix="some prefix", kms_key_id="some kms key id", force_upload=True, tags=["tagkey1=tagvalue1"]) self.parsed_globals = FakeArgs(region="us-east-1", endpoint_url=None, verify_ssl=None) self.deploy_command = DeployCommand(self.session) self.deployer = Deployer(Mock()) self.deployer.create_and_wait_for_changeset = Mock() self.deployer.execute_changeset = Mock() self.deployer.wait_for_execute = Mock()
def deploy_template(session, config, packaged_yaml, approve=False): success = True print('\nDeploying...') client = session.create_client('cloudformation') stack_name = conventions.generate_stack_name(config['Parameters']) deployer = Deployer(client) tags = conventions.merge_tags(config.get('Tags', {}), config['Parameters']) try: result = deployer.create_and_wait_for_changeset( stack_name=stack_name, cfn_template=packaged_yaml, parameter_values=deploy_template_parameters_builder( config['Parameters']), capabilities=['CAPABILITY_NAMED_IAM'], role_arn=None, notification_arns=None, s3_uploader=None, tags=deploy_template_tags_builder(tags)) display_changeset(session, stack_name, result.changeset_id) if not approve: utils.get_confirmation() execute_changeset_token = str(uuid.uuid4()) client.execute_change_set(ChangeSetName=result.changeset_id, StackName=stack_name, ClientRequestToken=execute_changeset_token) deployer.wait_for_execute(stack_name, result.changeset_type) except exceptions.ChangeEmptyError as ex: logging.error(ex) success = False except exceptions.DeployFailedError as ex: logging.error(ex) # print(colored(ex.response['Error']['Message'], 'red')) stack_events = get_stack_events(session, stack_name, execute_changeset_token) aws.display_cfn_stack_events(stack_events) success = False except botocore.exceptions.ClientError as ex: logging.error(ex) success = False print('Done.') return success
def _run_main(self, parsed_args, parsed_globals): cloudformation_client = \ self._session.create_client( 'cloudformation', region_name=parsed_globals.region, endpoint_url=parsed_globals.endpoint_url, verify=parsed_globals.verify_ssl) template_path = parsed_args.template_file if not os.path.isfile(template_path): raise exceptions.InvalidTemplatePathError( template_path=template_path) # Parse parameters with open(template_path, "r") as handle: template_str = handle.read() stack_name = parsed_args.stack_name parameter_overrides = self.parse_key_value_arg( parsed_args.parameter_overrides, self.PARAMETER_OVERRIDE_CMD) tags_dict = self.parse_key_value_arg(parsed_args.tags, self.TAGS_CMD) tags = [{"Key": key, "Value": value} for key, value in tags_dict.items()] template_dict = yaml_parse(template_str) parameters = self.merge_parameters(template_dict, parameter_overrides) template_size = os.path.getsize(parsed_args.template_file) if template_size > 51200 and not parsed_args.s3_bucket: raise exceptions.DeployBucketRequiredError() bucket = parsed_args.s3_bucket if bucket: s3_client = self._session.create_client( "s3", config=Config(signature_version='s3v4'), region_name=parsed_globals.region, verify=parsed_globals.verify_ssl) s3_uploader = S3Uploader(s3_client, bucket, parsed_args.s3_prefix, parsed_args.kms_key_id, parsed_args.force_upload) else: s3_uploader = None deployer = Deployer(cloudformation_client) return self.deploy(deployer, stack_name, template_str, parameters, parsed_args.capabilities, parsed_args.execute_changeset, parsed_args.role_arn, parsed_args.notification_arns, s3_uploader, tags, parsed_args.fail_on_empty_changeset)
def publish(self, stack_name): d = Deployer(boto3.client('cloudformation')) result = d.create_and_wait_for_changeset( stack_name=stack_name, cfn_template=self.get_template(), parameter_values=[], capabilities=['CAPABILITY_IAM']) d.execute_changeset(result.changeset_id, stack_name) d.wait_for_execute(stack_name, result.changeset_type)
def setUp(self): self.session = mock.Mock() self.session.get_scoped_config.return_value = {} self.parsed_args = FakeArgs( template_file='./foo', stack_name="some_stack_name", parameter_overrides=["Key1=Value1", "Key2=Value2"], no_execute_changeset=False, execute_changeset=True, capabilities=None) self.parsed_globals = FakeArgs(region="us-east-1", endpoint_url=None, verify_ssl=None) self.deploy_command = DeployCommand(self.session) self.deployer = Deployer(Mock()) self.deployer.create_and_wait_for_changeset = Mock() self.deployer.execute_changeset = Mock() self.deployer.wait_for_execute = Mock()
class TestDeployer(unittest.TestCase): def setUp(self): client = botocore.session.get_session().create_client( 'cloudformation', region_name="us-east-1") self.stub_client = Stubber(client) self.changeset_prefix = "some-changeset-prefix" self.deployer = Deployer(client, self.changeset_prefix) def test_has_stack_success(self): stack_name = "stack_name" expected_params = {"StackName": stack_name} response = {"Stacks": [make_stack_obj(stack_name)]} self.stub_client.add_response('describe_stacks', response, expected_params) with self.stub_client: response = self.deployer.has_stack(stack_name) self.assertTrue(response) def test_has_stack_no_stack(self): stack_name = "stack_name" expected_params = {"StackName": stack_name} # Response contains NO stack no_stack_response = {"Stacks": []} self.stub_client.add_response('describe_stacks', no_stack_response, expected_params) with self.stub_client: response = self.deployer.has_stack(stack_name) self.assertFalse(response) # Response is a ClientError with a message that the stack does not exist self.stub_client.add_client_error( 'describe_stacks', "ClientError", "Stack with id {0} does not exist".format(stack_name)) with self.stub_client: response = self.deployer.has_stack(stack_name) self.assertFalse(response) def test_has_stack_review_in_progress(self): stack_name = "stack_name" expected_params = {"StackName": stack_name} # Response contains NO stack review_in_progress_response = { "Stacks": [make_stack_obj(stack_name, "REVIEW_IN_PROGRESS")] } self.stub_client.add_response('describe_stacks', review_in_progress_response, expected_params) with self.stub_client: response = self.deployer.has_stack(stack_name) self.assertFalse(response) def test_has_stack_exception(self): self.stub_client.add_client_error('describe_stacks', "ValidationError", "Service is bad") with self.stub_client: with self.assertRaises(botocore.exceptions.ClientError): self.deployer.has_stack("stack_name") def test_create_changeset_success(self): stack_name = "stack_name" template = "template" parameters = [ { "ParameterKey": "Key1", "ParameterValue": "Value" }, { "ParameterKey": "Key2", "UsePreviousValue": True }, { "ParameterKey": "Key3", "UsePreviousValue": False }, ] # Parameters that Use Previous Value will be removed on stack creation # to either force CloudFormation to use the Default value, or ask user to specify a parameter filtered_parameters = [ { "ParameterKey": "Key1", "ParameterValue": "Value" }, { "ParameterKey": "Key3", "UsePreviousValue": False }, ] capabilities = ["capabilities"] role_arn = "arn:aws:iam::1234567890:role" notification_arns = ["arn:aws:sns:region:1234567890:notify"] # Case 1: Stack DOES NOT exist self.deployer.has_stack = Mock() self.deployer.has_stack.return_value = False expected_params = { "ChangeSetName": botocore.stub.ANY, "StackName": stack_name, "TemplateBody": template, "ChangeSetType": "CREATE", "Parameters": filtered_parameters, "Capabilities": capabilities, "Description": botocore.stub.ANY, "RoleARN": role_arn, "NotificationARNs": notification_arns } response = {"Id": "changeset ID"} self.stub_client.add_response("create_change_set", response, expected_params) with self.stub_client: result = self.deployer.create_changeset(stack_name, template, parameters, capabilities, role_arn, notification_arns) self.assertEquals(response["Id"], result.changeset_id) self.assertEquals("CREATE", result.changeset_type) # Case 2: Stack exists. We are updating it self.deployer.has_stack.return_value = True expected_params["ChangeSetType"] = "UPDATE" expected_params["Parameters"] = parameters self.stub_client.add_response("create_change_set", response, expected_params) with self.stub_client: result = self.deployer.create_changeset(stack_name, template, parameters, capabilities, role_arn, notification_arns) self.assertEquals(response["Id"], result.changeset_id) self.assertEquals("UPDATE", result.changeset_type) def test_create_changeset_exception(self): stack_name = "stack_name" template = "template" parameters = [{ "ParameterKey": "Key1", "ParameterValue": "Value", "UsePreviousValue": True }] capabilities = ["capabilities"] role_arn = "arn:aws:iam::1234567890:role" notification_arns = ["arn:aws:sns:region:1234567890:notify"] self.deployer.has_stack = Mock() self.deployer.has_stack.return_value = False self.stub_client.add_client_error('create_change_set', "Somethign is wrong", "Service is bad") with self.stub_client: with self.assertRaises(botocore.exceptions.ClientError): self.deployer.create_changeset(stack_name, template, parameters, capabilities, role_arn, notification_arns) def test_execute_changeset(self): stack_name = "stack_name" changeset_id = "changeset_id" expected_params = { "ChangeSetName": changeset_id, "StackName": stack_name } self.stub_client.add_response("execute_change_set", {}, expected_params) with self.stub_client: self.deployer.execute_changeset(changeset_id, stack_name) def test_execute_changeset_exception(self): stack_name = "stack_name" changeset_id = "changeset_id" self.stub_client.add_client_error('execute_change_set', "Somethign is wrong", "Service is bad") with self.stub_client: with self.assertRaises(botocore.exceptions.ClientError): self.deployer.execute_changeset(changeset_id, stack_name) def test_create_and_wait_for_changeset_successful(self): stack_name = "stack_name" template = "template" parameters = [{ "ParameterKey": "Key1", "ParameterValue": "Value", "UsePreviousValue": True }] capabilities = ["capabilities"] changeset_id = "changeset id" changeset_type = "changeset type" role_arn = "arn:aws:iam::1234567890:role" notification_arns = ["arn:aws:sns:region:1234567890:notify"] self.deployer.create_changeset = Mock() self.deployer.create_changeset.return_value = ChangeSetResult( changeset_id, changeset_type) self.deployer.wait_for_changeset = Mock() result = self.deployer.create_and_wait_for_changeset( stack_name, template, parameters, capabilities, role_arn, notification_arns) self.assertEquals(result.changeset_id, changeset_id) self.assertEquals(result.changeset_type, changeset_type) def test_create_and_wait_for_changeset_error_waiting_for_changeset(self): stack_name = "stack_name" template = "template" parameters = [{ "ParameterKey": "Key1", "ParameterValue": "Value", "UsePreviousValue": True }] capabilities = ["capabilities"] changeset_id = "changeset id" changeset_type = "changeset type" role_arn = "arn:aws:iam::1234567890:role" notification_arns = ["arn:aws:sns:region:1234567890:notify"] self.deployer.create_changeset = Mock() self.deployer.create_changeset.return_value = ChangeSetResult( changeset_id, changeset_type) self.deployer.wait_for_changeset = Mock() self.deployer.wait_for_changeset.side_effect = RuntimeError with self.assertRaises(RuntimeError): result = self.deployer.create_and_wait_for_changeset( stack_name, template, parameters, capabilities, role_arn, notification_arns) def test_wait_for_changeset_no_changes(self): stack_name = "stack_name" changeset_id = "changeset-id" mock_client = Mock() mock_deployer = Deployer(mock_client) mock_waiter = Mock() mock_client.get_waiter.return_value = mock_waiter response = { "Status": "FAILED", "StatusReason": "The submitted information didn't contain changes." } waiter_error = botocore.exceptions.WaiterError(name="name", reason="reason", last_response=response) mock_waiter.wait.side_effect = waiter_error with self.assertRaises(exceptions.ChangeEmptyError): mock_deployer.wait_for_changeset(changeset_id, stack_name) waiter_config = {'Delay': 5} mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id, StackName=stack_name, WaiterConfig=waiter_config) mock_client.get_waiter.assert_called_once_with( "change_set_create_complete") def test_wait_for_changeset_failed_to_create_changeset(self): stack_name = "stack_name" changeset_id = "changeset-id" mock_client = Mock() mock_deployer = Deployer(mock_client) mock_waiter = Mock() mock_client.get_waiter.return_value = mock_waiter response = {"Status": "FAILED", "StatusReason": "some reason"} waiter_error = botocore.exceptions.WaiterError(name="name", reason="reason", last_response=response) mock_waiter.wait.side_effect = waiter_error with self.assertRaises(RuntimeError): mock_deployer.wait_for_changeset(changeset_id, stack_name) waiter_config = {'Delay': 5} mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id, StackName=stack_name, WaiterConfig=waiter_config) mock_client.get_waiter.assert_called_once_with( "change_set_create_complete") def test_wait_for_execute_no_changes(self): stack_name = "stack_name" changeset_type = "CREATE" mock_client = Mock() mock_deployer = Deployer(mock_client) mock_waiter = Mock() mock_client.get_waiter.return_value = mock_waiter waiter_error = botocore.exceptions.WaiterError(name="name", reason="reason", last_response={}) mock_waiter.wait.side_effect = waiter_error with self.assertRaises(exceptions.DeployFailedError): mock_deployer.wait_for_execute(stack_name, changeset_type) waiter_config = { 'Delay': 5, 'MaxAttempts': 720, } mock_waiter.wait.assert_called_once_with(StackName=stack_name, WaiterConfig=waiter_config) mock_client.get_waiter.assert_called_once_with("stack_create_complete")
class TestDeployer(unittest.TestCase): def setUp(self): client = botocore.session.get_session().create_client( 'cloudformation', region_name="us-east-1") self.stub_client = Stubber(client) self.changeset_prefix = "some-changeset-prefix" self.deployer = Deployer(client, self.changeset_prefix) def test_has_stack_success(self): stack_name = "stack_name" expected_params = {"StackName": stack_name} response = {"Stacks": [make_stack_obj(stack_name)]} self.stub_client.add_response('describe_stacks', response, expected_params) with self.stub_client: response = self.deployer.has_stack(stack_name) self.assertTrue(response) def test_has_stack_no_stack(self): stack_name = "stack_name" expected_params = {"StackName": stack_name} # Response contains NO stack no_stack_response = {"Stacks": []} self.stub_client.add_response('describe_stacks', no_stack_response, expected_params) with self.stub_client: response = self.deployer.has_stack(stack_name) self.assertFalse(response) # Response is a ClientError with a message that the stack does not exist self.stub_client.add_client_error( 'describe_stacks', "ClientError", "Stack with id {0} does not exist".format(stack_name)) with self.stub_client: response = self.deployer.has_stack(stack_name) self.assertFalse(response) def test_has_stack_review_in_progress(self): stack_name = "stack_name" expected_params = {"StackName": stack_name} # Response contains NO stack review_in_progress_response = { "Stacks": [make_stack_obj(stack_name, "REVIEW_IN_PROGRESS")] } self.stub_client.add_response('describe_stacks', review_in_progress_response, expected_params) with self.stub_client: response = self.deployer.has_stack(stack_name) self.assertFalse(response) def test_has_stack_exception(self): self.stub_client.add_client_error('describe_stacks', "ValidationError", "Service is bad") with self.stub_client: with self.assertRaises(botocore.exceptions.ClientError): self.deployer.has_stack("stack_name") def test_create_changeset_success(self): stack_name = "stack_name" template = "template" parameters = [{ "ParameterKey": "Key1", "ParameterValue": "Value", "UsePreviousValue": True }] capabilities = ["capabilities"] # Case 1: Stack DOES NOT exist self.deployer.has_stack = Mock() self.deployer.has_stack.return_value = False expected_params = { "ChangeSetName": botocore.stub.ANY, "StackName": stack_name, "TemplateBody": template, "ChangeSetType": "CREATE", "Parameters": parameters, "Capabilities": capabilities, "Description": botocore.stub.ANY } response = {"Id": "changeset ID"} self.stub_client.add_response("create_change_set", response, expected_params) with self.stub_client: result = self.deployer.create_changeset(stack_name, template, parameters, capabilities) self.assertEquals(response["Id"], result.changeset_id) self.assertEquals("CREATE", result.changeset_type) # Case 2: Stack exists. We are updating it self.deployer.has_stack.return_value = True expected_params["ChangeSetType"] = "UPDATE" self.stub_client.add_response("create_change_set", response, expected_params) with self.stub_client: result = self.deployer.create_changeset(stack_name, template, parameters, capabilities) self.assertEquals(response["Id"], result.changeset_id) self.assertEquals("UPDATE", result.changeset_type) def test_create_changeset_exception(self): stack_name = "stack_name" template = "template" parameters = [{ "ParameterKey": "Key1", "ParameterValue": "Value", "UsePreviousValue": True }] capabilities = ["capabilities"] self.deployer.has_stack = Mock() self.deployer.has_stack.return_value = False self.stub_client.add_client_error('create_change_set', "Somethign is wrong", "Service is bad") with self.stub_client: with self.assertRaises(botocore.exceptions.ClientError): self.deployer.create_changeset(stack_name, template, parameters, capabilities) def test_execute_changeset(self): stack_name = "stack_name" changeset_id = "changeset_id" expected_params = { "ChangeSetName": changeset_id, "StackName": stack_name } self.stub_client.add_response("execute_change_set", {}, expected_params) with self.stub_client: self.deployer.execute_changeset(changeset_id, stack_name) def test_execute_changeset_exception(self): stack_name = "stack_name" changeset_id = "changeset_id" self.stub_client.add_client_error('execute_change_set', "Somethign is wrong", "Service is bad") with self.stub_client: with self.assertRaises(botocore.exceptions.ClientError): self.deployer.execute_changeset(changeset_id, stack_name) def test_create_and_wait_for_changeset_successful(self): stack_name = "stack_name" template = "template" parameters = [{ "ParameterKey": "Key1", "ParameterValue": "Value", "UsePreviousValue": True }] capabilities = ["capabilities"] changeset_id = "changeset id" changeset_type = "changeset type" self.deployer.create_changeset = Mock() self.deployer.create_changeset.return_value = ChangeSetResult( changeset_id, changeset_type) self.deployer.wait_for_changeset = Mock() result = self.deployer.create_and_wait_for_changeset( stack_name, template, parameters, capabilities) self.assertEquals(result.changeset_id, changeset_id) self.assertEquals(result.changeset_type, changeset_type) def test_create_and_wait_for_changeset_error_waiting_for_changeset(self): stack_name = "stack_name" template = "template" parameters = [{ "ParameterKey": "Key1", "ParameterValue": "Value", "UsePreviousValue": True }] capabilities = ["capabilities"] changeset_id = "changeset id" changeset_type = "changeset type" self.deployer.create_changeset = Mock() self.deployer.create_changeset.return_value = ChangeSetResult( changeset_id, changeset_type) self.deployer.wait_for_changeset = Mock() self.deployer.wait_for_changeset.side_effect = RuntimeError with self.assertRaises(RuntimeError): result = self.deployer.create_and_wait_for_changeset( stack_name, template, parameters, capabilities) def test_wait_for_changeset_no_changes(self): stack_name = "stack_name" changeset_id = "changeset-id" mock_client = Mock() mock_deployer = Deployer(mock_client) mock_waiter = Mock() mock_client.get_waiter.return_value = mock_waiter response = { "Status": "FAILED", "StatusReason": "No updates are to be performed" } waiter_error = botocore.exceptions.WaiterError(name="name", reason="reason", last_response=response) mock_waiter.wait.side_effect = waiter_error with self.assertRaises(exceptions.ChangeEmptyError): mock_deployer.wait_for_changeset(changeset_id, stack_name) mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id, StackName=stack_name) mock_client.get_waiter.assert_called_once_with( "change_set_create_complete") def test_wait_for_changeset_failed_to_create_changeset(self): stack_name = "stack_name" changeset_id = "changeset-id" mock_client = Mock() mock_deployer = Deployer(mock_client) mock_waiter = Mock() mock_client.get_waiter.return_value = mock_waiter response = {"Status": "FAILED", "StatusReason": "some reason"} waiter_error = botocore.exceptions.WaiterError(name="name", reason="reason", last_response=response) mock_waiter.wait.side_effect = waiter_error with self.assertRaises(RuntimeError): mock_deployer.wait_for_changeset(changeset_id, stack_name) mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id, StackName=stack_name) mock_client.get_waiter.assert_called_once_with( "change_set_create_complete") def test_wait_for_execute_no_changes(self): stack_name = "stack_name" changeset_type = "CREATE" mock_client = Mock() mock_deployer = Deployer(mock_client) mock_waiter = Mock() mock_client.get_waiter.return_value = mock_waiter waiter_error = botocore.exceptions.WaiterError(name="name", reason="reason", last_response={}) mock_waiter.wait.side_effect = waiter_error with self.assertRaises(exceptions.DeployFailedError): mock_deployer.wait_for_execute(stack_name, changeset_type) mock_waiter.wait.assert_called_once_with(StackName=stack_name) mock_client.get_waiter.assert_called_once_with("stack_create_complete")
class TestDeployer(unittest.TestCase): def setUp(self): client = botocore.session.get_session().create_client('cloudformation', region_name="us-east-1") self.stub_client = Stubber(client) self.changeset_prefix = "some-changeset-prefix" self.deployer = Deployer(client, self.changeset_prefix) def test_has_stack_success(self): stack_name = "stack_name" expected_params = { "StackName": stack_name } response = { "Stacks": [ make_stack_obj(stack_name) ] } self.stub_client.add_response('describe_stacks', response, expected_params) with self.stub_client: response = self.deployer.has_stack(stack_name) self.assertTrue(response) def test_has_stack_no_stack(self): stack_name = "stack_name" expected_params = { "StackName": stack_name } # Response contains NO stack no_stack_response = { "Stacks": [] } self.stub_client.add_response('describe_stacks', no_stack_response, expected_params) with self.stub_client: response = self.deployer.has_stack(stack_name) self.assertFalse(response) # Response is a ClientError with a message that the stack does not exist self.stub_client.add_client_error('describe_stacks', "ClientError", "Stack with id {0} does not exist" .format(stack_name)) with self.stub_client: response = self.deployer.has_stack(stack_name) self.assertFalse(response) def test_has_stack_review_in_progress(self): stack_name = "stack_name" expected_params = { "StackName": stack_name } # Response contains NO stack review_in_progress_response = { "Stacks": [make_stack_obj(stack_name, "REVIEW_IN_PROGRESS")] } self.stub_client.add_response('describe_stacks', review_in_progress_response, expected_params) with self.stub_client: response = self.deployer.has_stack(stack_name) self.assertFalse(response) def test_has_stack_exception(self): self.stub_client.add_client_error('describe_stacks', "ValidationError", "Service is bad") with self.stub_client: with self.assertRaises(botocore.exceptions.ClientError): self.deployer.has_stack("stack_name") def test_create_changeset_success(self): stack_name = "stack_name" template = "template" parameters = [{"ParameterKey": "Key1", "ParameterValue": "Value", "UsePreviousValue": True}] capabilities = ["capabilities"] # Case 1: Stack DOES NOT exist self.deployer.has_stack = Mock() self.deployer.has_stack.return_value = False expected_params = { "ChangeSetName": botocore.stub.ANY, "StackName": stack_name, "TemplateBody": template, "ChangeSetType": "CREATE", "Parameters": parameters, "Capabilities": capabilities, "Description": botocore.stub.ANY } response = { "Id": "changeset ID" } self.stub_client.add_response("create_change_set", response, expected_params) with self.stub_client: result = self.deployer.create_changeset( stack_name, template, parameters, capabilities) self.assertEquals(response["Id"], result.changeset_id) self.assertEquals("CREATE", result.changeset_type) # Case 2: Stack exists. We are updating it self.deployer.has_stack.return_value = True expected_params["ChangeSetType"] = "UPDATE" self.stub_client.add_response("create_change_set", response, expected_params) with self.stub_client: result = self.deployer.create_changeset( stack_name, template, parameters, capabilities) self.assertEquals(response["Id"], result.changeset_id) self.assertEquals("UPDATE", result.changeset_type) def test_create_changeset_exception(self): stack_name = "stack_name" template = "template" parameters = [{"ParameterKey": "Key1", "ParameterValue": "Value", "UsePreviousValue": True}] capabilities = ["capabilities"] self.deployer.has_stack = Mock() self.deployer.has_stack.return_value = False self.stub_client.add_client_error( 'create_change_set', "Somethign is wrong", "Service is bad") with self.stub_client: with self.assertRaises(botocore.exceptions.ClientError): self.deployer.create_changeset(stack_name, template, parameters, capabilities) def test_execute_changeset(self): stack_name = "stack_name" changeset_id = "changeset_id" expected_params = { "ChangeSetName": changeset_id, "StackName": stack_name } self.stub_client.add_response("execute_change_set", {}, expected_params) with self.stub_client: self.deployer.execute_changeset(changeset_id, stack_name) def test_execute_changeset_exception(self): stack_name = "stack_name" changeset_id = "changeset_id" self.stub_client.add_client_error( 'execute_change_set', "Somethign is wrong", "Service is bad") with self.stub_client: with self.assertRaises(botocore.exceptions.ClientError): self.deployer.execute_changeset(changeset_id, stack_name) def test_create_and_wait_for_changeset_successful(self): stack_name = "stack_name" template = "template" parameters = [{"ParameterKey": "Key1", "ParameterValue": "Value", "UsePreviousValue": True}] capabilities = ["capabilities"] changeset_id = "changeset id" changeset_type = "changeset type" self.deployer.create_changeset = Mock() self.deployer.create_changeset.return_value = ChangeSetResult(changeset_id, changeset_type) self.deployer.wait_for_changeset = Mock() result = self.deployer.create_and_wait_for_changeset( stack_name, template, parameters, capabilities) self.assertEquals(result.changeset_id, changeset_id) self.assertEquals(result.changeset_type, changeset_type) def test_create_and_wait_for_changeset_error_waiting_for_changeset(self): stack_name = "stack_name" template = "template" parameters = [{"ParameterKey": "Key1", "ParameterValue": "Value", "UsePreviousValue": True}] capabilities = ["capabilities"] changeset_id = "changeset id" changeset_type = "changeset type" self.deployer.create_changeset = Mock() self.deployer.create_changeset.return_value = ChangeSetResult(changeset_id, changeset_type) self.deployer.wait_for_changeset = Mock() self.deployer.wait_for_changeset.side_effect = RuntimeError with self.assertRaises(RuntimeError): result = self.deployer.create_and_wait_for_changeset( stack_name, template, parameters, capabilities) def test_wait_for_changeset_no_changes(self): stack_name = "stack_name" changeset_id = "changeset-id" mock_client = Mock() mock_deployer = Deployer(mock_client) mock_waiter = Mock() mock_client.get_waiter.return_value = mock_waiter response = { "Status": "FAILED", "StatusReason": "No updates are to be performed" } waiter_error = botocore.exceptions.WaiterError(name="name", reason="reason", last_response=response) mock_waiter.wait.side_effect = waiter_error with self.assertRaises(exceptions.ChangeEmptyError): mock_deployer.wait_for_changeset(changeset_id, stack_name) mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id, StackName=stack_name) mock_client.get_waiter.assert_called_once_with( "change_set_create_complete") def test_wait_for_changeset_failed_to_create_changeset(self): stack_name = "stack_name" changeset_id = "changeset-id" mock_client = Mock() mock_deployer = Deployer(mock_client) mock_waiter = Mock() mock_client.get_waiter.return_value = mock_waiter response = { "Status": "FAILED", "StatusReason": "some reason" } waiter_error = botocore.exceptions.WaiterError(name="name", reason="reason", last_response=response) mock_waiter.wait.side_effect = waiter_error with self.assertRaises(RuntimeError): mock_deployer.wait_for_changeset(changeset_id, stack_name) mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id, StackName=stack_name) mock_client.get_waiter.assert_called_once_with( "change_set_create_complete") def test_wait_for_execute_no_changes(self): stack_name = "stack_name" changeset_type = "CREATE" mock_client = Mock() mock_deployer = Deployer(mock_client) mock_waiter = Mock() mock_client.get_waiter.return_value = mock_waiter waiter_error = botocore.exceptions.WaiterError(name="name", reason="reason", last_response={}) mock_waiter.wait.side_effect = waiter_error with self.assertRaises(exceptions.DeployFailedError): mock_deployer.wait_for_execute(stack_name, changeset_type) mock_waiter.wait.assert_called_once_with(StackName=stack_name) mock_client.get_waiter.assert_called_once_with( "stack_create_complete")
class TestDeployer(unittest.TestCase): def setUp(self): client = botocore.session.get_session().create_client('cloudformation', region_name="us-east-1") self.stub_client = Stubber(client) self.changeset_prefix = "some-changeset-prefix" self.deployer = Deployer(client, self.changeset_prefix) def test_has_stack_success(self): stack_name = "stack_name" expected_params = { "StackName": stack_name } response = { "Stacks": [ make_stack_obj(stack_name) ] } self.stub_client.add_response('describe_stacks', response, expected_params) with self.stub_client: response = self.deployer.has_stack(stack_name) self.assertTrue(response) def test_has_stack_no_stack(self): stack_name = "stack_name" expected_params = { "StackName": stack_name } # Response contains NO stack no_stack_response = { "Stacks": [] } self.stub_client.add_response('describe_stacks', no_stack_response, expected_params) with self.stub_client: response = self.deployer.has_stack(stack_name) self.assertFalse(response) # Response is a ClientError with a message that the stack does not exist self.stub_client.add_client_error('describe_stacks', "ClientError", "Stack with id {0} does not exist" .format(stack_name)) with self.stub_client: response = self.deployer.has_stack(stack_name) self.assertFalse(response) def test_has_stack_review_in_progress(self): stack_name = "stack_name" expected_params = { "StackName": stack_name } # Response contains NO stack review_in_progress_response = { "Stacks": [make_stack_obj(stack_name, "REVIEW_IN_PROGRESS")] } self.stub_client.add_response('describe_stacks', review_in_progress_response, expected_params) with self.stub_client: response = self.deployer.has_stack(stack_name) self.assertFalse(response) def test_has_stack_exception(self): self.stub_client.add_client_error('describe_stacks', "ValidationError", "Service is bad") with self.stub_client: with self.assertRaises(botocore.exceptions.ClientError): self.deployer.has_stack("stack_name") def test_create_changeset_success(self): stack_name = "stack_name" template = "template" parameters = [ {"ParameterKey": "Key1", "ParameterValue": "Value"}, {"ParameterKey": "Key2", "UsePreviousValue": True}, {"ParameterKey": "Key3", "UsePreviousValue": False}, ] # Parameters that Use Previous Value will be removed on stack creation # to either force CloudFormation to use the Default value, or ask user to specify a parameter filtered_parameters = [ {"ParameterKey": "Key1", "ParameterValue": "Value"}, {"ParameterKey": "Key3", "UsePreviousValue": False}, ] capabilities = ["capabilities"] role_arn = "arn:aws:iam::1234567890:role" notification_arns = ["arn:aws:sns:region:1234567890:notify"] s3_uploader = None tags = [{"Key":"key1", "Value": "val1"}] # Case 1: Stack DOES NOT exist self.deployer.has_stack = Mock() self.deployer.has_stack.return_value = False expected_params = { "ChangeSetName": botocore.stub.ANY, "StackName": stack_name, "TemplateBody": template, "ChangeSetType": "CREATE", "Parameters": filtered_parameters, "Capabilities": capabilities, "Description": botocore.stub.ANY, "RoleARN": role_arn, "NotificationARNs": notification_arns, "Tags": tags } response = { "Id": "changeset ID" } self.stub_client.add_response("create_change_set", response, expected_params) with self.stub_client: result = self.deployer.create_changeset( stack_name, template, parameters, capabilities, role_arn, notification_arns, s3_uploader, tags) self.assertEquals(response["Id"], result.changeset_id) self.assertEquals("CREATE", result.changeset_type) # Case 2: Stack exists. We are updating it self.deployer.has_stack.return_value = True self.stub_client.add_response("get_template_summary", {"Parameters": [{"ParameterKey": parameter["ParameterKey"]} for parameter in parameters]}, {"StackName": stack_name}) expected_params["ChangeSetType"] = "UPDATE" expected_params["Parameters"] = parameters self.stub_client.add_response("create_change_set", response, expected_params) # template has new parameter but should not be included in # expected_params as no previous value parameters = list(parameters) + \ [{"ParameterKey": "New", "UsePreviousValue": True}] with self.stub_client: result = self.deployer.create_changeset( stack_name, template, parameters, capabilities, role_arn, notification_arns, s3_uploader, tags) self.assertEquals(response["Id"], result.changeset_id) self.assertEquals("UPDATE", result.changeset_type) def test_create_changeset_success_s3_bucket(self): stack_name = "stack_name" template = "template" template_url = "https://s3.amazonaws.com/bucket/file" parameters = [ {"ParameterKey": "Key1", "ParameterValue": "Value"}, {"ParameterKey": "Key2", "UsePreviousValue": True}, {"ParameterKey": "Key3", "UsePreviousValue": False}, ] # Parameters that Use Previous Value will be removed on stack creation # to either force CloudFormation to use the Default value, or ask user to specify a parameter filtered_parameters = [ {"ParameterKey": "Key1", "ParameterValue": "Value"}, {"ParameterKey": "Key3", "UsePreviousValue": False}, ] capabilities = ["capabilities"] role_arn = "arn:aws:iam::1234567890:role" notification_arns = ["arn:aws:sns:region:1234567890:notify"] s3_uploader = Mock() def to_path_style_s3_url(some_string, Version=None): return "https://s3.amazonaws.com/bucket/file" s3_uploader.to_path_style_s3_url = to_path_style_s3_url def upload_with_dedup(filename,extension): return "s3://bucket/file" s3_uploader.upload_with_dedup = upload_with_dedup # Case 1: Stack DOES NOT exist self.deployer.has_stack = Mock() self.deployer.has_stack.return_value = False expected_params = { "ChangeSetName": botocore.stub.ANY, "StackName": stack_name, "TemplateURL": template_url, "ChangeSetType": "CREATE", "Parameters": filtered_parameters, "Capabilities": capabilities, "Description": botocore.stub.ANY, "RoleARN": role_arn, "Tags": [], "NotificationARNs": notification_arns } response = { "Id": "changeset ID" } self.stub_client.add_response("create_change_set", response, expected_params) with self.stub_client: result = self.deployer.create_changeset( stack_name, template, parameters, capabilities, role_arn, notification_arns, s3_uploader, []) self.assertEquals(response["Id"], result.changeset_id) self.assertEquals("CREATE", result.changeset_type) # Case 2: Stack exists. We are updating it self.deployer.has_stack.return_value = True self.stub_client.add_response("get_template_summary", {"Parameters": [{"ParameterKey": parameter["ParameterKey"]} for parameter in parameters]}, {"StackName": stack_name}) expected_params["ChangeSetType"] = "UPDATE" expected_params["Parameters"] = parameters # template has new parameter but should not be included in # expected_params as no previous value parameters = list(parameters) + \ [{"ParameterKey": "New", "UsePreviousValue": True}] self.stub_client.add_response("create_change_set", response, expected_params) with self.stub_client: result = self.deployer.create_changeset( stack_name, template, parameters, capabilities, role_arn, notification_arns, s3_uploader, []) self.assertEquals(response["Id"], result.changeset_id) self.assertEquals("UPDATE", result.changeset_type) def test_create_changeset_exception(self): stack_name = "stack_name" template = "template" parameters = [{"ParameterKey": "Key1", "ParameterValue": "Value", "UsePreviousValue": True}] capabilities = ["capabilities"] role_arn = "arn:aws:iam::1234567890:role" notification_arns = ["arn:aws:sns:region:1234567890:notify"] s3_uploader = None tags = [{"Key":"key1", "Value": "val1"}] self.deployer.has_stack = Mock() self.deployer.has_stack.return_value = False self.stub_client.add_client_error( 'create_change_set', "Somethign is wrong", "Service is bad") with self.stub_client: with self.assertRaises(botocore.exceptions.ClientError): self.deployer.create_changeset(stack_name, template, parameters, capabilities, role_arn, notification_arns, None, tags) def test_execute_changeset(self): stack_name = "stack_name" changeset_id = "changeset_id" expected_params = { "ChangeSetName": changeset_id, "StackName": stack_name } self.stub_client.add_response("execute_change_set", {}, expected_params) with self.stub_client: self.deployer.execute_changeset(changeset_id, stack_name) def test_execute_changeset_exception(self): stack_name = "stack_name" changeset_id = "changeset_id" self.stub_client.add_client_error( 'execute_change_set', "Somethign is wrong", "Service is bad") with self.stub_client: with self.assertRaises(botocore.exceptions.ClientError): self.deployer.execute_changeset(changeset_id, stack_name) def test_create_and_wait_for_changeset_successful(self): stack_name = "stack_name" template = "template" parameters = [{"ParameterKey": "Key1", "ParameterValue": "Value", "UsePreviousValue": True}] capabilities = ["capabilities"] changeset_id = "changeset id" changeset_type = "changeset type" role_arn = "arn:aws:iam::1234567890:role" notification_arns = ["arn:aws:sns:region:1234567890:notify"] s3_uploader = None tags = [{"Key":"key1", "Value": "val1"}] self.deployer.create_changeset = Mock() self.deployer.create_changeset.return_value = ChangeSetResult(changeset_id, changeset_type) self.deployer.wait_for_changeset = Mock() result = self.deployer.create_and_wait_for_changeset( stack_name, template, parameters, capabilities, role_arn, notification_arns, s3_uploader, tags) self.assertEquals(result.changeset_id, changeset_id) self.assertEquals(result.changeset_type, changeset_type) def test_create_and_wait_for_changeset_error_waiting_for_changeset(self): stack_name = "stack_name" template = "template" parameters = [{"ParameterKey": "Key1", "ParameterValue": "Value", "UsePreviousValue": True}] capabilities = ["capabilities"] changeset_id = "changeset id" changeset_type = "changeset type" role_arn = "arn:aws:iam::1234567890:role" notification_arns = ["arn:aws:sns:region:1234567890:notify"] s3_uploader = None tags = [{"Key":"key1", "Value": "val1"}] self.deployer.create_changeset = Mock() self.deployer.create_changeset.return_value = ChangeSetResult(changeset_id, changeset_type) self.deployer.wait_for_changeset = Mock() self.deployer.wait_for_changeset.side_effect = RuntimeError with self.assertRaises(RuntimeError): result = self.deployer.create_and_wait_for_changeset( stack_name, template, parameters, capabilities, role_arn, notification_arns, s3_uploader, tags) def test_wait_for_changeset_no_changes(self): stack_name = "stack_name" changeset_id = "changeset-id" mock_client = Mock() mock_deployer = Deployer(mock_client) mock_waiter = Mock() mock_client.get_waiter.return_value = mock_waiter response = { "Status": "FAILED", "StatusReason": "The submitted information didn't contain changes." } waiter_error = botocore.exceptions.WaiterError(name="name", reason="reason", last_response=response) mock_waiter.wait.side_effect = waiter_error with self.assertRaises(exceptions.ChangeEmptyError): mock_deployer.wait_for_changeset(changeset_id, stack_name) waiter_config = {'Delay': 5} mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id, StackName=stack_name, WaiterConfig=waiter_config) mock_client.get_waiter.assert_called_once_with( "change_set_create_complete") def test_wait_for_changeset_no_changes_with_another_error_msg(self): stack_name = "stack_name" changeset_id = "changeset-id" mock_client = Mock() mock_deployer = Deployer(mock_client) mock_waiter = Mock() mock_client.get_waiter.return_value = mock_waiter response = { "Status": "FAILED", "StatusReason": "No updates are to be performed" } waiter_error = botocore.exceptions.WaiterError(name="name", reason="reason", last_response=response) mock_waiter.wait.side_effect = waiter_error with self.assertRaises(exceptions.ChangeEmptyError): mock_deployer.wait_for_changeset(changeset_id, stack_name) waiter_config = {'Delay': 5} mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id, StackName=stack_name, WaiterConfig=waiter_config) mock_client.get_waiter.assert_called_once_with( "change_set_create_complete") def test_wait_for_changeset_failed_to_create_changeset(self): stack_name = "stack_name" changeset_id = "changeset-id" mock_client = Mock() mock_deployer = Deployer(mock_client) mock_waiter = Mock() mock_client.get_waiter.return_value = mock_waiter response = { "Status": "FAILED", "StatusReason": "some reason" } waiter_error = botocore.exceptions.WaiterError(name="name", reason="reason", last_response=response) mock_waiter.wait.side_effect = waiter_error with self.assertRaises(RuntimeError): mock_deployer.wait_for_changeset(changeset_id, stack_name) waiter_config = {'Delay': 5} mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id, StackName=stack_name, WaiterConfig=waiter_config) mock_client.get_waiter.assert_called_once_with( "change_set_create_complete") def test_wait_for_execute_no_changes(self): stack_name = "stack_name" changeset_type = "CREATE" mock_client = Mock() mock_deployer = Deployer(mock_client) mock_waiter = Mock() mock_client.get_waiter.return_value = mock_waiter waiter_error = botocore.exceptions.WaiterError(name="name", reason="reason", last_response={}) mock_waiter.wait.side_effect = waiter_error with self.assertRaises(exceptions.DeployFailedError): mock_deployer.wait_for_execute(stack_name, changeset_type) waiter_config = { 'Delay': 5, 'MaxAttempts': 720, } mock_waiter.wait.assert_called_once_with(StackName=stack_name, WaiterConfig=waiter_config) mock_client.get_waiter.assert_called_once_with( "stack_create_complete")
class TestDeployer(unittest.TestCase): def setUp(self): client = botocore.session.get_session().create_client( 'cloudformation', region_name="us-east-1") self.stub_client = Stubber(client) self.changeset_prefix = "some-changeset-prefix" self.deployer = Deployer(client, self.changeset_prefix) def test_has_stack_success(self): stack_name = "stack_name" expected_params = {"StackName": stack_name} response = {"Stacks": [make_stack_obj(stack_name)]} self.stub_client.add_response('describe_stacks', response, expected_params) with self.stub_client: response = self.deployer.has_stack(stack_name) self.assertTrue(response) def test_has_stack_no_stack(self): stack_name = "stack_name" expected_params = {"StackName": stack_name} # Response contains NO stack no_stack_response = {"Stacks": []} self.stub_client.add_response('describe_stacks', no_stack_response, expected_params) with self.stub_client: response = self.deployer.has_stack(stack_name) self.assertFalse(response) # Response is a ClientError with a message that the stack does not exist self.stub_client.add_client_error( 'describe_stacks', "ClientError", "Stack with id {0} does not exist".format(stack_name)) with self.stub_client: response = self.deployer.has_stack(stack_name) self.assertFalse(response) def test_has_stack_review_in_progress(self): stack_name = "stack_name" expected_params = {"StackName": stack_name} # Response contains NO stack review_in_progress_response = { "Stacks": [make_stack_obj(stack_name, "REVIEW_IN_PROGRESS")] } self.stub_client.add_response('describe_stacks', review_in_progress_response, expected_params) with self.stub_client: response = self.deployer.has_stack(stack_name) self.assertFalse(response) def test_has_stack_exception(self): self.stub_client.add_client_error('describe_stacks', "ValidationError", "Service is bad") with self.stub_client: with self.assertRaises(botocore.exceptions.ClientError): self.deployer.has_stack("stack_name") def test_create_changeset_success(self): stack_name = "stack_name" template = "template" parameters = [ { "ParameterKey": "Key1", "ParameterValue": "Value" }, { "ParameterKey": "Key2", "UsePreviousValue": True }, { "ParameterKey": "Key3", "UsePreviousValue": False }, ] # Parameters that Use Previous Value will be removed on stack creation # to either force CloudFormation to use the Default value, or ask user to specify a parameter filtered_parameters = [ { "ParameterKey": "Key1", "ParameterValue": "Value" }, { "ParameterKey": "Key3", "UsePreviousValue": False }, ] capabilities = ["capabilities"] role_arn = "arn:aws:iam::1234567890:role" notification_arns = ["arn:aws:sns:region:1234567890:notify"] s3_uploader = None tags = [{"Key": "key1", "Value": "val1"}] # Case 1: Stack DOES NOT exist self.deployer.has_stack = mock.Mock() self.deployer.has_stack.return_value = False expected_params = { "ChangeSetName": botocore.stub.ANY, "StackName": stack_name, "TemplateBody": template, "ChangeSetType": "CREATE", "Parameters": filtered_parameters, "Capabilities": capabilities, "Description": botocore.stub.ANY, "RoleARN": role_arn, "NotificationARNs": notification_arns, "Tags": tags } response = {"Id": "changeset ID"} self.stub_client.add_response("create_change_set", response, expected_params) with self.stub_client: result = self.deployer.create_changeset(stack_name, template, parameters, capabilities, role_arn, notification_arns, s3_uploader, tags) self.assertEqual(response["Id"], result.changeset_id) self.assertEqual("CREATE", result.changeset_type) # Case 2: Stack exists. We are updating it self.deployer.has_stack.return_value = True self.stub_client.add_response( "get_template_summary", { "Parameters": [{ "ParameterKey": parameter["ParameterKey"] } for parameter in parameters] }, {"StackName": stack_name}) expected_params["ChangeSetType"] = "UPDATE" expected_params["Parameters"] = parameters self.stub_client.add_response("create_change_set", response, expected_params) # template has new parameter but should not be included in # expected_params as no previous value parameters = list(parameters) + \ [{"ParameterKey": "New", "UsePreviousValue": True}] with self.stub_client: result = self.deployer.create_changeset(stack_name, template, parameters, capabilities, role_arn, notification_arns, s3_uploader, tags) self.assertEqual(response["Id"], result.changeset_id) self.assertEqual("UPDATE", result.changeset_type) def test_create_changeset_success_s3_bucket(self): stack_name = "stack_name" template = "template" template_url = "https://s3.amazonaws.com/bucket/file" parameters = [ { "ParameterKey": "Key1", "ParameterValue": "Value" }, { "ParameterKey": "Key2", "UsePreviousValue": True }, { "ParameterKey": "Key3", "UsePreviousValue": False }, ] # Parameters that Use Previous Value will be removed on stack creation # to either force CloudFormation to use the Default value, or ask user to specify a parameter filtered_parameters = [ { "ParameterKey": "Key1", "ParameterValue": "Value" }, { "ParameterKey": "Key3", "UsePreviousValue": False }, ] capabilities = ["capabilities"] role_arn = "arn:aws:iam::1234567890:role" notification_arns = ["arn:aws:sns:region:1234567890:notify"] s3_uploader = mock.Mock() def to_path_style_s3_url(some_string, Version=None): return "https://s3.amazonaws.com/bucket/file" s3_uploader.to_path_style_s3_url = to_path_style_s3_url def upload_with_dedup(filename, extension): return "s3://bucket/file" s3_uploader.upload_with_dedup = upload_with_dedup # Case 1: Stack DOES NOT exist self.deployer.has_stack = mock.Mock() self.deployer.has_stack.return_value = False expected_params = { "ChangeSetName": botocore.stub.ANY, "StackName": stack_name, "TemplateURL": template_url, "ChangeSetType": "CREATE", "Parameters": filtered_parameters, "Capabilities": capabilities, "Description": botocore.stub.ANY, "RoleARN": role_arn, "Tags": [], "NotificationARNs": notification_arns } response = {"Id": "changeset ID"} self.stub_client.add_response("create_change_set", response, expected_params) with self.stub_client: result = self.deployer.create_changeset(stack_name, template, parameters, capabilities, role_arn, notification_arns, s3_uploader, []) self.assertEqual(response["Id"], result.changeset_id) self.assertEqual("CREATE", result.changeset_type) # Case 2: Stack exists. We are updating it self.deployer.has_stack.return_value = True self.stub_client.add_response( "get_template_summary", { "Parameters": [{ "ParameterKey": parameter["ParameterKey"] } for parameter in parameters] }, {"StackName": stack_name}) expected_params["ChangeSetType"] = "UPDATE" expected_params["Parameters"] = parameters # template has new parameter but should not be included in # expected_params as no previous value parameters = list(parameters) + \ [{"ParameterKey": "New", "UsePreviousValue": True}] self.stub_client.add_response("create_change_set", response, expected_params) with self.stub_client: result = self.deployer.create_changeset(stack_name, template, parameters, capabilities, role_arn, notification_arns, s3_uploader, []) self.assertEqual(response["Id"], result.changeset_id) self.assertEqual("UPDATE", result.changeset_type) def test_create_changeset_exception(self): stack_name = "stack_name" template = "template" parameters = [{ "ParameterKey": "Key1", "ParameterValue": "Value", "UsePreviousValue": True }] capabilities = ["capabilities"] role_arn = "arn:aws:iam::1234567890:role" notification_arns = ["arn:aws:sns:region:1234567890:notify"] s3_uploader = None tags = [{"Key": "key1", "Value": "val1"}] self.deployer.has_stack = mock.Mock() self.deployer.has_stack.return_value = False self.stub_client.add_client_error('create_change_set', "Somethign is wrong", "Service is bad") with self.stub_client: with self.assertRaises(botocore.exceptions.ClientError): self.deployer.create_changeset(stack_name, template, parameters, capabilities, role_arn, notification_arns, None, tags) def test_execute_changeset(self): stack_name = "stack_name" changeset_id = "changeset_id" expected_params = { "ChangeSetName": changeset_id, "StackName": stack_name, "DisableRollback": False } self.stub_client.add_response("execute_change_set", {}, expected_params) with self.stub_client: self.deployer.execute_changeset(changeset_id, stack_name) def test_execute_changeset_disable_rollback(self): stack_name = "stack_name" changeset_id = "changeset_id" disable_rollback = True expected_params = { "ChangeSetName": changeset_id, "StackName": stack_name, "DisableRollback": disable_rollback } self.stub_client.add_response("execute_change_set", {}, expected_params) with self.stub_client: self.deployer.execute_changeset(changeset_id, stack_name, disable_rollback) def test_execute_changeset_exception(self): stack_name = "stack_name" changeset_id = "changeset_id" self.stub_client.add_client_error('execute_change_set', "Somethign is wrong", "Service is bad") with self.stub_client: with self.assertRaises(botocore.exceptions.ClientError): self.deployer.execute_changeset(changeset_id, stack_name) def test_create_and_wait_for_changeset_successful(self): stack_name = "stack_name" template = "template" parameters = [{ "ParameterKey": "Key1", "ParameterValue": "Value", "UsePreviousValue": True }] capabilities = ["capabilities"] changeset_id = "changeset id" changeset_type = "changeset type" role_arn = "arn:aws:iam::1234567890:role" notification_arns = ["arn:aws:sns:region:1234567890:notify"] s3_uploader = None tags = [{"Key": "key1", "Value": "val1"}] self.deployer.create_changeset = mock.Mock() self.deployer.create_changeset.return_value = ChangeSetResult( changeset_id, changeset_type) self.deployer.wait_for_changeset = mock.Mock() result = self.deployer.create_and_wait_for_changeset( stack_name, template, parameters, capabilities, role_arn, notification_arns, s3_uploader, tags) self.assertEqual(result.changeset_id, changeset_id) self.assertEqual(result.changeset_type, changeset_type) def test_create_and_wait_for_changeset_error_waiting_for_changeset(self): stack_name = "stack_name" template = "template" parameters = [{ "ParameterKey": "Key1", "ParameterValue": "Value", "UsePreviousValue": True }] capabilities = ["capabilities"] changeset_id = "changeset id" changeset_type = "changeset type" role_arn = "arn:aws:iam::1234567890:role" notification_arns = ["arn:aws:sns:region:1234567890:notify"] s3_uploader = None tags = [{"Key": "key1", "Value": "val1"}] self.deployer.create_changeset = mock.Mock() self.deployer.create_changeset.return_value = ChangeSetResult( changeset_id, changeset_type) self.deployer.wait_for_changeset = mock.Mock() self.deployer.wait_for_changeset.side_effect = RuntimeError with self.assertRaises(RuntimeError): result = self.deployer.create_and_wait_for_changeset( stack_name, template, parameters, capabilities, role_arn, notification_arns, s3_uploader, tags) def test_wait_for_changeset_no_changes(self): stack_name = "stack_name" changeset_id = "changeset-id" mock_client = mock.Mock() mock_deployer = Deployer(mock_client) mock_waiter = mock.Mock() mock_client.get_waiter.return_value = mock_waiter response = { "Status": "FAILED", "StatusReason": "The submitted information didn't contain changes." } waiter_error = botocore.exceptions.WaiterError(name="name", reason="reason", last_response=response) mock_waiter.wait.side_effect = waiter_error with self.assertRaises(exceptions.ChangeEmptyError): mock_deployer.wait_for_changeset(changeset_id, stack_name) waiter_config = {'Delay': 5} mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id, StackName=stack_name, WaiterConfig=waiter_config) mock_client.get_waiter.assert_called_once_with( "change_set_create_complete") def test_wait_for_changeset_no_changes_with_another_error_msg(self): stack_name = "stack_name" changeset_id = "changeset-id" mock_client = mock.Mock() mock_deployer = Deployer(mock_client) mock_waiter = mock.Mock() mock_client.get_waiter.return_value = mock_waiter response = { "Status": "FAILED", "StatusReason": "No updates are to be performed" } waiter_error = botocore.exceptions.WaiterError(name="name", reason="reason", last_response=response) mock_waiter.wait.side_effect = waiter_error with self.assertRaises(exceptions.ChangeEmptyError): mock_deployer.wait_for_changeset(changeset_id, stack_name) waiter_config = {'Delay': 5} mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id, StackName=stack_name, WaiterConfig=waiter_config) mock_client.get_waiter.assert_called_once_with( "change_set_create_complete") def test_wait_for_changeset_failed_to_create_changeset(self): stack_name = "stack_name" changeset_id = "changeset-id" mock_client = mock.Mock() mock_deployer = Deployer(mock_client) mock_waiter = mock.Mock() mock_client.get_waiter.return_value = mock_waiter response = {"Status": "FAILED", "StatusReason": "some reason"} waiter_error = botocore.exceptions.WaiterError(name="name", reason="reason", last_response=response) mock_waiter.wait.side_effect = waiter_error with self.assertRaises(RuntimeError): mock_deployer.wait_for_changeset(changeset_id, stack_name) waiter_config = {'Delay': 5} mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id, StackName=stack_name, WaiterConfig=waiter_config) mock_client.get_waiter.assert_called_once_with( "change_set_create_complete") def test_wait_for_execute_no_changes(self): stack_name = "stack_name" changeset_type = "CREATE" mock_client = mock.Mock() mock_deployer = Deployer(mock_client) mock_waiter = mock.Mock() mock_client.get_waiter.return_value = mock_waiter waiter_error = botocore.exceptions.WaiterError(name="name", reason="reason", last_response={}) mock_waiter.wait.side_effect = waiter_error with self.assertRaises(exceptions.DeployFailedError): mock_deployer.wait_for_execute(stack_name, changeset_type) waiter_config = { 'Delay': 30, 'MaxAttempts': 120, } mock_waiter.wait.assert_called_once_with(StackName=stack_name, WaiterConfig=waiter_config) mock_client.get_waiter.assert_called_once_with("stack_create_complete")