예제 #1
0
 def test_context_namespace_delimiter_is_overriden_and_is_empty(self):
     config = Config({"namespace": "namespace", "namespace_delimiter": ""})
     context = Context(config=config)
     fqn = context.get_fqn("stack1")
     self.assertEqual(fqn, "namespacestack1")
예제 #2
0
class TestStack(unittest.TestCase):
    def setUp(self):
        self.sd = {"name": "test"}
        self.context = Context({"namespace": "namespace"})
        self.stack = Stack(
            definition=generate_definition("vpc", 1),
            context=self.context,
        )

    def test_stack_requires(self):
        definition = generate_definition(
            "vpc",
            1,
            parameters={
                "ExternalParameter": "fakeStack2::FakeParameter",
            },
            requires=[self.context.get_fqn("fakeStack")],
        )
        stack = Stack(definition=definition, context=self.context)
        self.assertIn(
            self.context.get_fqn("fakeStack"),
            stack.requires,
        )
        self.assertIn(
            self.context.get_fqn("fakeStack2"),
            stack.requires,
        )

    def test_empty_parameters(self):
        build_action_parameters = {}
        self.assertEqual({},
                         _gather_parameters(self.sd, build_action_parameters))

    def test_generic_build_action_override(self):
        sdef = self.sd
        sdef["parameters"] = {"Address": "10.0.0.1", "Foo": "BAR"}
        build_action_parameters = {"Address": "192.168.1.1"}
        result = _gather_parameters(sdef, build_action_parameters)
        self.assertEqual(result["Address"], "192.168.1.1")
        self.assertEqual(result["Foo"], "BAR")

    def test_stack_specific_override(self):
        sdef = self.sd
        sdef["parameters"] = {"Address": "10.0.0.1", "Foo": "BAR"}
        build_action_parameters = {"test::Address": "192.168.1.1"}
        result = _gather_parameters(sdef, build_action_parameters)
        self.assertEqual(result["Address"], "192.168.1.1")
        self.assertEqual(result["Foo"], "BAR")

    def test_invalid_stack_specific_override(self):
        sdef = self.sd
        sdef["parameters"] = {"Address": "10.0.0.1", "Foo": "BAR"}
        build_action_parameters = {"FAKE::Address": "192.168.1.1"}
        result = _gather_parameters(sdef, build_action_parameters)
        self.assertEqual(result["Address"], "10.0.0.1")
        self.assertEqual(result["Foo"], "BAR")

    def test_specific_vs_generic_build_action_override(self):
        sdef = self.sd
        sdef["parameters"] = {"Address": "10.0.0.1", "Foo": "BAR"}
        build_action_parameters = {
            "test::Address": "192.168.1.1",
            "Address": "10.0.0.1"
        }
        result = _gather_parameters(sdef, build_action_parameters)
        self.assertEqual(result["Address"], "192.168.1.1")
        self.assertEqual(result["Foo"], "BAR")
예제 #3
0
 def test_context_get_fqn_empty_namespace(self):
     context = Context(config=Config({"namespace": ""}))
     fqn = context.get_fqn("vpc")
     self.assertEqual(fqn, "vpc")
     self.assertEqual(context.tags, {})
예제 #4
0
 def test_context_get_fqn_stack_name(self):
     context = Context(config=self.config)
     fqn = context.get_fqn("stack1")
     self.assertEqual(fqn, "namespace-stack1")
예제 #5
0
 def test_context_get_fqn(self):
     context = Context(config=self.config)
     fqn = context.get_fqn()
     self.assertEqual(fqn, "namespace")
예제 #6
0
 def test_context_get_fqn_replace_dot(self):
     context = Context(config=Config({"namespace": "my.namespace"}))
     fqn = context.get_fqn()
     self.assertEqual(fqn, "my-namespace")
예제 #7
0
 def test_context_get_fqn_stack_name(self):
     context = Context(config=self.config)
     fqn = context.get_fqn("stack1")
     self.assertEqual(fqn, "namespace-stack1")
예제 #8
0
class TestDestroyAction(unittest.TestCase):
    def setUp(self):
        self.context = Context({'namespace': 'namespace'})
        self.context.config = {
            'stacks': [
                {
                    'name': 'vpc'
                },
                {
                    'name': 'bastion',
                    'requires': ['vpc']
                },
                {
                    'name': 'instance',
                    'requires': ['vpc', 'bastion']
                },
                {
                    'name': 'db',
                    'requires': ['instance', 'vpc', 'bastion']
                },
                {
                    'name': 'other',
                    'requires': ['db']
                },
            ],
        }
        self.action = destroy.Action(self.context, provider=mock.MagicMock())

    def test_generate_plan(self):
        plan = self.action._generate_plan()
        self.assertEqual(
            [
                self.context.get_fqn(s)
                for s in ['other', 'db', 'instance', 'bastion', 'vpc']
            ],
            plan.keys(),
        )

    def test_only_execute_plan_when_forced(self):
        with mock.patch.object(self.action,
                               '_generate_plan') as mock_generate_plan:
            self.action.run(force=False)
            self.assertEqual(mock_generate_plan().execute.call_count, 0)

    def test_execute_plan_when_forced(self):
        with mock.patch.object(self.action,
                               '_generate_plan') as mock_generate_plan:
            self.action.run(force=True)
            self.assertEqual(mock_generate_plan().execute.call_count, 1)

    def test_destroy_stack_complete_if_state_submitted(self):
        # Simulate the provider not being able to find the stack (a result of
        # it being successfully deleted)
        self.action.provider = mock.MagicMock()
        self.action.provider.get_stack.side_effect = StackDoesNotExist('mock')
        status = self.action._destroy_stack(MockStack('vpc'), status=PENDING)
        # if we haven't processed the step (ie. has never been SUBMITTED, should be skipped)
        self.assertEqual(status, SKIPPED)
        status = self.action._destroy_stack(MockStack('vpc'), status=SUBMITTED)
        # if we have processed the step and then can't find the stack, it means
        # we successfully deleted it
        self.assertEqual(status, COMPLETE)

    def test_destroy_stack_in_parallel(self):
        count = {'_count': 0}
        mock_provider = mock.MagicMock()
        self.context.config = {
            'stacks': [
                {
                    'name': 'vpc'
                },
                {
                    'name': 'bastion',
                    'requires': ['vpc']
                },
                {
                    'name': 'instance',
                    'requires': ['vpc']
                },
                {
                    'name': 'db',
                    'requies': ['vpc']
                },
            ],
        }
        stacks_dict = self.context.get_stacks_dict()

        def get_stack(stack_name):
            return stacks_dict.get(stack_name)

        def get_stack_staggered(stack_name):
            count['_count'] += 1
            if not count['_count'] % 2:
                raise StackDoesNotExist(stack_name)
            return stacks_dict.get(stack_name)

        def wait_func(*args):
            # we want "get_stack" above to return staggered results for the stack
            # being "deleted" to simulate waiting on stacks to complete
            mock_provider.get_stack.side_effect = get_stack_staggered

        plan = self.action._generate_plan()
        plan._wait_func = wait_func

        # swap for mock provider
        plan.provider = mock_provider
        self.action.provider = mock_provider

        # we want "get_stack" to return the mock stacks above on the first
        # pass. "wait_func" will simulate the stack being deleted every second
        # pass
        mock_provider.get_stack.side_effect = get_stack
        mock_provider.is_stack_destroyed.return_value = False
        mock_provider.is_stack_in_progress.return_value = True

        independent_stacks = filter(lambda x: x.name != 'vpc',
                                    self.context.get_stacks())
        while not plan._single_run():
            # vpc should be the last stack that is deleted
            if plan['namespace-vpc'].completed:
                self.assertFalse(plan.list_pending())

            # all of the independent stacks should be submitted at the same time
            if any([plan[stack.fqn].submitted
                    for stack in independent_stacks]):
                self.assertTrue(
                    all([
                        plan[stack.fqn].submitted
                        for stack in independent_stacks
                    ]))
            wait_func()

    def test_destroy_stack_step_statuses(self):
        mock_provider = mock.MagicMock()
        stacks_dict = self.context.get_stacks_dict()

        def get_stack(stack_name):
            return stacks_dict.get(stack_name)

        plan = self.action._generate_plan()
        _, step = plan.list_pending()[0]
        # we need the AWS provider to generate the plan, but swap it for
        # the mock one to make the test easier
        self.action.provider = mock_provider

        # simulate stack doesn't exist and we haven't submitted anything for deletion
        mock_provider.get_stack.side_effect = StackDoesNotExist('mock')
        status = step.run()
        self.assertEqual(status, SKIPPED)

        # simulate stack getting successfully deleted
        mock_provider.get_stack.side_effect = get_stack
        mock_provider.is_stack_destroyed.return_value = False
        mock_provider.is_stack_in_progress.return_value = False
        status = step.run()
        self.assertEqual(status, SUBMITTED)
        mock_provider.is_stack_destroyed.return_value = False
        mock_provider.is_stack_in_progress.return_value = True
        status = step.run()
        self.assertEqual(status, SUBMITTED)
        mock_provider.is_stack_destroyed.return_value = True
        mock_provider.is_stack_in_progress.return_value = False
        status = step.run()
        self.assertEqual(status, COMPLETE)
예제 #9
0
 def test_context_get_fqn_replace_dot(self):
     context = Context(config=Config({"namespace": "my.namespace"}))
     fqn = context.get_fqn()
     self.assertEqual(fqn, "my-namespace")
예제 #10
0
 def test_context_get_fqn_empty_namespace(self):
     context = Context(config=Config({"namespace": ""}))
     fqn = context.get_fqn("vpc")
     self.assertEqual(fqn, "vpc")
     self.assertEqual(context.tags, {})
예제 #11
0
 def test_context_get_fqn(self):
     context = Context(config=self.config)
     fqn = context.get_fqn()
     self.assertEqual(fqn, "namespace")
예제 #12
0
class TestDestroyAction(unittest.TestCase):

    def setUp(self):
        self.context = Context({'namespace': 'namespace'})
        self.context.config = {
            'stacks': [
                {'name': 'vpc'},
                {'name': 'bastion', 'requires': ['vpc']},
                {'name': 'instance', 'requires': ['vpc', 'bastion']},
                {'name': 'db', 'requires': ['instance', 'vpc', 'bastion']},
                {'name': 'other', 'requires': ['db']},
            ],
        }
        self.action = destroy.Action(self.context, provider=mock.MagicMock())

    def test_generate_plan(self):
        plan = self.action._generate_plan()
        self.assertEqual(
            [self.context.get_fqn(s) for s in ['other', 'db', 'instance', 'bastion', 'vpc']],
            plan.keys(),
        )

    def test_only_execute_plan_when_forced(self):
        with mock.patch.object(self.action, '_generate_plan') as mock_generate_plan:
            self.action.run(force=False)
            self.assertEqual(mock_generate_plan().execute.call_count, 0)

    def test_execute_plan_when_forced(self):
        with mock.patch.object(self.action, '_generate_plan') as mock_generate_plan:
            self.action.run(force=True)
            self.assertEqual(mock_generate_plan().execute.call_count, 1)

    def test_destroy_stack_complete_if_state_submitted(self):
        # Simulate the provider not being able to find the stack (a result of
        # it being successfully deleted)
        self.action.provider = mock.MagicMock()
        self.action.provider.get_stack.side_effect = StackDoesNotExist('mock')
        status = self.action._destroy_stack(MockStack('vpc'), status=PENDING)
        # if we haven't processed the step (ie. has never been SUBMITTED, should be skipped)
        self.assertEqual(status, SKIPPED)
        status = self.action._destroy_stack(MockStack('vpc'), status=SUBMITTED)
        # if we have processed the step and then can't find the stack, it means
        # we successfully deleted it
        self.assertEqual(status, COMPLETE)

    def test_destroy_stack_in_parallel(self):
        count = {'_count': 0}
        mock_provider = mock.MagicMock()
        self.context.config = {
            'stacks': [
                {'name': 'vpc'},
                {'name': 'bastion', 'requires': ['vpc']},
                {'name': 'instance', 'requires': ['vpc']},
                {'name': 'db', 'requies': ['vpc']},
            ],
        }
        stacks_dict = self.context.get_stacks_dict()

        def get_stack(stack_name):
            return stacks_dict.get(stack_name)

        def get_stack_staggered(stack_name):
            count['_count'] += 1
            if not count['_count'] % 2:
                raise StackDoesNotExist(stack_name)
            return stacks_dict.get(stack_name)

        def wait_func(*args):
            # we want "get_stack" above to return staggered results for the stack
            # being "deleted" to simulate waiting on stacks to complete
            mock_provider.get_stack.side_effect = get_stack_staggered

        plan = self.action._generate_plan()
        plan._wait_func = wait_func

        # swap for mock provider
        plan.provider = mock_provider
        self.action.provider = mock_provider

        # we want "get_stack" to return the mock stacks above on the first
        # pass. "wait_func" will simulate the stack being deleted every second
        # pass
        mock_provider.get_stack.side_effect = get_stack
        mock_provider.is_stack_destroyed.return_value = False
        mock_provider.is_stack_in_progress.return_value = True

        independent_stacks = filter(lambda x: x.name != 'vpc', self.context.get_stacks())
        while not plan._single_run():
            # vpc should be the last stack that is deleted
            if plan['namespace-vpc'].completed:
                self.assertFalse(plan.list_pending())

            # all of the independent stacks should be submitted at the same time
            if any([plan[stack.fqn].submitted for stack in independent_stacks]):
                self.assertTrue(all([plan[stack.fqn].submitted for stack in independent_stacks]))
            wait_func()

    def test_destroy_stack_step_statuses(self):
        mock_provider = mock.MagicMock()
        stacks_dict = self.context.get_stacks_dict()

        def get_stack(stack_name):
            return stacks_dict.get(stack_name)

        plan = self.action._generate_plan()
        _, step = plan.list_pending()[0]
        # we need the AWS provider to generate the plan, but swap it for
        # the mock one to make the test easier
        self.action.provider = mock_provider

        # simulate stack doesn't exist and we haven't submitted anything for deletion
        mock_provider.get_stack.side_effect = StackDoesNotExist('mock')
        status = step.run()
        self.assertEqual(status, SKIPPED)

        # simulate stack getting successfully deleted
        mock_provider.get_stack.side_effect = get_stack
        mock_provider.is_stack_destroyed.return_value = False
        mock_provider.is_stack_in_progress.return_value = False
        status = step.run()
        self.assertEqual(status, SUBMITTED)
        mock_provider.is_stack_destroyed.return_value = False
        mock_provider.is_stack_in_progress.return_value = True
        status = step.run()
        self.assertEqual(status, SUBMITTED)
        mock_provider.is_stack_destroyed.return_value = True
        mock_provider.is_stack_in_progress.return_value = False
        status = step.run()
        self.assertEqual(status, COMPLETE)
예제 #13
0
class TestStack(unittest.TestCase):
    def setUp(self):
        self.sd = {"name": "test"}
        self.context = Context({"namespace": "namespace"})
        self.stack = Stack(
            definition=generate_definition("vpc", 1),
            context=self.context,
        )

    def test_stack_requires(self):
        definition = generate_definition(
            base_name="vpc",
            stack_id=1,
            variables={
                "Var1":
                "${noop fakeStack3::FakeOutput}",
                "Var2":
                ("some.template.value:${output fakeStack2::FakeOutput}:"
                 "${output fakeStack::FakeOutput}"),
                "Var3":
                "${output fakeStack::FakeOutput},"
                "${output fakeStack2::FakeOutput}",
            },
            requires=[self.context.get_fqn("fakeStack")],
        )
        stack = Stack(definition=definition, context=self.context)
        self.assertEqual(len(stack.requires), 2)
        self.assertIn(
            self.context.get_fqn("fakeStack"),
            stack.requires,
        )
        self.assertIn(
            self.context.get_fqn("fakeStack2"),
            stack.requires,
        )

    def test_stack_requires_circular_ref(self):
        definition = generate_definition(
            base_name="vpc",
            stack_id=1,
            variables={
                "Var1": "${output vpc.1::FakeOutput}",
            },
        )
        stack = Stack(definition=definition, context=self.context)
        with self.assertRaises(ValueError):
            stack.requires

    def test_stack_cfn_parameters(self):
        definition = generate_definition(
            base_name="vpc",
            stack_id=1,
            variables={
                "Param1": "${output fakeStack::FakeOutput}",
            },
        )
        stack = Stack(definition=definition, context=self.context)
        stack._blueprint = MagicMock()
        stack._blueprint.get_parameter_values.return_value = {
            "Param2": "Some Resolved Value",
        }
        self.assertEqual(len(stack.parameter_values.keys()), 1)
        param = stack.parameter_values["Param2"]
        self.assertEqual(param, "Some Resolved Value")

    def test_gather_variables_fails_on_parameters_in_stack_def(self):
        sdef = self.sd
        sdef["parameters"] = {"Address": "10.0.0.1", "Foo": "BAR"}
        with self.assertRaises(AttributeError):
            _gather_variables(sdef)
예제 #14
0
class TestStack(unittest.TestCase):

    def setUp(self):
        self.sd = {"name": "test"}
        self.context = Context({"namespace": "namespace"})
        self.stack = Stack(
            definition=generate_definition("vpc", 1),
            context=self.context,
        )

    def test_stack_requires(self):
        definition = generate_definition(
            base_name="vpc",
            stack_id=1,
            parameters={
                "ExternalParameter": "fakeStack2::FakeParameter",
            },
            variables={
                "Var1": "${noop fakeStack3::FakeOutput}",
                "Var2": (
                    "some.template.value:${fakeStack2::FakeOutput}:"
                    "${fakeStack::FakeOutput}"
                ),
                "Var3": "${fakeStack::FakeOutput},"
                        "${output fakeStack2::FakeOutput}",
            },
            requires=[self.context.get_fqn("fakeStack")],
        )
        stack = Stack(definition=definition, context=self.context)
        self.assertEqual(len(stack.requires), 2)
        self.assertIn(
            self.context.get_fqn("fakeStack"),
            stack.requires,
        )
        self.assertIn(
            self.context.get_fqn("fakeStack2"),
            stack.requires,
        )

    def test_stack_requires_circular_ref(self):
        definition = generate_definition(
            base_name="vpc",
            stack_id=1,
            variables={
                "Var1": "${vpc.1::FakeOutput}",
            },
        )
        stack = Stack(definition=definition, context=self.context)
        with self.assertRaises(ValueError):
            stack.requires

    def test_stack_cfn_parameters(self):
        definition = generate_definition(
            base_name="vpc",
            stack_id=1,
            parameters={
                "Param1": "fakeStack::FakeOutput",
            },
        )
        stack = Stack(definition=definition, context=self.context)
        stack._blueprint = MagicMock()
        stack._blueprint.get_cfn_parameters.return_value = {
            "Param2": "Some Resolved Value",
        }
        self.assertEqual(len(stack.cfn_parameters.keys()), 2)
        param = stack.cfn_parameters["Param2"]
        self.assertEqual(param, "Some Resolved Value")

    def test_empty_parameters(self):
        build_action_parameters = {}
        self.assertEqual({}, _gather_parameters(self.sd,
                                                build_action_parameters))

    def test_generic_build_action_override(self):
        sdef = self.sd
        sdef["parameters"] = {"Address": "10.0.0.1", "Foo": "BAR"}
        build_action_parameters = {"Address": "192.168.1.1"}
        result = _gather_parameters(sdef, build_action_parameters)
        self.assertEqual(result["Address"], "192.168.1.1")
        self.assertEqual(result["Foo"], "BAR")

    def test_stack_specific_override(self):
        sdef = self.sd
        sdef["parameters"] = {"Address": "10.0.0.1", "Foo": "BAR"}
        build_action_parameters = {"test::Address": "192.168.1.1"}
        result = _gather_parameters(sdef, build_action_parameters)
        self.assertEqual(result["Address"], "192.168.1.1")
        self.assertEqual(result["Foo"], "BAR")

    def test_invalid_stack_specific_override(self):
        sdef = self.sd
        sdef["parameters"] = {"Address": "10.0.0.1", "Foo": "BAR"}
        build_action_parameters = {"FAKE::Address": "192.168.1.1"}
        result = _gather_parameters(sdef, build_action_parameters)
        self.assertEqual(result["Address"], "10.0.0.1")
        self.assertEqual(result["Foo"], "BAR")

    def test_specific_vs_generic_build_action_override(self):
        sdef = self.sd
        sdef["parameters"] = {"Address": "10.0.0.1", "Foo": "BAR"}
        build_action_parameters = {
            "test::Address": "192.168.1.1",
            "Address": "10.0.0.1"}
        result = _gather_parameters(sdef, build_action_parameters)
        self.assertEqual(result["Address"], "192.168.1.1")
        self.assertEqual(result["Foo"], "BAR")
예제 #15
0
 def test_context_namespace_delimiter_is_overriden_and_is_empty(self):
     config = Config({"namespace": "namespace", "namespace_delimiter": ""})
     context = Context(config=config)
     fqn = context.get_fqn("stack1")
     self.assertEqual(fqn, "namespacestack1")
예제 #16
0
파일: test_stack.py 프로젝트: 40a/stacker
class TestStack(unittest.TestCase):

    def setUp(self):
        self.sd = {"name": "test"}
        self.context = Context({"namespace": "namespace"})
        self.stack = Stack(
            definition=generate_definition("vpc", 1),
            context=self.context,
        )

    def test_stack_requires(self):
        definition = generate_definition(
            "vpc",
            1,
            parameters={
                "ExternalParameter": "fakeStack2::FakeParameter",
            },
            requires=[self.context.get_fqn("fakeStack")],
        )
        stack = Stack(definition=definition, context=self.context)
        self.assertIn(
            self.context.get_fqn("fakeStack"),
            stack.requires,
        )
        self.assertIn(
            self.context.get_fqn("fakeStack2"),
            stack.requires,
        )

    def test_empty_parameters(self):
        build_action_parameters = {}
        self.assertEqual({}, _gather_parameters(self.sd,
                                                build_action_parameters))

    def test_generic_build_action_override(self):
        sdef = self.sd
        sdef["parameters"] = {"Address": "10.0.0.1", "Foo": "BAR"}
        build_action_parameters = {"Address": "192.168.1.1"}
        result = _gather_parameters(sdef, build_action_parameters)
        self.assertEqual(result["Address"], "192.168.1.1")
        self.assertEqual(result["Foo"], "BAR")

    def test_stack_specific_override(self):
        sdef = self.sd
        sdef["parameters"] = {"Address": "10.0.0.1", "Foo": "BAR"}
        build_action_parameters = {"test::Address": "192.168.1.1"}
        result = _gather_parameters(sdef, build_action_parameters)
        self.assertEqual(result["Address"], "192.168.1.1")
        self.assertEqual(result["Foo"], "BAR")

    def test_invalid_stack_specific_override(self):
        sdef = self.sd
        sdef["parameters"] = {"Address": "10.0.0.1", "Foo": "BAR"}
        build_action_parameters = {"FAKE::Address": "192.168.1.1"}
        result = _gather_parameters(sdef, build_action_parameters)
        self.assertEqual(result["Address"], "10.0.0.1")
        self.assertEqual(result["Foo"], "BAR")

    def test_specific_vs_generic_build_action_override(self):
        sdef = self.sd
        sdef["parameters"] = {"Address": "10.0.0.1", "Foo": "BAR"}
        build_action_parameters = {
            "test::Address": "192.168.1.1",
            "Address": "10.0.0.1"}
        result = _gather_parameters(sdef, build_action_parameters)
        self.assertEqual(result["Address"], "192.168.1.1")
        self.assertEqual(result["Foo"], "BAR")
예제 #17
0
class TestBuildAction(unittest.TestCase):

    def setUp(self):
        self.context = Context({'namespace': 'namespace'})
        self.build_action = build.Action(self.context, provider=TestProvider())

    def _get_context(self, **kwargs):
        config = {'stacks': [
            {'name': 'vpc'},
            {'name': 'bastion', 'parameters': {'test': 'vpc::something'}},
            {'name': 'db', 'parameters': {'test': 'vpc::something',
             'else': 'bastion::something'}},
            {'name': 'other', 'parameters': {}}
        ]}
        return Context({'namespace': 'namespace'}, config=config, **kwargs)

    def test_resolve_parameters_referencing_non_existant_output(self):
        parameters = {
            'param_1': 'mock::output_1',
            'param_2': 'other::does_not_exist',
        }
        self.build_action.provider.set_outputs({
            self.context.get_fqn('mock'): {'output_1': 'output'},
            self.context.get_fqn('other'): {},
        })
        mock_blueprint = mock.MagicMock()
        type(mock_blueprint).parameters = parameters
        with self.assertRaises(exceptions.OutputDoesNotExist):
            self.build_action._resolve_parameters(parameters,
                                                  mock_blueprint)

    def test_resolve_parameters(self):
        parameters = {
            'param_1': 'mock::output_1',
            'param_2': 'other::output_2',
        }
        self.build_action.provider.set_outputs({
            self.context.get_fqn('mock'): {'output_1': 'output1'},
            self.context.get_fqn('other'): {'output_2': 'output2'},
        })

        mock_blueprint = mock.MagicMock()
        type(mock_blueprint).parameters = parameters
        resolved_parameters = self.build_action._resolve_parameters(
            parameters,
            mock_blueprint,
        )
        self.assertEqual(resolved_parameters['param_1'], 'output1')
        self.assertEqual(resolved_parameters['param_2'], 'output2')

    def test_resolve_parameters_referencing_non_existant_stack(self):
        parameters = {
            'param_1': 'mock::output_1',
        }
        self.build_action.provider.set_outputs({})
        mock_blueprint = mock.MagicMock()
        type(mock_blueprint).parameters = parameters
        with self.assertRaises(exceptions.StackDoesNotExist):
            self.build_action._resolve_parameters(parameters,
                                                  mock_blueprint)

    def test_gather_missing_from_stack(self):
        stack_params = {'Address': '10.0.0.1'}
        stack = MockStack(stack_params)
        def_params = {}
        required = ['Address']
        self.assertEqual(
            self.build_action._handle_missing_parameters(def_params, required,
                                                         stack),
            stack_params.items())

    def test_missing_params_no_stack(self):
        params = {}
        required = ['Address']
        with self.assertRaises(exceptions.MissingParameterException) as cm:
            self.build_action._handle_missing_parameters(params, required)

        self.assertEqual(cm.exception.parameters, required)

    def test_stack_params_dont_override_given_params(self):
        stack_params = {'Address': '10.0.0.1'}
        stack = MockStack(stack_params)
        def_params = {'Address': '192.168.0.1'}
        required = ['Address']
        result = self.build_action._handle_missing_parameters(def_params,
                                                              required, stack)
        self.assertEqual(result, def_params.items())

    def test_get_dependencies(self):
        context = self._get_context()
        build_action = build.Action(context)
        dependencies = build_action._get_dependencies()
        self.assertEqual(
            dependencies[context.get_fqn('bastion')],
            set([context.get_fqn('vpc')]),
        )
        self.assertEqual(
            dependencies[context.get_fqn('db')],
            set([context.get_fqn(s) for s in['vpc', 'bastion']]),
        )
        self.assertFalse(dependencies[context.get_fqn('other')])

    def test_get_stack_execution_order(self):
        context = self._get_context()
        build_action = build.Action(context)
        dependencies = build_action._get_dependencies()
        execution_order = build_action.get_stack_execution_order(dependencies)
        self.assertEqual(
            execution_order,
            [context.get_fqn(s) for s in ['other', 'vpc', 'bastion', 'db']],
        )

    def test_generate_plan(self):
        context = self._get_context()
        build_action = build.Action(context)
        plan = build_action._generate_plan()
        self.assertEqual(
            plan.keys(),
            [context.get_fqn(s) for s in ['other', 'vpc', 'bastion', 'db']],
        )

    def test_dont_execute_plan_when_outline_specified(self):
        context = self._get_context()
        build_action = build.Action(context)
        with mock.patch.object(build_action, '_generate_plan') as \
                mock_generate_plan:
            build_action.run(outline=True)
            self.assertEqual(mock_generate_plan().execute.call_count, 0)

    def test_execute_plan_when_outline_not_specified(self):
        context = self._get_context()
        build_action = build.Action(context)
        with mock.patch.object(build_action, '_generate_plan') as \
                mock_generate_plan:
            build_action.run(outline=False)
            self.assertEqual(mock_generate_plan().execute.call_count, 1)

    def test_launch_stack_step_statuses(self):
        mock_provider = mock.MagicMock()
        mock_stack = mock.MagicMock()

        context = self._get_context()
        build_action = build.Action(context, provider=mock_provider)
        plan = build_action._generate_plan()
        _, step = plan.list_pending()[0]
        step.stack = mock.MagicMock()
        step.stack.locked = False

        # mock provider shouldn't return a stack at first since it hasn't been
        # launched
        mock_provider.get_stack.return_value = None
        with mock.patch.object(build_action, 's3_stack_push'):
            # initial run should return SUBMITTED since we've passed off to CF
            status = step.run()
            self.assertEqual(status, SUBMITTED)

            # provider should now return the CF stack since it exists
            mock_provider.get_stack.return_value = mock_stack
            # simulate that we're still in progress
            mock_provider.is_stack_in_progress.return_value = True
            mock_provider.is_stack_completed.return_value = False
            status = step.run()
            step.set_status(status)
            # status should still be SUBMITTED since we're waiting for it to
            # complete
            self.assertEqual(status, SUBMITTED)
            # simulate completed stack
            mock_provider.is_stack_completed.return_value = True
            mock_provider.is_stack_in_progress.return_value = False
            status = step.run()
            self.assertEqual(status, COMPLETE)
            # simulate stack should be skipped
            mock_provider.is_stack_completed.return_value = False
            mock_provider.is_stack_in_progress.return_value = False
            mock_provider.update_stack.side_effect = StackDidNotChange
            status = step.run()
            self.assertEqual(status, SKIPPED)

            # simulate an update is required
            mock_provider.reset_mock()
            mock_provider.update_stack.side_effect = None
            step.set_status(PENDING)
            status = step.run()
            self.assertEqual(status, SUBMITTED)
            self.assertEqual(mock_provider.update_stack.call_count, 1)

    def test_should_update(self):
        test_scenario = namedtuple('test_scenario',
                                   ['locked', 'force', 'result'])
        test_scenarios = (
            test_scenario(locked=False, force=False, result=True),
            test_scenario(locked=False, force=True, result=True),
            test_scenario(locked=True, force=False, result=False),
            test_scenario(locked=True, force=True, result=True)
        )
        mock_stack = mock.MagicMock(["locked", "force", "name"])
        mock_stack.name = "test-stack"
        for t in test_scenarios:
            mock_stack.locked = t.locked
            mock_stack.force = t.force
            self.assertEqual(build.should_update(mock_stack), t.result)

    def test_should_submit(self):
        test_scenario = namedtuple('test_scenario',
                                   ['enabled', 'result'])
        test_scenarios = (
            test_scenario(enabled=False, result=False),
            test_scenario(enabled=True, result=True),
        )

        mock_stack = mock.MagicMock(["enabled", "name"])
        mock_stack.name = "test-stack"
        for t in test_scenarios:
            mock_stack.enabled = t.enabled
            self.assertEqual(build.should_submit(mock_stack), t.result)