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 __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_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_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_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_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_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_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_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 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_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_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_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_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_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_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_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_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_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_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_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 validate_template(template_file, confirm, yes): confirm = confirm or yes if not confirm: check_update_available() try: loader = FileLoader() template = loader.get_cloudformation_template(template_file, None) template = CloudFormationTemplateTransformer.transform_template(template) CloudFormation().validate_template(template) click.echo("Template is valid") except CfnSphereException as e: LOGGER.error(e) sys.exit(1) except Exception as e: LOGGER.error("Failed with unexpected error") LOGGER.exception(e) LOGGER.info("Please report at https://github.com/cfn-sphere/cfn-sphere/issues!") sys.exit(1)
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_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_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_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_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)