def test_delete_stacks_uses_the_correct_order( self, custom_resource_mock, stack_mock, template_loader_mock, dependency_resolver_mock, parameter_resolver_mock, cfn_mock): dependency_resolver_mock.return_value.get_stack_order.return_value = [ 'a', 'c' ] cfn_mock.return_value.get_stack_names.return_value = ['a', 'c'] stack_a = CloudFormationStack('', [], 'a', '') stack_c = CloudFormationStack('', [], 'c', '') def stack_side_effect(*args, **kwargs): if args[2] == 'a': return stack_a if args[2] == 'c': return stack_c return None stack_mock.side_effect = stack_side_effect handler = StackActionHandler(Mock()) handler.delete_stacks() expected_calls = [call(stack_c), call(stack_a)] six.assertCountEqual(self, expected_calls, cfn_mock.return_value.delete_stack.mock_calls)
def test_create_or_update_tests_exits_gracefully_if_preexisting_stack_disappears( self, custom_resource_mock, stack_mock, template_loader_mock, dependency_resolver_mock, parameter_resolver_mock, cfn_mock): dependency_resolver_mock.return_value.get_stack_order.return_value = [ 'a', 'c' ] cfn_mock.return_value.get_stack_names.return_value = ['a', 'd'] stack_a = CloudFormationStack('', [], 'a', '') stack_c = CloudFormationStack('', [], 'c', '') def stack_side_effect(*args, **kwargs): if kwargs['name'] == 'a': return stack_a if kwargs['name'] == 'c': return stack_c return None stack_mock.side_effect = stack_side_effect handler = StackActionHandler(Mock()) handler.create_or_update_stacks() # stack a needs update cfn_mock.return_value.validate_stack_is_ready_for_action.assert_called_once_with( stack_a) cfn_mock.return_value.update_stack.assert_called_once_with(stack_a) # stack c doesn't exist, must be created cfn_mock.return_value.create_stack.assert_called_once_with(stack_c)
def test_delete_stacks(self, custom_resource_mock, stack_mock, template_loader_mock, dependency_resolver_mock, parameter_resolver_mock, cfn_mock): dependency_resolver_mock.return_value.get_stack_order.return_value = [ 'a', 'c' ] cfn_mock.return_value.get_stack_names.return_value = ['a', 'd'] stack_a = CloudFormationStack('', [], 'a', '') stack_c = CloudFormationStack('', [], 'c', '') def stack_side_effect(*args, **kwargs): if args[2] == 'a': return stack_a if args[2] == 'c': return stack_c return None stack_mock.side_effect = stack_side_effect handler = StackActionHandler(Mock()) handler.delete_stacks() cfn_mock.return_value.delete_stack.assert_called_once_with(stack_a)
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: stack_config = self.config.stacks.get(stack_name) if stack_name in existing_stacks: stack = CloudFormationStack( None, None, stack_name, None, None, service_role=stack_config.service_role) 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_process_post_resources(self, handle_sns_subscriptions_mock): custom_resource_yaml = dedent(""" PostCustomResources: exposeTopicSubscription: Type: "Custom::SNS::Subscription" Properties: TopicArn: Ref: exposeTopicArn QueueResourceName: exposeQueue """) custom_resource_dict = yaml.load(custom_resource_yaml) template = CloudFormationTemplate(custom_resource_dict, 'foo') stack = CloudFormationStack(template, {}, 'foo', 'foo-region') CustomResourceHandler.process_post_resources(stack) handle_sns_subscriptions_mock.assert_called_once_with( { 'Type': 'Custom::SNS::Subscription', 'Properties': { 'TopicArn': { 'Ref': 'exposeTopicArn' }, 'QueueResourceName': 'exposeQueue' } }, 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_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_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_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 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) if stack_config.stack_policy_url: self.logger.info("Using stack policy from {0}".format( stack_config.stack_policy_url)) stack_policy = FileLoader.get_yaml_or_json_file( stack_config.stack_policy_url, stack_config.working_dir) else: stack_policy = None template = TemplateHandler.get_template(stack_config.template_url, stack_config.working_dir) parameters = self.parameter_resolver.resolve_parameter_values( stack_name, stack_config, self.cli_parameters) stack = CloudFormationStack( template=template, parameters=parameters, tags=stack_config.tags, name=stack_name, region=self.config.region, timeout=stack_config.timeout, service_role=stack_config.service_role, stack_policy=stack_policy, failure_action=stack_config.failure_action) 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 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 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) parameters = self.parameter_resolver.resolve_parameter_values( stack_config.parameters, stack_name) stack = CloudFormationStack(template=template, parameters=parameters, tags=(stack_config.tags), name=stack_name, region=self.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)