def test_handle_stack_event_returns_expected_event(self, _): event = { 'PhysicalResourceId': 'arn:aws:cloudformation:eu-west-1:1234567890:stack/my-stack/my-stack-id', 'StackName': 'my-stack', 'LogicalResourceId': 'my-stack', 'StackId': 'arn:aws:cloudformation:eu-west-1:1234567890:stack/my-stack/my-stack-id', 'ResourceType': 'AWS::CloudFormation::Stack', 'Timestamp': datetime.datetime(2016, 4, 1, 8, 3, 27, 548000, tzinfo=tzutc()), 'EventId': 'my-event-id', 'ResourceStatus': 'CREATE_COMPLETE' } valid_from_timestamp = datetime.datetime(2016, 4, 1, 8, 3, 25, 548000, tzinfo=tzutc()) cfn = CloudFormation() result = cfn.handle_stack_event(event, valid_from_timestamp, "CREATE_COMPLETE", "my-stack") self.assertDictEqual(event, result)
def __init__(self, config, dry_run=False): self.logger = get_logger(root=True) self.config = config self.cfn = CloudFormation(region=self.config.region, dry_run=dry_run) self.parameter_resolver = ParameterResolver(self.cfn, region=self.config.region) self.cli_parameters = config.cli_params
def test_update_stack_calls_cloudformation_api_properly_with_stack_policy( self, _, cloudformation_mock): stack = Mock(spec=CloudFormationStack) stack.name = "stack-name" stack.get_parameters_list.return_value = [('a', 'b')] stack.get_tags_list.return_value = [('any-tag', 'any-tag-value')] stack.parameters = {} stack.template = Mock(spec=CloudFormationTemplate) stack.template.name = "template-name" stack.template.get_template_json.return_value = {'key': 'value'} stack.service_role = None stack.stack_policy = "{foo:baa}" stack.timeout = 42 cfn = CloudFormation() cfn.update_stack(stack) cloudformation_mock.return_value.set_stack_policy.assert_called_once_with( StackName='stack-name', StackPolicyBody='"{foo:baa}"') cloudformation_mock.return_value.update_stack.assert_called_once_with( Capabilities=['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], Parameters=[('a', 'b')], StackName='stack-name', Tags=[('any-tag', 'any-tag-value')], TemplateBody={'key': 'value'})
def test_handle_stack_event_raises_exception_on_rollback_complete(self, _): event = { 'PhysicalResourceId': 'arn:aws:sns:eu-west-1:1234567890:my-topic', 'StackName': 'my-stack', 'LogicalResourceId': 'my-stack', 'StackId': 'arn:aws:cloudformation:eu-west-1:1234567890:stack/my-stack/my-stack-id', 'ResourceType': 'AWS::CloudFormation::Stack', 'Timestamp': datetime.datetime(2016, 4, 1, 8, 3, 27, 548000, tzinfo=tzutc()), 'EventId': 'my-event-id', 'ResourceStatus': 'ROLLBACK_COMPLETE' } valid_from_timestamp = datetime.datetime(2016, 4, 1, 8, 3, 25, 548000, tzinfo=tzutc()) cfn = CloudFormation() with self.assertRaises(CfnStackActionFailedException): cfn.handle_stack_event(event, valid_from_timestamp, "CREATE_COMPLETE", "my-stack")
def test_create_stack_calls_cloudformation_api_properly_with_stack_policy(self, _, cloudformation_mock): stack = Mock(spec=CloudFormationStack) stack.name = "stack-name" stack.get_parameters_list.return_value = [('a', 'b')] stack.get_tags_list.return_value = [('any-tag', 'any-tag-value')] stack.parameters = {} stack.template = Mock(spec=CloudFormationTemplate) stack.template.name = "template-name" stack.template.get_template_json.return_value = {'key': 'value'} stack.service_role = None stack.stack_policy = "{foo:baa}" stack.timeout = 42 cfn = CloudFormation() cfn.create_stack(stack) cloudformation_mock.return_value.create_stack.assert_called_once_with( Capabilities=['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], OnFailure='ROLLBACK', Parameters=[('a', 'b')], StackName='stack-name', Tags=[('any-tag', 'any-tag-value')], TemplateBody={'key': 'value'}, StackPolicyBody='"{foo:baa}"' )
def test_create_stack_calls_cloudformation_api_properly_with_service_role(self, _, cloudformation_mock): stack = Mock(spec=CloudFormationStack) stack.name = "stack-name" stack.get_parameters_list.return_value = [('a', 'b')] stack.get_tags_list.return_value = [('any-tag', 'any-tag-value')] stack.parameters = {} stack.template = Mock(spec=CloudFormationTemplate) stack.template.name = "template-name" stack.template.get_template_json.return_value = {'key': 'value'} stack.service_role = "arn:aws:iam::1234567890:role/my-role" stack.stack_policy = None stack.failure_action = None stack.disable_rollback = False stack.timeout = 42 cfn = CloudFormation() cfn.create_stack(stack) cloudformation_mock.return_value.create_stack.assert_called_once_with( Capabilities=['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], Parameters=[('a', 'b')], StackName='stack-name', Tags=[('any-tag', 'any-tag-value')], TemplateBody={'key': 'value'}, RoleARN="arn:aws:iam::1234567890:role/my-role" )
def test_create_stack_calls_cloudformation_api_properly_with_disable_rollback_true(self, _, cloudformation_mock): stack = Mock(spec=CloudFormationStack) stack.name = "stack-name" stack.get_parameters_list.return_value = [('a', 'b')] stack.get_tags_list.return_value = [('any-tag', 'any-tag-value')] stack.parameters = {} stack.template = Mock(spec=CloudFormationTemplate) stack.template.name = "template-name" stack.template.get_template_json.return_value = {'key': 'value'} stack.service_role = None stack.stack_policy = "{foo:baa}" stack.failure_action = "DO_NOTHING" stack.disable_rollback = "True" stack.timeout = 42 cfn = CloudFormation() cfn.create_stack(stack) cloudformation_mock.return_value.create_stack.assert_called_once_with( Capabilities=['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], OnFailure='DO_NOTHING', DisableRollback=True, Parameters=[('a', 'b')], StackName='stack-name', Tags=[('any-tag', 'any-tag-value')], TemplateBody={'key': 'value'}, StackPolicyBody='"{foo:baa}"' )
def test_wait_for_stack_event_returns_on_start_event_with_valid_timestamp( self, cloudformation_mock): timestamp = datetime.datetime.utcnow() template_mock = Mock(spec=CloudFormationTemplate) template_mock.url = "foo.yml" template_mock.get_template_body_dict.return_value = {} event = StackEvent() event.resource_type = "AWS::CloudFormation::Stack" event.resource_status = "UPDATE_IN_PROGRESS" event.event_id = "123" event.timestamp = timestamp stack_events_mock = Mock() stack_events_mock.describe_stack_events.return_value = [event] cloudformation_mock.connect_to_region.return_value = stack_events_mock cfn = CloudFormation() event = cfn.wait_for_stack_events("foo", "UPDATE_IN_PROGRESS", timestamp - timedelta(seconds=10), timeout=10) self.assertEqual(timestamp, event.timestamp)
def test_handle_stack_event_returns_none_on_rollback_in_progress_state( self, _): event = { 'PhysicalResourceId': 'arn:aws:sns:eu-west-1:1234567890:my-topic', 'StackName': 'my-stack', 'LogicalResourceId': 'my-stack', 'StackId': 'arn:aws:cloudformation:eu-west-1:1234567890:stack/my-stack/my-stack-id', 'ResourceType': 'AWS::CloudFormation::Stack', 'Timestamp': datetime.datetime(2016, 4, 1, 8, 3, 27, 548000, tzinfo=tzutc()), 'EventId': 'my-event-id', 'ResourceStatus': 'ROLLBACK_IN_PROGRESS', 'ResourceStatusReason': 'Foo' } valid_from_timestamp = datetime.datetime(2016, 4, 1, 8, 3, 25, 548000, tzinfo=tzutc()) cfn = CloudFormation() result = cfn.handle_stack_event(event, valid_from_timestamp, "CREATE_COMPLETE", "my-stack") self.assertIsNone(result)
def test_get_stack_parameters_dict_returns_empty_dict_for_empty_parameters(self, _, get_stack_mock): cfn = CloudFormation() stack_mock = Mock() stack_mock.parameters = [] get_stack_mock.return_value = stack_mock result = cfn.get_stack_parameters_dict('foo') self.assertDictEqual({}, result)
def test_validate_stack_is_ready_for_action_raises_proper_exception_on_boto_error(self, cloudformation_mock): cloudformation_mock.return_value.describe_stacks.side_effect = BotoServerError('400', 'Bad Request') stack = CloudFormationStack('', [], 'my-stack', 'my-region') cfn = CloudFormation() with self.assertRaises(CfnSphereBotoError): cfn.validate_stack_is_ready_for_action(stack) cloudformation_mock.return_value.describe_stacks.assert_called_once_with('my-stack')
def test_validate_stack_is_ready_for_action_passes_if_stack_is_in_rollback_complete_state(self, get_stack_mock): stack_mock = Mock() stack_mock.stack_name = "my-stack" stack_mock.stack_status = "ROLLBACK_COMPLETE" get_stack_mock.return_value = stack_mock stack = CloudFormationStack('', [], 'my-stack', 'my-region') cfn = CloudFormation() cfn.validate_stack_is_ready_for_action(stack)
def test_get_stacks_correctly_calls_aws_api(self, cloudformation_mock): stacks = [Mock(spec=Stack), Mock(spec=Stack)] result = ResultSet() result.extend(stacks) result.next_token = None cloudformation_mock.connect_to_region.return_value.describe_stacks.return_value = result cfn = CloudFormation() self.assertListEqual(stacks, cfn.get_stacks())
def test_validate_stack_is_ready_for_action_raises_exception_on_unknown_stack_state(self, get_stack_mock): stack_mock = Mock() stack_mock.stack_name = "my-stack" stack_mock.stack_status = "FOO" get_stack_mock.return_value = stack_mock stack = CloudFormationStack('', [], 'my-stack', 'my-region') cfn = CloudFormation() with self.assertRaises(CfnStackActionFailedException): cfn.validate_stack_is_ready_for_action(stack)
def test_get_stack_parameters_dict_returns_proper_dict(self, _, get_stack_mock): cfn = CloudFormation() stack_mock = Mock() stack_mock.parameters = [{"ParameterKey": "myKey1", "ParameterValue": "myValue1"}, {"ParameterKey": "myKey2", "ParameterValue": "myValue2"}] get_stack_mock.return_value = stack_mock result = cfn.get_stack_parameters_dict('foo') self.assertDictEqual({'myKey1': 'myValue1', 'myKey2': 'myValue2'}, result)
def test_validate_stack_is_ready_for_action_raises_exception_on_create_in_progress(self, get_stack_mock): stack_mock = Mock() stack_mock.stack_name = "my-stack" stack_mock.stack_status = "CREATE_IN_PROGRESS" get_stack_mock.return_value = stack_mock stack = CloudFormationStack('', [], 'my-stack', 'my-region') cfn = CloudFormation() with self.assertRaises(CfnStackActionFailedException): cfn.validate_stack_is_ready_for_action(stack)
def test_validate_stack_is_ready_for_action_passes_if_stack_is_in_good_state(self, cloudformation_mock): describe_stack_mock = Mock() describe_stack_mock.stack_status = "UPDATE_COMPLETE" describe_stack_mock.stack_name = "my-stack" cloudformation_mock.return_value.describe_stacks.return_value = [describe_stack_mock] stack = CloudFormationStack('', [], 'my-stack', 'my-region') cfn = CloudFormation() cfn.validate_stack_is_ready_for_action(stack) cloudformation_mock.return_value.describe_stacks.assert_called_once_with('my-stack')
def test_validate_stack_is_ready_for_action_raises_proper_exception_on_boto_error(self, get_stack_mock): get_stack_mock.side_effect = CfnSphereBotoError(None) stack_mock = Mock() stack_mock.stack_name = "my-stack" stack_mock.stack_status = "UPDATE_COMPLETE" get_stack_mock.return_value = stack_mock stack = CloudFormationStack('', [], 'my-stack', 'my-region') cfn = CloudFormation() with self.assertRaises(CfnSphereBotoError): cfn.validate_stack_is_ready_for_action(stack)
def test_validate_stack_is_ready_for_action_raises_proper_exception_on_boto_error( self, cloudformation_mock): cloudformation_mock.return_value.describe_stacks.side_effect = BotoServerError( '400', 'Bad Request') stack = CloudFormationStack('', [], 'my-stack', 'my-region') cfn = CloudFormation() with self.assertRaises(CfnSphereBotoError): cfn.validate_stack_is_ready_for_action(stack) cloudformation_mock.return_value.describe_stacks.assert_called_once_with( 'my-stack')
def test_delete_stack_calls_wait_properly(self, wait_mock, _a, _b): stack = Mock(spec=CloudFormationStack) stack.name = "stack-name" stack.get_parameters_list.return_value = [] stack.parameters = {} stack.template = Mock(spec=CloudFormationTemplate) stack.template.name = "template-name" stack.timeout = 42 cfn = CloudFormation() cfn.delete_stack(stack) wait_mock.assert_called_once_with(stack.name, 'delete', stack.timeout)
def test_validate_stack_is_ready_for_action_raises_exception_on_bad_stack_state(self, cloudformation_mock): describe_stack_mock = Mock() describe_stack_mock.stack_status = "UPDATE_IN_PROGRESS" describe_stack_mock.stack_name = "my-stack" cloudformation_mock.return_value.describe_stacks.return_value = [describe_stack_mock] stack = CloudFormationStack('', [], 'my-stack', 'my-region') cfn = CloudFormation() with self.assertRaises(CfnStackActionFailedException): cfn.validate_stack_is_ready_for_action(stack) cloudformation_mock.return_value.describe_stacks.assert_called_once_with('my-stack')
def test_get_stacks_correctly_aggregates_paged_results(self, cloudformation_mock): stacks_1 = [Mock(spec=Stack), Mock(spec=Stack)] stacks_2 = [Mock(spec=Stack), Mock(spec=Stack)] result_1 = ResultSet() result_1.extend(stacks_1) result_1.next_token = "my-next-token" result_2 = ResultSet() result_2.extend(stacks_2) result_2.next_token = None cloudformation_mock.connect_to_region.return_value.describe_stacks.side_effect = [result_1, result_2] cfn = CloudFormation() self.assertListEqual(stacks_1 + stacks_2, cfn.get_stacks())
def test_stack_exists_returns_false_for_non_existing_stack( self, get_stack_mock): get_stack_mock.side_effect = ClientError( {"Error": { "Message": "Stack with id stack3 does not exist" }}, "Foo") self.assertFalse(CloudFormation().stack_exists("stack3"))
def test_handle_stack_event_returns_none_if_event_is_no_stack_event(self, _): event = { 'PhysicalResourceId': 'arn:aws:sns:eu-west-1:1234567890:my-topic', 'StackName': 'my-stack', 'LogicalResourceId': 'Topic', 'StackId': 'arn:aws:cloudformation:eu-west-1:1234567890:stack/my-stack/my-stack-id', 'ResourceType': 'AWS::SNS::Topic', 'Timestamp': datetime.datetime(2016, 4, 1, 8, 3, 27, 548000, tzinfo=tzutc()), 'EventId': 'my-event-id', 'ResourceStatus': 'CREATE_COMPLETE' } valid_from_timestamp = datetime.datetime(2016, 4, 1, 8, 3, 25, 548000, tzinfo=tzutc()) cfn = CloudFormation() result = cfn.handle_stack_event(event, valid_from_timestamp, "CREATE_COMPLETE", "my-stack") self.assertIsNone(result)
def test_handle_stack_event_raises_exception_on_rollback_complete(self, _): event = { 'PhysicalResourceId': 'arn:aws:sns:eu-west-1:1234567890:my-topic', 'StackName': 'my-stack', 'LogicalResourceId': 'VPC', 'StackId': 'arn:aws:cloudformation:eu-west-1:1234567890:stack/my-stack/my-stack-id', 'ResourceType': 'AWS::CloudFormation::Stack', 'Timestamp': datetime.datetime(2016, 4, 1, 8, 3, 27, 548000, tzinfo=tzutc()), 'EventId': 'my-event-id', 'ResourceStatus': 'ROLLBACK_COMPLETE' } valid_from_timestamp = datetime.datetime(2016, 4, 1, 8, 3, 25, 548000, tzinfo=tzutc()) cfn = CloudFormation() with self.assertRaises(CfnStackActionFailedException): cfn.handle_stack_event(event, valid_from_timestamp, "CREATE_COMPLETE")
def test_handle_stack_event_returns_none_if_event_has_not_expected_state(self, _): event = { 'PhysicalResourceId': 'arn:aws:cloudformation:eu-west-1:1234567890:stack/my-stack/my-stack-id', 'StackName': 'my-stack', 'LogicalResourceId': 'cfn-sphere-test-vpc', 'StackId': 'arn:aws:cloudformation:eu-west-1:1234567890:stack/my-stack/my-stack-id', 'ResourceType': 'AWS::CloudFormation::Stack', 'Timestamp': datetime.datetime(2016, 4, 1, 8, 3, 27, 548000, tzinfo=tzutc()), 'EventId': 'my-event-id', 'ResourceStatus': 'CREATE_IN_PROGRESS' } valid_from_timestamp = datetime.datetime(2016, 4, 1, 8, 3, 25, 548000, tzinfo=tzutc()) cfn = CloudFormation() result = cfn.handle_stack_event(event, valid_from_timestamp, "CREATE_COMPLETE") self.assertIsNone(result)
def test_validate_stack_is_ready_for_action_passes_if_stack_is_in_good_state( self, cloudformation_mock): describe_stack_mock = Mock() describe_stack_mock.stack_status = "UPDATE_COMPLETE" describe_stack_mock.stack_name = "my-stack" cloudformation_mock.return_value.describe_stacks.return_value = [ describe_stack_mock ] stack = CloudFormationStack('', [], 'my-stack', 'my-region') cfn = CloudFormation() cfn.validate_stack_is_ready_for_action(stack) cloudformation_mock.return_value.describe_stacks.assert_called_once_with( 'my-stack')
def test_handle_stack_event_returns_none_on_rollback_in_progress_state(self, _): event = { 'PhysicalResourceId': 'arn:aws:sns:eu-west-1:1234567890:my-topic', 'StackName': 'my-stack', 'LogicalResourceId': 'VPC', 'StackId': 'arn:aws:cloudformation:eu-west-1:1234567890:stack/my-stack/my-stack-id', 'ResourceType': 'AWS::CloudFormation::Stack', 'Timestamp': datetime.datetime(2016, 4, 1, 8, 3, 27, 548000, tzinfo=tzutc()), 'EventId': 'my-event-id', 'ResourceStatus': 'ROLLBACK_IN_PROGRESS', 'ResourceStatusReason': 'Foo' } valid_from_timestamp = datetime.datetime(2016, 4, 1, 8, 3, 25, 548000, tzinfo=tzutc()) cfn = CloudFormation() result = cfn.handle_stack_event(event, valid_from_timestamp, "CREATE_COMPLETE") self.assertIsNone(result)
def test_get_stack_parameters_dict_returns_proper_dict(self, _, get_stack_mock): cfn = CloudFormation() parameter_1 = Mock() parameter_1.key = "myKey1" parameter_1.value = "myValue1" parameter_2 = Mock() parameter_2.key = "myKey2" parameter_2.value = "myValue2" stack_mock = Mock() stack_mock.parameters = [parameter_1, parameter_2] get_stack_mock.return_value = stack_mock result = cfn.get_stack_parameters_dict('foo') self.assertDictEqual({'myKey1': 'myValue1', 'myKey2': 'myValue2'}, result)
def test_get_stacks_dict_always_returns_empty_list_parameters_and_outputs( self, get_stack_descriptions_mock): get_stack_descriptions_mock.return_value = [{"StackName": "Foo"}] self.assertEqual({'Foo': { 'outputs': [], 'parameters': [] }}, CloudFormation().get_stacks_dict())
def test_validate_stack_is_ready_for_action_raises_exception_on_bad_stack_state( self, cloudformation_mock): describe_stack_mock = Mock() describe_stack_mock.stack_status = "UPDATE_IN_PROGRESS" describe_stack_mock.stack_name = "my-stack" cloudformation_mock.return_value.describe_stacks.return_value = [ describe_stack_mock ] stack = CloudFormationStack('', [], 'my-stack', 'my-region') cfn = CloudFormation() with self.assertRaises(CfnStackActionFailedException): cfn.validate_stack_is_ready_for_action(stack) cloudformation_mock.return_value.describe_stacks.assert_called_once_with( 'my-stack')
def test_get_stacks_correctly_aggregates_paged_results( self, cloudformation_mock): stacks_1 = [Mock(spec=Stack), Mock(spec=Stack)] stacks_2 = [Mock(spec=Stack), Mock(spec=Stack)] result_1 = ResultSet() result_1.extend(stacks_1) result_1.next_token = "my-next-token" result_2 = ResultSet() result_2.extend(stacks_2) result_2.next_token = None cloudformation_mock.connect_to_region.return_value.describe_stacks.side_effect = [ result_1, result_2 ] cfn = CloudFormation() self.assertListEqual(stacks_1 + stacks_2, cfn.get_stacks())
def test_delete_stack_calls_cloudformation_api_properly( self, _, cloudformation_mock): stack = Mock(spec=CloudFormationStack) stack.name = "stack-name" stack.get_parameters_list.return_value = [('a', 'b')] stack.get_tags_list.return_value = [('any-tag', 'any-tag-value')] stack.parameters = {} stack.template = Mock(spec=CloudFormationTemplate) stack.template.name = "template-name" stack.template.get_template_json.return_value = {'key': 'value'} stack.service_role = None stack.stack_policy = "{foo:baa}" stack.timeout = 42 cfn = CloudFormation() cfn.delete_stack(stack) cloudformation_mock.return_value.delete_stack.assert_called_once_with( StackName=stack.name)
def test_is_boto_stack_does_not_exist_exception_returns_true_for_message2( self): exception = Mock(spec=ClientError) exception.response = { "Error": { "Message": "Stack with id foo does not exist" } } self.assertTrue( CloudFormation.is_boto_stack_does_not_exist_exception(exception))
def test_update_stack_calls_cloudformation_api_properly(self, _, cloudformation_mock): stack = Mock(spec=CloudFormationStack) stack.name = "stack-name" stack.get_parameters_list.return_value = [('a', 'b')] stack.tags = [('any-tag', 'any-tag-value')] stack.parameters = {} stack.template = Mock(spec=CloudFormationTemplate) stack.template.name = "template-name" stack.template.get_template_json.return_value = {'key': 'value'} stack.timeout = 42 cfn = CloudFormation() cfn.update_stack(stack) cloudformation_mock.return_value.update_stack.assert_called_once_with('stack-name', capabilities=['CAPABILITY_IAM'], parameters=[('a', 'b')], tags=[('any-tag', 'any-tag-value')], template_body={'key': 'value'})
def test_is_boto_no_update_required_exception_returns_true_for_message( self): exception = Mock(spec=ClientError) exception.response = { "Error": { "Message": "No updates are to be performed." } } self.assertTrue( CloudFormation.is_boto_no_update_required_exception(exception))
def test_wait_for_stack_event_returns_on_update_complete(self, cloudformation_mock): timestamp = datetime.datetime.utcnow() template_mock = Mock(spec=CloudFormationTemplate) template_mock.url = "foo.yml" template_mock.get_template_body_dict.return_value = {} event = StackEvent() event.resource_type = "AWS::CloudFormation::Stack" event.resource_status = "UPDATE_COMPLETE" event.event_id = "123" event.timestamp = timestamp stack_events_mock = Mock() stack_events_mock.describe_stack_events.return_value = [event] cloudformation_mock.connect_to_region.return_value = stack_events_mock cfn = CloudFormation() cfn.wait_for_stack_events("foo", "UPDATE_COMPLETE", timestamp - timedelta(seconds=10), timeout=10)
def test_update_stack_calls_cloudformation_api_properly( self, _, cloudformation_mock): stack = Mock(spec=CloudFormationStack) stack.name = "stack-name" stack.get_parameters_list.return_value = [('a', 'b')] stack.tags = [('any-tag', 'any-tag-value')] stack.parameters = {} stack.template = Mock(spec=CloudFormationTemplate) stack.template.name = "template-name" stack.template.get_template_json.return_value = {'key': 'value'} stack.timeout = 42 cfn = CloudFormation() cfn.update_stack(stack) cloudformation_mock.return_value.update_stack.assert_called_once_with( 'stack-name', capabilities=['CAPABILITY_IAM'], parameters=[('a', 'b')], tags=[('any-tag', 'any-tag-value')], template_body={'key': 'value'})
def test_get_stack_parameters_dict_returns_proper_dict( self, _, get_stack_mock): cfn = CloudFormation() parameter_1 = Mock() parameter_1.key = "myKey1" parameter_1.value = "myValue1" parameter_2 = Mock() parameter_2.key = "myKey2" parameter_2.value = "myValue2" stack_mock = Mock() stack_mock.parameters = [parameter_1, parameter_2] get_stack_mock.return_value = stack_mock result = cfn.get_stack_parameters_dict('foo') self.assertDictEqual({ 'myKey1': 'myValue1', 'myKey2': 'myValue2' }, result)
def test_get_stacks_dict_returns_stack_dict(self, get_stack_descriptions_mock): get_stack_descriptions_mock.return_value = [{ "StackName": "Foo", "Parameters": [], "Outputs": [] }] self.assertEqual({'Foo': { 'outputs': [], 'parameters': [] }}, CloudFormation().get_stacks_dict())
def test_wait_for_stack_event_raises_exception_on_rollback(self, cloudformation_mock): timestamp = datetime.datetime.utcnow() template_mock = Mock(spec=CloudFormationTemplate) template_mock.url = "foo.yml" template_mock.get_template_body_dict.return_value = {} event = StackEvent() event.resource_type = "AWS::CloudFormation::Stack" event.resource_status = "ROLLBACK_COMPLETE" event.event_id = "123" event.timestamp = timestamp stack_events_mock = Mock() stack_events_mock.describe_stack_events.return_value = [event] cloudformation_mock.connect_to_region.return_value = stack_events_mock cfn = CloudFormation() with self.assertRaises(Exception): cfn.wait_for_stack_events("foo", ["UPDATE_COMPLETE"], timestamp - timedelta(seconds=10), timeout=10)
def test_set_stack_policy_calls_cloudformation_api_properly( self, cloudformation_mock): stack = Mock(spec=CloudFormationStack) stack.name = "stack-name" stack.get_parameters_list.return_value = [('a', 'b')] stack.get_tags_list.return_value = [('any-tag', 'any-tag-value')] stack.parameters = {} stack.template = Mock(spec=CloudFormationTemplate) stack.template.name = "template-name" stack.template.get_template_json.return_value = {'key': 'value'} stack.service_role = None stack.stack_policy = """{"Statement":[{"Effect":"Allow"}]}""" stack.failure_action = None stack.disable_rollback = False stack.timeout = 42 cfn = CloudFormation() cfn._set_stack_policy(stack) cloudformation_mock.return_value.set_stack_policy.assert_called_once_with( StackName='stack-name', StackPolicyBody='"{\\"Statement\\":[{\\"Effect\\":\\"Allow\\"}]}"')
def test_create_stack_calls_cloudformation_api_properly(self, _, cloudformation_mock): stack = Mock(spec=CloudFormationStack) stack.name = "stack-name" stack.get_parameters_list.return_value = [('a', 'b')] stack.get_tags_list.return_value = [('any-tag', 'any-tag-value')] stack.parameters = {} stack.template = Mock(spec=CloudFormationTemplate) stack.template.name = "template-name" stack.template.get_template_json.return_value = {'key': 'value'} stack.timeout = 42 cfn = CloudFormation() cfn.create_stack(stack) cloudformation_mock.return_value.create_stack.assert_called_once_with( Capabilities=['CAPABILITY_IAM'], OnFailure='DELETE', Parameters=[('a', 'b')], StackName='stack-name', Tags=[('any-tag', 'any-tag-value')], TemplateBody={'key': 'value'}, TimeoutInMinutes=42)
def test_wait_for_stack_event_returns_on_start_event_with_valid_timestamp(self, cloudformation_mock): timestamp = datetime.datetime.utcnow() template_mock = Mock(spec=CloudFormationTemplate) template_mock.url = "foo.yml" template_mock.get_template_body_dict.return_value = {} event = StackEvent() event.resource_type = "AWS::CloudFormation::Stack" event.resource_status = "UPDATE_IN_PROGRESS" event.event_id = "123" event.timestamp = timestamp stack_events_mock = Mock() stack_events_mock.describe_stack_events.return_value = [event] cloudformation_mock.connect_to_region.return_value = stack_events_mock cfn = CloudFormation() event = cfn.wait_for_stack_events("foo", "UPDATE_IN_PROGRESS", timestamp - timedelta(seconds=10), timeout=10) self.assertEqual(timestamp, event.timestamp)
def test_wait_for_stack_event_returns_on_update_complete( self, cloudformation_mock): timestamp = datetime.datetime.utcnow() template_mock = Mock(spec=CloudFormationTemplate) template_mock.url = "foo.yml" template_mock.get_template_body_dict.return_value = {} event = StackEvent() event.resource_type = "AWS::CloudFormation::Stack" event.resource_status = "UPDATE_COMPLETE" event.event_id = "123" event.timestamp = timestamp stack_events_mock = Mock() stack_events_mock.describe_stack_events.return_value = [event] cloudformation_mock.connect_to_region.return_value = stack_events_mock cfn = CloudFormation() cfn.wait_for_stack_events("foo", "UPDATE_COMPLETE", timestamp - timedelta(seconds=10), timeout=10)
def __init__(self, config): self.logger = get_logger(root=True) self.config = config self.cfn = CloudFormation(region=self.config.region) self.parameter_resolver = ParameterResolver(region=self.config.region)
class StackActionHandler(object): def __init__(self, config): self.logger = get_logger(root=True) self.config = config self.cfn = CloudFormation(region=self.config.region) self.parameter_resolver = ParameterResolver(region=self.config.region) def create_or_update_stacks(self): existing_stacks = self.cfn.get_stack_names() desired_stacks = self.config.stacks stack_processing_order = DependencyResolver().get_stack_order(desired_stacks) if len(stack_processing_order) > 1: self.logger.info( "Will process stacks in the following order: {0}".format(", ".join(stack_processing_order))) for stack_name in stack_processing_order: stack_config = self.config.stacks.get(stack_name) raw_template = FileLoader.get_file_from_url(stack_config.template_url, stack_config.working_dir) template = CloudFormationTemplateTransformer.transform_template(raw_template) combined_tags = dict(self.config.tags) combined_tags.update(stack_config.tags) parameters = self.parameter_resolver.resolve_parameter_values(stack_config.parameters, stack_name) stack = CloudFormationStack(template=template, parameters=parameters, tags=combined_tags, name=stack_name, region=self.config.region, timeout=stack_config.timeout) if stack_name in existing_stacks: self.cfn.validate_stack_is_ready_for_action(stack) self.cfn.update_stack(stack) else: self.cfn.create_stack(stack) CustomResourceHandler.process_post_resources(stack) def delete_stacks(self): existing_stacks = self.cfn.get_stack_names() stacks = self.config.stacks stack_processing_order = DependencyResolver().get_stack_order(stacks) stack_processing_order.reverse() self.logger.info("Will delete stacks in the following order: {0}".format(", ".join(stack_processing_order))) for stack_name in stack_processing_order: if stack_name in existing_stacks: stack = CloudFormationStack(None, None, stack_name, None, None) self.cfn.validate_stack_is_ready_for_action(stack) self.cfn.delete_stack(stack) else: self.logger.info("Stack {0} is already deleted".format(stack_name))
def test_is_boto_stack_does_not_exist_exception_returns_true_for_message2(self): exception = Mock(spec=ClientError) exception.response = {"Error": {"Message": "Stack with id foo does not exist"}} self.assertTrue(CloudFormation.is_boto_stack_does_not_exist_exception(exception))
def test_is_boto_stack_does_not_exist_exception_returns_false_with_other_message(self): exception = Mock(spec=ClientError) exception.response = {"Error": {"Message": "Other error"}} self.assertFalse(CloudFormation.is_boto_stack_does_not_exist_exception(exception))
def test_is_boto_stack_does_not_exist_exception_returns_false_with_other_exception(self): exception = Mock(spec=Exception) exception.message = "No updates are to be performed." self.assertFalse(CloudFormation.is_boto_stack_does_not_exist_exception(exception))
def test_is_boto_no_update_required_exception_returns_true_for_message(self): exception = Mock(spec=ClientError) exception.response = {"Error": {"Message": "No updates are to be performed."}} self.assertTrue(CloudFormation.is_boto_no_update_required_exception(exception))
def test_is_boto_no_update_required_exception_returns_false_without_message(self): exception = Mock(spec=ClientError) exception.response = {"Error": {"Message": "Something went wrong"}} self.assertFalse(CloudFormation.is_boto_no_update_required_exception(exception))