Exemple #1
0
    def test_execute_plan_locked(self):
        """Test execute plan locked.

        Locked stacks still need to have their requires evaluated when
        they're being created.

        """
        vpc = Stack(definition=generate_definition("vpc", 1),
                    context=self.context)
        bastion = Stack(
            definition=generate_definition("bastion", 1, requires=[vpc.name]),
            locked=True,
            context=self.context,
        )

        calls = []

        def fn(stack, status=None):
            calls.append(stack.fqn)
            return COMPLETE

        graph = Graph.from_steps([Step(vpc, fn), Step(bastion, fn)])
        plan = Plan(description="Test", graph=graph)
        plan.execute(walk)

        self.assertEqual(calls, ["namespace-vpc.1", "namespace-bastion.1"])
Exemple #2
0
    def test_execute_plan_filtered(self):
        """Test execute plan filtered."""
        vpc = Stack(definition=generate_definition("vpc", 1),
                    context=self.context)
        db = Stack(
            definition=generate_definition("db", 1, requires=[vpc.name]),
            context=self.context,
        )
        app = Stack(
            definition=generate_definition("app", 1, requires=[db.name]),
            context=self.context,
        )

        calls = []

        def fn(stack, status=None):
            calls.append(stack.fqn)
            return COMPLETE

        context = mock.MagicMock()
        context.persistent_graph_locked = False
        context.stack_names = ["db.1"]
        graph = Graph.from_steps([Step(vpc, fn), Step(db, fn), Step(app, fn)])
        plan = Plan(context=context, description="Test", graph=graph)
        plan.execute(walk)

        self.assertEqual(calls, ["namespace-vpc.1", "namespace-db.1"])
Exemple #3
0
    def test_execute_plan_failed(self):
        """Test execute plan failed."""
        vpc = Stack(definition=generate_definition("vpc", 1),
                    context=self.context)
        bastion = Stack(
            definition=generate_definition("bastion", 1, requires=[vpc.name]),
            context=self.context,
        )
        db = Stack(definition=generate_definition("db", 1),
                   context=self.context)

        calls = []

        def fn(stack, status=None):
            calls.append(stack.fqn)
            if stack.name == vpc_step.name:
                return FAILED
            return COMPLETE

        vpc_step = Step(vpc, fn)
        bastion_step = Step(bastion, fn)
        db_step = Step(db, fn)

        graph = Graph.from_steps([vpc_step, bastion_step, db_step])
        plan = Plan(description="Test", graph=graph)
        with self.assertRaises(PlanFailed):
            plan.execute(walk)

        calls.sort()

        self.assertEqual(calls, ["namespace-db.1", "namespace-vpc.1"])
Exemple #4
0
    def test_execute_plan_exception(self):
        """Test execute plan exception."""
        vpc = Stack(definition=generate_definition("vpc", 1),
                    context=self.context)
        bastion = Stack(
            definition=generate_definition("bastion", 1, requires=[vpc.name]),
            context=self.context,
        )

        calls = []

        def fn(stack, status=None):
            calls.append(stack.fqn)
            if stack.name == vpc_step.name:
                raise ValueError("Boom")
            return COMPLETE

        vpc_step = Step(vpc, fn)
        bastion_step = Step(bastion, fn)

        graph = Graph.from_steps([vpc_step, bastion_step])
        plan = Plan(description="Test", graph=graph)

        with self.assertRaises(PlanFailed):
            plan.execute(walk)

        self.assertEqual(calls, ["namespace-vpc.1"])
        self.assertEqual(vpc_step.status, FAILED)
Exemple #5
0
    def test_execute_plan_cancelled(self):
        """Test execute plan cancelled."""
        vpc = Stack(definition=generate_definition("vpc", 1),
                    context=self.context)
        bastion = Stack(
            definition=generate_definition("bastion", 1, requires=[vpc.name]),
            context=self.context,
        )

        calls = []

        def fn(stack, status=None):
            calls.append(stack.fqn)
            if stack.fqn == vpc_step.name:
                raise CancelExecution
            return COMPLETE

        vpc_step = Step(vpc, fn)
        bastion_step = Step(bastion, fn)

        graph = Graph.from_steps([vpc_step, bastion_step])
        plan = Plan(description="Test", graph=graph)
        plan.execute(walk)

        self.assertEqual(calls, ["namespace-vpc.1", "namespace-bastion.1"])
Exemple #6
0
    def test_execute_plan_skipped(self):
        """Test execute plan skipped."""
        vpc = Stack(definition=generate_definition('vpc', 1),
                    context=self.context)
        bastion = Stack(definition=generate_definition('bastion',
                                                       1,
                                                       requires=[vpc.name]),
                        context=self.context)

        calls = []

        def fn(stack, status=None):
            calls.append(stack.fqn)
            if stack.fqn == vpc_step.name:
                return SKIPPED
            return COMPLETE

        vpc_step = Step(vpc, fn)
        bastion_step = Step(bastion, fn)

        graph = Graph.from_steps([vpc_step, bastion_step])
        plan = Plan(description="Test", graph=graph)
        plan.execute(walk)

        self.assertEqual(calls, ['namespace-vpc.1', 'namespace-bastion.1'])
Exemple #7
0
    def test_execute_plan_no_persist(self):
        """Test execute plan with no persistent graph."""
        context = Context(config=self.config)
        context.put_persistent_graph = mock.MagicMock()
        vpc = Stack(definition=generate_definition("vpc", 1), context=context)
        bastion = Stack(
            definition=generate_definition("bastion", 1, requires=[vpc.name]),
            context=context,
        )

        calls = []

        def _launch_stack(stack, status=None):
            calls.append(stack.fqn)
            return COMPLETE

        graph = Graph.from_steps(
            [Step(vpc, _launch_stack),
             Step(bastion, _launch_stack)])
        plan = Plan(description="Test", graph=graph, context=context)

        plan.execute(walk)

        self.assertEqual(calls, ["namespace-vpc.1", "namespace-bastion.1"])
        context.put_persistent_graph.assert_not_called()
Exemple #8
0
 def test_output_handler(self) -> None:
     """Test output handler."""
     stack = Stack(definition=generate_definition("vpc", 1), context=self.context)
     stack.set_outputs({"SomeOutput": "Test Output"})
     self.context.get_stack.return_value = stack
     value = OutputLookup.handle("stack-name::SomeOutput", context=self.context)
     self.assertEqual(value, "Test Output")
     self.assertEqual(self.context.get_stack.call_count, 1)
     args = self.context.get_stack.call_args
     self.assertEqual(args[0][0], "stack-name")
Exemple #9
0
    def test_plan_targeted(self):
        """Test plan targeted."""
        context = Context(config=self.config)
        vpc = Stack(definition=generate_definition("vpc", 1), context=context)
        bastion = Stack(
            definition=generate_definition("bastion", 1, requires=[vpc.name]),
            context=context,
        )
        context.stack_names = [vpc.name]
        graph = Graph.from_steps([Step(vpc, fn=None), Step(bastion, fn=None)])
        plan = Plan(description="Test", graph=graph, context=context)

        self.assertEqual({vpc.name: set()}, plan.graph.to_dict())
Exemple #10
0
    def test_plan_reverse(self) -> None:
        """Test plan reverse."""
        vpc = Stack(definition=generate_definition("vpc", 1), context=self.context)
        bastion = Stack(
            definition=generate_definition("bastion", 1, requires=[vpc.name]),
            context=self.context,
        )
        graph = Graph.from_steps([Step(vpc, fn=None), Step(bastion, fn=None)])
        plan = Plan(description="Test", graph=graph, reverse=True)

        # order is different between python2/3 so can't compare dicts
        result_graph_dict = plan.graph.to_dict()
        self.assertEqual(set(), result_graph_dict.get("bastion.1"))
        self.assertEqual(set(["bastion.1"]), result_graph_dict.get("vpc.1"))
Exemple #11
0
    def test_plan(self) -> None:
        """Test plan."""
        vpc = Stack(definition=generate_definition("vpc", 1), context=self.context)
        bastion = Stack(
            definition=generate_definition("bastion", 1, requires=[vpc.name]),
            context=self.context,
        )

        graph = Graph.from_steps([Step(vpc, fn=None), Step(bastion, fn=None)])
        plan = Plan(description="Test", graph=graph)

        self.assertEqual(
            plan.graph.to_dict(), {"bastion.1": set(["vpc.1"]), "vpc.1": set([])}
        )
Exemple #12
0
    def test_variable_resolve_simple_lookup(self):
        """Test variable resolve simple lookup."""
        stack = Stack(definition=generate_definition("vpc", 1),
                      context=self.context)
        stack.set_outputs({
            "FakeOutput": "resolved",
            "FakeOutput2": "resolved2",
        })

        self.context.get_stack.return_value = stack

        var = Variable("Param1", "${output fakeStack::FakeOutput}")
        var.resolve(self.context, self.provider)
        self.assertTrue(var.resolved)
        self.assertEqual(var.value, "resolved")
Exemple #13
0
 def test_stack_cfn_parameters(self) -> None:
     """Test stack cfn parameters."""
     definition = generate_definition(
         base_name="vpc",
         stack_id=1,
         variables={"Param1": "${output fakeStack::FakeOutput}"},
     )
     stack = Stack(definition=definition, context=self.context)
     # pylint: disable=protected-access
     stack._blueprint = MagicMock()
     stack._blueprint.parameter_values = {
         "Param2": "Some Resolved Value",
     }
     param = stack.parameter_values["Param2"]
     self.assertEqual(param, "Some Resolved Value")
Exemple #14
0
    def test_plan(self):
        """Test plan."""
        vpc = Stack(definition=generate_definition('vpc', 1),
                    context=self.context)
        bastion = Stack(definition=generate_definition('bastion',
                                                       1,
                                                       requires=[vpc.name]),
                        context=self.context)

        graph = Graph.from_steps([Step(vpc, fn=None), Step(bastion, fn=None)])
        plan = Plan(description="Test", graph=graph)

        self.assertEqual(plan.graph.to_dict(), {
            'bastion.1': set(['vpc.1']),
            'vpc.1': set([])
        })
Exemple #15
0
    def from_stack_name(cls,
                        stack_name,
                        context,
                        requires=None,
                        fn=None,
                        watch_func=None):
        """Create a step using only a stack name.

        Args:
            stack_name (str): Name of a CloudFormation stack.
            context (:class:`runway.cfngin.context.Context`): Context object.
                Required to initialize a "fake" :class:`runway.cfngin.stack.Stack`.
            requires (List[str]): Stacks that this stack depends on.
            fn (Callable): The function to run to execute the step.
                This function will be ran multiple times until the step
                is "done".
            watch_func (Callable): an optional function that will be
                called to "tail" the step action.

        Returns:
            :class:`Step`

        """
        # pylint: disable=import-outside-toplevel
        from runway.cfngin.config import Stack as StackConfig
        from runway.cfngin.stack import Stack

        stack_def = StackConfig({
            'name': stack_name,
            'requires': requires or []
        })
        stack = Stack(stack_def, context)
        return cls(stack, fn=fn, watch_func=watch_func)
Exemple #16
0
 def test_stack_requires(self):
     """Test stack requires."""
     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=["fakeStack"],
     )
     stack = Stack(definition=definition, context=self.context)
     self.assertEqual(len(stack.requires), 2)
     self.assertIn(
         "fakeStack",
         stack.requires,
     )
     self.assertIn(
         "fakeStack2",
         stack.requires,
     )
Exemple #17
0
    def test_execute_plan(self) -> None:
        """Test execute plan."""
        context = CfnginContext(config=self.config)
        context.put_persistent_graph = mock.MagicMock()
        vpc = Stack(definition=generate_definition("vpc", 1), context=context)
        bastion = Stack(
            definition=generate_definition("bastion", 1, requires=[vpc.name]),
            context=context,
        )
        removed = Stack(
            definition=generate_definition("removed", 1, requires=[]), context=context
        )
        context._persistent_graph = Graph.from_steps([Step(removed)])

        calls: List[str] = []

        def _launch_stack(stack: Stack, status: Optional[Status] = None) -> Status:
            calls.append(stack.fqn)
            return COMPLETE

        def _destroy_stack(stack: Stack, status: Optional[Status] = None) -> Status:
            calls.append(stack.fqn)
            return COMPLETE

        graph = Graph.from_steps(
            [
                Step(removed, fn=_destroy_stack),
                Step(vpc, fn=_launch_stack),
                Step(bastion, fn=_launch_stack),
            ]
        )
        plan = Plan(description="Test", graph=graph, context=context)
        plan.context._persistent_graph_lock_code = plan.lock_code  # type: ignore
        plan.execute(walk)

        # the order these are appended changes between python2/3
        self.assertIn("namespace-vpc.1", calls)
        self.assertIn("namespace-bastion.1", calls)
        self.assertIn("namespace-removed.1", calls)
        context.put_persistent_graph.assert_called()

        # order is different between python2/3 so can't compare dicts
        result_graph_dict = context.persistent_graph.to_dict()  # type: ignore
        self.assertEqual(2, len(result_graph_dict))
        self.assertEqual(set(), result_graph_dict.get("vpc.1"))
        self.assertEqual(set(["vpc.1"]), result_graph_dict.get("bastion.1"))
        self.assertIsNone(result_graph_dict.get("namespace-removed.1"))
Exemple #18
0
 def test_stack_tags_extra(self):
     """Test stack tags extra."""
     self.config.tags = {"environment": "prod"}
     definition = generate_definition(base_name="vpc",
                                      stack_id=1,
                                      tags={"app": "graph"})
     stack = Stack(definition=definition, context=self.context)
     self.assertEqual(stack.tags, {"environment": "prod", "app": "graph"})
Exemple #19
0
 def test_stack_tags_override(self):
     """Test stack tags override."""
     self.config.tags = {"environment": "prod"}
     definition = generate_definition(base_name="vpc",
                                      stack_id=1,
                                      tags={"environment": "stage"})
     stack = Stack(definition=definition, context=self.context)
     self.assertEqual(stack.tags, {"environment": "stage"})
Exemple #20
0
    def test_execute_plan(self):
        """Test execute plan."""
        context = Context(config=self.config)
        context.put_persistent_graph = mock.MagicMock()
        vpc = Stack(definition=generate_definition('vpc', 1), context=context)
        bastion = Stack(definition=generate_definition('bastion',
                                                       1,
                                                       requires=[vpc.name]),
                        context=context)
        removed = Stack(definition=generate_definition('removed',
                                                       1,
                                                       requires=[]),
                        context=context)
        context._persistent_graph = Graph.from_steps([removed])

        calls = []

        def _launch_stack(stack, status=None):
            calls.append(stack.fqn)
            return COMPLETE

        def _destroy_stack(stack, status=None):
            calls.append(stack.fqn)
            return COMPLETE

        graph = Graph.from_steps([
            Step(removed, _destroy_stack),
            Step(vpc, _launch_stack),
            Step(bastion, _launch_stack)
        ])
        plan = Plan(description="Test", graph=graph, context=context)
        plan.context._persistent_graph_lock_code = plan.lock_code
        plan.execute(walk)

        # the order these are appended changes between python2/3
        self.assertIn('namespace-vpc.1', calls)
        self.assertIn('namespace-bastion.1', calls)
        self.assertIn('namespace-removed.1', calls)
        context.put_persistent_graph.assert_called()

        # order is different between python2/3 so can't compare dicts
        result_graph_dict = context.persistent_graph.to_dict()
        self.assertEqual(2, len(result_graph_dict))
        self.assertEqual(set(), result_graph_dict.get('vpc.1'))
        self.assertEqual(set(['vpc.1']), result_graph_dict.get('bastion.1'))
        self.assertIsNone(result_graph_dict.get('namespace-removed.1'))
Exemple #21
0
 def test_stack_requires_circular_ref(self):
     """Test stack requires circular ref."""
     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  # pylint: disable=pointless-statement
Exemple #22
0
 def setUp(self):
     """Run before tests."""
     self.sd = {"name": "test"}  # pylint: disable=invalid-name
     self.config = Config({"namespace": "namespace"})
     self.context = Context(config=self.config)
     self.stack = Stack(
         definition=generate_definition("vpc", 1),
         context=self.context,
     )
     register_lookup_handler("noop", lambda **kwargs: "test")
Exemple #23
0
    def test_build_graph_cyclic_dependencies(self):
        """Test build graph cyclic dependencies."""
        vpc = Stack(definition=generate_definition('vpc', 1),
                    context=self.context)
        db = Stack(definition=generate_definition('db', 1, requires=['app.1']),
                   context=self.context)
        app = Stack(definition=generate_definition('app', 1,
                                                   requires=['db.1']),
                    context=self.context)

        with self.assertRaises(GraphError) as expected:
            Graph.from_steps(
                [Step(vpc, None),
                 Step(db, None),
                 Step(app, None)])
        message = ("Error detected when adding 'db.1' "
                   "as a dependency of 'app.1': graph is "
                   "not acyclic")
        self.assertEqual(str(expected.exception), message)
Exemple #24
0
    def test_variable_resolve_multiple_lookups_string(self):
        """Test variable resolve multiple lookups string."""
        var = Variable(
            "Param1",
            "url://${output fakeStack::FakeOutput}@"
            "${output fakeStack::FakeOutput2}",
        )

        stack = Stack(definition=generate_definition("vpc", 1),
                      context=self.context)
        stack.set_outputs({
            "FakeOutput": "resolved",
            "FakeOutput2": "resolved2",
        })

        self.context.get_stack.return_value = stack
        var.resolve(self.context, self.provider)
        self.assertTrue(var.resolved)
        self.assertEqual(var.value, "url://resolved@resolved2")
Exemple #25
0
    def test_variable_resolve_nested_lookup(self):
        """Test variable resolve nested lookup."""
        stack = Stack(definition=generate_definition("vpc", 1),
                      context=self.context)
        stack.set_outputs({
            "FakeOutput": "resolved",
            "FakeOutput2": "resolved2",
        })

        def mock_handler(value, context, provider, **kwargs):
            return "looked up: {}".format(value)

        register_lookup_handler("lookup", mock_handler)
        self.context.get_stack.return_value = stack
        var = Variable(
            "Param1",
            "${lookup ${lookup ${output fakeStack::FakeOutput}}}",
        )
        var.resolve(self.context, self.provider)
        self.assertTrue(var.resolved)
        self.assertEqual(var.value, "looked up: looked up: resolved")
Exemple #26
0
    def test_build_graph_missing_dependency(self):
        """Test build graph missing dependency."""
        bastion = Stack(definition=generate_definition('bastion',
                                                       1,
                                                       requires=['vpc.1']),
                        context=self.context)

        with self.assertRaises(GraphError) as expected:
            Graph.from_steps([Step(bastion, None)])
        message_starts = ("Error detected when adding 'vpc.1' "
                          "as a dependency of 'bastion.1':")
        message_contains = "dependent node vpc.1 does not exist"
        self.assertTrue(str(expected.exception).startswith(message_starts))
        self.assertTrue(message_contains in str(expected.exception))
Exemple #27
0
    def test_execute_plan_skipped(self) -> None:
        """Test execute plan skipped."""
        vpc = Stack(definition=generate_definition("vpc", 1), context=self.context)
        bastion = Stack(
            definition=generate_definition("bastion", 1, requires=[vpc.name]),
            context=self.context,
        )

        calls: List[str] = []

        def fn(stack: Stack, status: Optional[Status] = None) -> Status:
            calls.append(stack.fqn)
            if stack.fqn == vpc_step.name:
                return SKIPPED
            return COMPLETE

        vpc_step = Step(vpc, fn=fn)
        bastion_step = Step(bastion, fn=fn)

        graph = Graph.from_steps([vpc_step, bastion_step])
        plan = Plan(description="Test", graph=graph)
        plan.execute(walk)

        self.assertEqual(calls, ["namespace-vpc.1", "namespace-bastion.1"])
Exemple #28
0
    def setUp(self) -> None:
        """Run before tests."""
        self.sd = {"name": "test"}  # pylint: disable=invalid-name
        self.config = CfnginConfig.parse_obj({"namespace": "namespace"})
        self.context = CfnginContext(config=self.config)
        self.stack = Stack(definition=generate_definition("vpc", 1),
                           context=self.context)

        class FakeLookup(LookupHandler):
            """False Lookup."""

            # pylint: disable=arguments-differ,unused-argument
            @classmethod
            def handle(cls, value: str, *__args: Any,
                       **__kwargs: Any) -> str:  # type: ignore
                """Perform the lookup."""
                return "test"

        register_lookup_handler("noop", FakeLookup)
Exemple #29
0
    def test_dump(self) -> None:
        """Test dump."""
        requires: List[str] = []
        steps: List[Step] = []

        for i in range(5):
            overrides = {
                "variables": {
                    "PublicSubnets": "1",
                    "SshKeyName": "1",
                    "PrivateSubnets": "1",
                    "Random": "${noop something}",
                },
                "requires": requires,
            }

            stack = Stack(
                definition=generate_definition("vpc", i, **overrides),
                context=self.context,
            )
            requires = [stack.name]

            steps += [Step(stack)]

        graph = Graph.from_steps(steps)
        plan = Plan(description="Test", graph=graph)

        tmp_dir = tempfile.mkdtemp()
        try:
            plan.dump(directory=tmp_dir, context=self.context)

            for step in plan.steps:
                template_path = os.path.join(
                    tmp_dir, stack_template_key_name(step.stack.blueprint)  # type: ignore
                )
                self.assertTrue(os.path.isfile(template_path))
        finally:
            shutil.rmtree(tmp_dir)
Exemple #30
0
 def test_stack_tags_default(self) -> None:
     """Test stack tags default."""
     self.config.tags = {"environment": "prod"}
     definition = generate_definition(base_name="vpc", stack_id=1)
     stack = Stack(definition=definition, context=self.context)
     self.assertEqual(stack.tags, {"environment": "prod"})