示例#1
0
 def setUp(self):
     region = "us-east-1"
     self.session = get_session(region=region)
     self.provider = Provider(self.session,
                              interactive=True,
                              recreate_failed=True)
     self.stubber = Stubber(self.provider.cloudformation)
示例#2
0
    def setUp(self):
        self.context = self._get_context()
        self.provider = Provider(None, interactive=False,
                                 recreate_failed=False)
        self.build_action = build.Action(self.context, provider=self.provider)

        self.stack = mock.MagicMock()
        self.stack.name = 'vpc'
        self.stack.fqn = 'vpc'
        self.stack.locked = False
        self.stack_status = None

        plan = self.build_action._generate_plan()
        _, self.step = plan.list_pending()[0]
        self.step.stack = self.stack

        def patch_object(*args, **kwargs):
            m = mock.patch.object(*args, **kwargs)
            self.addCleanup(m.stop)
            m.start()

        def get_stack(name, *args, **kwargs):
            if name != self.stack.name or not self.stack_status:
                raise StackDoesNotExist(name)

            return {'StackName': self.stack.name,
                    'StackStatus': self.stack_status,
                    'Tags': []}

        patch_object(self.provider, 'get_stack', side_effect=get_stack)
        patch_object(self.provider, 'update_stack')
        patch_object(self.provider, 'create_stack')
        patch_object(self.provider, 'destroy_stack')

        patch_object(self.build_action, "s3_stack_push")
示例#3
0
 def _make_provider(self):
     provider = Provider(self.session,
                         interactive=False,
                         recreate_failed=False)
     self._patch_object(provider, 'get_stack', side_effect=self._get_stack)
     self._patch_object(provider, 'update_stack')
     self._patch_object(provider, 'create_stack')
     self._patch_object(provider, 'destroy_stack')
     return provider
示例#4
0
文件: test_base.py 项目: mgla/stacker
 def test_ensure_cfn_bucket_exists(self):
     provider = Provider("us-east-1")
     action = BaseAction(context=mock_context("mynamespace"),
                         provider=provider)
     stubber = Stubber(action.s3_conn)
     stubber.add_response("head_bucket",
                          service_response={},
                          expected_params={
                              "Bucket": ANY,
                          })
     with stubber:
         action.ensure_cfn_bucket()
示例#5
0
    def setUp(self):
        self.context = self._get_context()
        self.session = get_session(region=None)
        self.provider = Provider(self.session,
                                 interactive=False,
                                 recreate_failed=False)
        provider_builder = MockProviderBuilder(self.provider)
        self.build_action = build.Action(self.context,
                                         provider_builder=provider_builder,
                                         cancel=MockThreadingEvent())

        self.stack = mock.MagicMock()
        self.stack.region = None
        self.stack.name = 'vpc'
        self.stack.fqn = 'vpc'
        self.stack.blueprint.rendered = '{}'
        self.stack.locked = False
        self.stack_status = None

        plan = self.build_action._generate_plan()
        self.step = plan.steps[0]
        self.step.stack = self.stack

        def patch_object(*args, **kwargs):
            m = mock.patch.object(*args, **kwargs)
            self.addCleanup(m.stop)
            m.start()

        def get_stack(name, *args, **kwargs):
            if name != self.stack.name or not self.stack_status:
                raise StackDoesNotExist(name)

            return {
                'StackName': self.stack.name,
                'StackStatus': self.stack_status,
                'Outputs': [],
                'Tags': []
            }

        def get_events(name, *args, **kwargs):
            return [{
                'ResourceStatus': 'ROLLBACK_IN_PROGRESS',
                'ResourceStatusReason': 'CFN fail'
            }]

        patch_object(self.provider, 'get_stack', side_effect=get_stack)
        patch_object(self.provider, 'update_stack')
        patch_object(self.provider, 'create_stack')
        patch_object(self.provider, 'destroy_stack')
        patch_object(self.provider, 'get_events', side_effect=get_events)

        patch_object(self.build_action, "s3_stack_push")
示例#6
0
文件: test_base.py 项目: mgla/stacker
 def test_ensure_cfn_forbidden(self):
     provider = Provider("us-west-1")
     action = BaseAction(context=mock_context("mynamespace"),
                         provider=provider)
     stubber = Stubber(action.s3_conn)
     stubber.add_client_error(
         "head_bucket",
         service_error_code="AccessDenied",
         service_message="Forbidden",
         http_status_code=403,
     )
     with stubber:
         with self.assertRaises(botocore.exceptions.ClientError):
             action.ensure_cfn_bucket()
示例#7
0
    def test_stack_template_url(self):
        test_cases = (
            ("us-east-1", "s3.amazonaws.com"),
            ("us-west-1", "s3-us-west-1.amazonaws.com"),
            ("eu-west-1", "s3-eu-west-1.amazonaws.com"),
            ("sa-east-1", "s3-sa-east-1.amazonaws.com"),
        )
        context = mock_context("mynamespace")
        blueprint = TestBlueprint(name="myblueprint", context=context)

        for region, endpoint in test_cases:
            provider = Provider(region)
            action = BaseAction(context=context, provider=provider)
            self.assertEqual(
                action.stack_template_url(blueprint),
                "https://%s/%s/%s-%s.json" %
                (endpoint, "stacker-mynamespace", "myblueprint", MOCK_VERSION))
示例#8
0
文件: test_base.py 项目: mgla/stacker
 def test_ensure_cfn_bucket_doesnt_exist_us_east(self):
     provider = Provider("us-east-1")
     action = BaseAction(context=mock_context("mynamespace"),
                         provider=provider)
     stubber = Stubber(action.s3_conn)
     stubber.add_client_error(
         "head_bucket",
         service_error_code="NoSuchBucket",
         service_message="Not Found",
         http_status_code=404,
     )
     stubber.add_response("create_bucket",
                          service_response={},
                          expected_params={
                              "Bucket": ANY,
                          })
     with stubber:
         action.ensure_cfn_bucket()
示例#9
0
    def test_stack_template_url(self):
        context = mock_context("mynamespace")
        blueprint = TestBlueprint(name="myblueprint", context=context)

        region = "us-east-1"
        endpoint = "https://example.com"
        session = get_session(region)
        provider = Provider(session)
        action = BaseAction(context=context,
                            provider_builder=MockProviderBuilder(
                                provider, region=region))

        with mock.patch('stacker.actions.base.get_s3_endpoint',
                        autospec=True,
                        return_value=endpoint):
            self.assertEqual(
                action.stack_template_url(blueprint),
                "%s/%s/stack_templates/%s/%s-%s.json" %
                (endpoint, "stacker-mynamespace", "mynamespace-myblueprint",
                 "myblueprint", MOCK_VERSION))
示例#10
0
 def test_ensure_cfn_bucket_doesnt_exist_us_west(self):
     session = get_session("us-west-1")
     provider = Provider(session)
     action = BaseAction(context=mock_context("mynamespace"),
                         provider_builder=MockProviderBuilder(
                             provider, region="us-west-1"))
     stubber = Stubber(action.s3_conn)
     stubber.add_client_error(
         "head_bucket",
         service_error_code="NoSuchBucket",
         service_message="Not Found",
         http_status_code=404,
     )
     stubber.add_response("create_bucket",
                          service_response={},
                          expected_params={
                              "Bucket": ANY,
                              "CreateBucketConfiguration": {
                                  "LocationConstraint": "us-west-1",
                              }
                          })
     with stubber:
         action.ensure_cfn_bucket()
示例#11
0
 def test_successful_init(self):
     replacements = True
     p = Provider(self.session,
                  interactive=True,
                  replacements_only=replacements)
     self.assertEqual(p.replacements_only, replacements)
示例#12
0
class TestProviderInteractiveMode(unittest.TestCase):
    def setUp(self):
        region = "us-east-1"
        self.session = get_session(region=region)
        self.provider = Provider(self.session,
                                 interactive=True,
                                 recreate_failed=True)
        self.stubber = Stubber(self.provider.cloudformation)

    def test_successful_init(self):
        replacements = True
        p = Provider(self.session,
                     interactive=True,
                     replacements_only=replacements)
        self.assertEqual(p.replacements_only, replacements)

    @patch("stacker.providers.aws.default.ask_for_approval")
    def test_update_stack_execute_success_no_stack_policy(
            self, patched_approval):
        stack_name = "my-fake-stack"

        self.stubber.add_response("create_change_set", {
            'Id': 'CHANGESETID',
            'StackId': 'STACKID'
        })
        changes = []
        changes.append(generate_change())

        self.stubber.add_response(
            "describe_change_set",
            generate_change_set_response(
                status="CREATE_COMPLETE",
                execution_status="AVAILABLE",
                changes=changes,
            ))

        self.stubber.add_response("execute_change_set", {})

        with self.stubber:
            self.provider.update_stack(
                fqn=stack_name,
                template=Template(url="http://fake.template.url.com/"),
                old_parameters=[],
                parameters=[],
                tags=[])

        patched_approval.assert_called_with(full_changeset=changes,
                                            params_diff=[],
                                            include_verbose=True,
                                            fqn=stack_name)

        self.assertEqual(patched_approval.call_count, 1)

    @patch("stacker.providers.aws.default.ask_for_approval")
    def test_update_stack_execute_success_with_stack_policy(
            self, patched_approval):
        stack_name = "my-fake-stack"

        self.stubber.add_response("create_change_set", {
            'Id': 'CHANGESETID',
            'StackId': 'STACKID'
        })
        changes = []
        changes.append(generate_change())

        self.stubber.add_response(
            "describe_change_set",
            generate_change_set_response(
                status="CREATE_COMPLETE",
                execution_status="AVAILABLE",
                changes=changes,
            ))

        self.stubber.add_response("set_stack_policy", {})

        self.stubber.add_response("execute_change_set", {})

        with self.stubber:
            self.provider.update_stack(
                fqn=stack_name,
                template=Template(url="http://fake.template.url.com/"),
                old_parameters=[],
                parameters=[],
                tags=[],
                stack_policy=Template(body="{}"),
            )

        patched_approval.assert_called_with(full_changeset=changes,
                                            params_diff=[],
                                            include_verbose=True,
                                            fqn=stack_name)

        self.assertEqual(patched_approval.call_count, 1)

    def test_select_update_method(self):
        for i in [[{
                'force_interactive': False,
                'force_change_set': False
        }, self.provider.interactive_update_stack],
                  [{
                      'force_interactive': True,
                      'force_change_set': False
                  }, self.provider.interactive_update_stack],
                  [{
                      'force_interactive': False,
                      'force_change_set': True
                  }, self.provider.interactive_update_stack],
                  [{
                      'force_interactive': True,
                      'force_change_set': True
                  }, self.provider.interactive_update_stack]]:
            self.assertEquals(self.provider.select_update_method(**i[0]), i[1])

    @patch('stacker.providers.aws.default.output_full_changeset')
    @patch('stacker.providers.aws.default.output_summary')
    def test_get_stack_changes_interactive(self, mock_output_summary,
                                           mock_output_full_cs):
        stack_name = "MockStack"
        mock_stack = generate_stack_object(stack_name)

        self.stubber.add_response(
            'describe_stacks',
            {'Stacks': [generate_describe_stacks_stack(stack_name)]})
        self.stubber.add_response('get_template',
                                  generate_get_template('cfn_template.yaml'))
        self.stubber.add_response("create_change_set", {
            'Id': 'CHANGESETID',
            'StackId': stack_name
        })
        changes = []
        changes.append(generate_change())

        self.stubber.add_response(
            "describe_change_set",
            generate_change_set_response(
                status="CREATE_COMPLETE",
                execution_status="AVAILABLE",
                changes=changes,
            ))
        self.stubber.add_response("delete_change_set", {})
        self.stubber.add_response(
            'describe_stacks',
            {'Stacks': [generate_describe_stacks_stack(stack_name)]})

        with self.stubber:
            self.provider.get_stack_changes(
                stack=mock_stack,
                template=Template(url="http://fake.template.url.com/"),
                parameters=[],
                tags=[])

        mock_output_summary.assert_called_with(stack_name,
                                               'changes',
                                               changes, [],
                                               replacements_only=False)
        mock_output_full_cs.assert_called_with(full_changeset=changes,
                                               params_diff=[],
                                               fqn=stack_name)
示例#13
0
class TestProviderDefaultMode(unittest.TestCase):
    def setUp(self):
        region = "us-east-1"
        self.session = get_session(region=region)
        self.provider = Provider(self.session,
                                 region=region,
                                 recreate_failed=False)
        self.stubber = Stubber(self.provider.cloudformation)

    def test_get_stack_stack_does_not_exist(self):
        stack_name = "MockStack"
        self.stubber.add_client_error(
            "describe_stacks",
            service_error_code="ValidationError",
            service_message="Stack with id %s does not exist" % stack_name,
            expected_params={"StackName": stack_name})

        with self.assertRaises(exceptions.StackDoesNotExist):
            with self.stubber:
                self.provider.get_stack(stack_name)

    def test_get_stack_stack_exists(self):
        stack_name = "MockStack"
        stack_response = {
            "Stacks": [generate_describe_stacks_stack(stack_name)]
        }
        self.stubber.add_response("describe_stacks",
                                  stack_response,
                                  expected_params={"StackName": stack_name})

        with self.stubber:
            response = self.provider.get_stack(stack_name)

        self.assertEqual(response["StackName"], stack_name)

    def test_select_update_method(self):
        for i in [[{
                'force_interactive': True,
                'force_change_set': False
        }, self.provider.interactive_update_stack],
                  [{
                      'force_interactive': False,
                      'force_change_set': False
                  }, self.provider.default_update_stack],
                  [{
                      'force_interactive': False,
                      'force_change_set': True
                  }, self.provider.noninteractive_changeset_update],
                  [{
                      'force_interactive': True,
                      'force_change_set': True
                  }, self.provider.interactive_update_stack]]:
            self.assertEquals(self.provider.select_update_method(**i[0]), i[1])

    def test_prepare_stack_for_update_completed(self):
        stack_name = "MockStack"
        stack = generate_describe_stacks_stack(stack_name,
                                               stack_status="UPDATE_COMPLETE")

        with self.stubber:
            self.assertTrue(self.provider.prepare_stack_for_update(stack, []))

    def test_prepare_stack_for_update_in_progress(self):
        stack_name = "MockStack"
        stack = generate_describe_stacks_stack(
            stack_name, stack_status="UPDATE_IN_PROGRESS")

        with self.assertRaises(exceptions.StackUpdateBadStatus) as raised:
            with self.stubber:
                self.provider.prepare_stack_for_update(stack, [])

            self.assertIn('in-progress', str(raised.exception))

    def test_prepare_stack_for_update_non_recreatable(self):
        stack_name = "MockStack"
        stack = generate_describe_stacks_stack(
            stack_name, stack_status="REVIEW_IN_PROGRESS")

        with self.assertRaises(exceptions.StackUpdateBadStatus) as raised:
            with self.stubber:
                self.provider.prepare_stack_for_update(stack, [])

        self.assertIn('Unsupported state', str(raised.exception))

    def test_prepare_stack_for_update_disallowed(self):
        stack_name = "MockStack"
        stack = generate_describe_stacks_stack(
            stack_name, stack_status="ROLLBACK_COMPLETE")

        with self.assertRaises(exceptions.StackUpdateBadStatus) as raised:
            with self.stubber:
                self.provider.prepare_stack_for_update(stack, [])

        self.assertIn('re-creation is disabled', str(raised.exception))
        # Ensure we point out to the user how to enable re-creation
        self.assertIn('--recreate-failed', str(raised.exception))

    def test_prepare_stack_for_update_bad_tags(self):
        stack_name = "MockStack"
        stack = generate_describe_stacks_stack(
            stack_name, stack_status="ROLLBACK_COMPLETE")

        self.provider.recreate_failed = True

        with self.assertRaises(exceptions.StackUpdateBadStatus) as raised:
            with self.stubber:
                self.provider.prepare_stack_for_update(stack,
                                                       tags=[{
                                                           'Key':
                                                           'stacker_namespace',
                                                           'Value': 'test'
                                                       }])

        self.assertIn('tags differ', str(raised.exception).lower())

    def test_prepare_stack_for_update_recreate(self):
        stack_name = "MockStack"
        stack = generate_describe_stacks_stack(
            stack_name, stack_status="ROLLBACK_COMPLETE")

        self.stubber.add_response("delete_stack", {},
                                  expected_params={"StackName": stack_name})

        self.provider.recreate_failed = True

        with self.stubber:
            self.assertFalse(self.provider.prepare_stack_for_update(stack, []))

    def test_noninteractive_changeset_update_no_stack_policy(self):
        stack_name = "MockStack"

        self.stubber.add_response("create_change_set", {
            'Id': 'CHANGESETID',
            'StackId': 'STACKID'
        })
        changes = []
        changes.append(generate_change())

        self.stubber.add_response(
            "describe_change_set",
            generate_change_set_response(
                status="CREATE_COMPLETE",
                execution_status="AVAILABLE",
                changes=changes,
            ))

        self.stubber.add_response("execute_change_set", {})

        with self.stubber:
            self.provider.noninteractive_changeset_update(
                fqn=stack_name,
                template=Template(url="http://fake.template.url.com/"),
                old_parameters=[],
                parameters=[],
                stack_policy=None,
                tags=[],
            )

    def test_noninteractive_changeset_update_with_stack_policy(self):
        stack_name = "MockStack"

        self.stubber.add_response("create_change_set", {
            'Id': 'CHANGESETID',
            'StackId': 'STACKID'
        })
        changes = []
        changes.append(generate_change())

        self.stubber.add_response(
            "describe_change_set",
            generate_change_set_response(
                status="CREATE_COMPLETE",
                execution_status="AVAILABLE",
                changes=changes,
            ))

        self.stubber.add_response("set_stack_policy", {})

        self.stubber.add_response("execute_change_set", {})

        with self.stubber:
            self.provider.noninteractive_changeset_update(
                fqn=stack_name,
                template=Template(url="http://fake.template.url.com/"),
                old_parameters=[],
                parameters=[],
                stack_policy=Template(body="{}"),
                tags=[],
            )

    @patch('stacker.providers.aws.default.output_full_changeset')
    def test_get_stack_changes_update(self, mock_output_full_cs):
        stack_name = "MockStack"
        mock_stack = generate_stack_object(stack_name)

        self.stubber.add_response(
            'describe_stacks',
            {'Stacks': [generate_describe_stacks_stack(stack_name)]})
        self.stubber.add_response('get_template',
                                  generate_get_template('cfn_template.yaml'))
        self.stubber.add_response("create_change_set", {
            'Id': 'CHANGESETID',
            'StackId': stack_name
        })
        changes = []
        changes.append(generate_change())

        self.stubber.add_response(
            "describe_change_set",
            generate_change_set_response(
                status="CREATE_COMPLETE",
                execution_status="AVAILABLE",
                changes=changes,
            ))
        self.stubber.add_response("delete_change_set", {})
        self.stubber.add_response(
            'describe_stacks',
            {'Stacks': [generate_describe_stacks_stack(stack_name)]})

        with self.stubber:
            result = self.provider.get_stack_changes(
                stack=mock_stack,
                template=Template(url="http://fake.template.url.com/"),
                parameters=[],
                tags=[])

        mock_output_full_cs.assert_called_with(full_changeset=changes,
                                               params_diff=[],
                                               fqn=stack_name,
                                               answer='y')
        expected_outputs = {
            'FakeOutput':
            '<inferred-change: MockStack.FakeOutput={}>'.format(
                str({"Ref": "FakeResource"}))
        }
        self.assertEqual(self.provider.get_outputs(stack_name),
                         expected_outputs)
        self.assertEqual(result, expected_outputs)

    @patch('stacker.providers.aws.default.output_full_changeset')
    def test_get_stack_changes_create(self, mock_output_full_cs):
        stack_name = "MockStack"
        mock_stack = generate_stack_object(stack_name)

        self.stubber.add_response(
            'describe_stacks', {
                'Stacks': [
                    generate_describe_stacks_stack(
                        stack_name, stack_status='REVIEW_IN_PROGRESS')
                ]
            })
        self.stubber.add_response("create_change_set", {
            'Id': 'CHANGESETID',
            'StackId': stack_name
        })
        changes = []
        changes.append(generate_change())

        self.stubber.add_response(
            "describe_change_set",
            generate_change_set_response(
                status="CREATE_COMPLETE",
                execution_status="AVAILABLE",
                changes=changes,
            ))
        self.stubber.add_response("delete_change_set", {})
        self.stubber.add_response(
            'describe_stacks', {
                'Stacks': [
                    generate_describe_stacks_stack(
                        stack_name, stack_status='REVIEW_IN_PROGRESS')
                ]
            })
        self.stubber.add_response(
            'describe_stacks', {
                'Stacks': [
                    generate_describe_stacks_stack(
                        stack_name, stack_status='REVIEW_IN_PROGRESS')
                ]
            })

        self.stubber.add_response("delete_stack", {})

        with self.stubber:
            self.provider.get_stack_changes(
                stack=mock_stack,
                template=Template(url="http://fake.template.url.com/"),
                parameters=[],
                tags=[])

        mock_output_full_cs.assert_called_with(full_changeset=changes,
                                               params_diff=[],
                                               fqn=stack_name,
                                               answer='y')

    def test_tail_stack_retry_on_missing_stack(self):
        stack_name = "SlowToCreateStack"
        stack = MagicMock(spec=Stack)
        stack.fqn = "my-namespace-{}".format(stack_name)

        default.TAIL_RETRY_SLEEP = .01

        # Ensure the stack never appears before we run out of retries
        for i in range(MAX_TAIL_RETRIES + 5):
            self.stubber.add_client_error(
                "describe_stack_events",
                service_error_code="ValidationError",
                service_message="Stack [{}] does not exist".format(stack_name),
                http_status_code=400,
                response_meta={"attempt": i + 1},
            )

        with self.stubber:
            try:
                self.provider.tail_stack(stack, threading.Event())
            except ClientError as exc:
                self.assertEqual(exc.response["ResponseMetadata"]["attempt"],
                                 MAX_TAIL_RETRIES)

    def test_tail_stack_retry_on_missing_stack_eventual_success(self):
        stack_name = "SlowToCreateStack"
        stack = MagicMock(spec=Stack)
        stack.fqn = "my-namespace-{}".format(stack_name)

        default.TAIL_RETRY_SLEEP = .01
        default.GET_EVENTS_SLEEP = .01

        rcvd_events = []

        def mock_log_func(e):
            rcvd_events.append(e)

        def valid_event_response(stack, event_id):
            return {
                "StackEvents": [
                    {
                        "StackId": stack.fqn + "12345",
                        "EventId": event_id,
                        "StackName": stack.fqn,
                        "Timestamp": datetime.now()
                    },
                ]
            }

        # Ensure the stack never appears before we run out of retries
        for i in range(3):
            self.stubber.add_client_error(
                "describe_stack_events",
                service_error_code="ValidationError",
                service_message="Stack [{}] does not exist".format(stack_name),
                http_status_code=400,
                response_meta={"attempt": i + 1},
            )

        self.stubber.add_response("describe_stack_events",
                                  valid_event_response(stack, "InitialEvents"))

        self.stubber.add_response("describe_stack_events",
                                  valid_event_response(stack, "Event1"))

        with self.stubber:
            try:
                self.provider.tail_stack(stack,
                                         threading.Event(),
                                         log_func=mock_log_func)
            except UnStubbedResponseError:
                # Eventually we run out of responses - could not happen in
                # regular execution
                # normally this would just be dealt with when the threads were
                # shutdown, but doing so here is a little difficult because
                # we can't control the `tail_stack` loop
                pass

        self.assertEqual(rcvd_events[0]["EventId"], "Event1")
示例#14
0
 def setUp(self):
     region = "us-east-1"
     self.session = get_session(region=region)
     self.provider = Provider(
         self.session, interactive=True, recreate_failed=True)
     self.stubber = Stubber(self.provider.cloudformation)
示例#15
0
class TestProviderInteractiveMode(unittest.TestCase):
    def setUp(self):
        region = "us-east-1"
        self.session = get_session(region=region)
        self.provider = Provider(
            self.session, interactive=True, recreate_failed=True)
        self.stubber = Stubber(self.provider.cloudformation)

    def test_successful_init(self):
        replacements = True
        p = Provider(self.session, interactive=True,
                     replacements_only=replacements)
        self.assertEqual(p.replacements_only, replacements)

    @patch("stacker.providers.aws.default.ask_for_approval")
    def test_update_stack_execute_success_no_stack_policy(self,
                                                          patched_approval):
        stack_name = "my-fake-stack"

        self.stubber.add_response(
            "create_change_set",
            {'Id': 'CHANGESETID', 'StackId': 'STACKID'}
        )
        changes = []
        changes.append(generate_change())

        self.stubber.add_response(
            "describe_change_set",
            generate_change_set_response(
                status="CREATE_COMPLETE", execution_status="AVAILABLE",
                changes=changes,
            )
        )

        self.stubber.add_response("execute_change_set", {})

        with self.stubber:
            self.provider.update_stack(
                fqn=stack_name,
                template=Template(url="http://fake.template.url.com/"),
                old_parameters=[],
                parameters=[], tags=[]
            )

        patched_approval.assert_called_with(full_changeset=changes,
                                            params_diff=[],
                                            include_verbose=True)

        self.assertEqual(patched_approval.call_count, 1)

    @patch("stacker.providers.aws.default.ask_for_approval")
    def test_update_stack_execute_success_with_stack_policy(self,
                                                            patched_approval):
        stack_name = "my-fake-stack"

        self.stubber.add_response(
            "create_change_set",
            {'Id': 'CHANGESETID', 'StackId': 'STACKID'}
        )
        changes = []
        changes.append(generate_change())

        self.stubber.add_response(
            "describe_change_set",
            generate_change_set_response(
                status="CREATE_COMPLETE", execution_status="AVAILABLE",
                changes=changes,
            )
        )

        self.stubber.add_response("set_stack_policy", {})

        self.stubber.add_response("execute_change_set", {})

        with self.stubber:
            self.provider.update_stack(
                fqn=stack_name,
                template=Template(url="http://fake.template.url.com/"),
                old_parameters=[],
                parameters=[], tags=[],
                stack_policy=Template(body="{}"),
            )

        patched_approval.assert_called_with(full_changeset=changes,
                                            params_diff=[],
                                            include_verbose=True)

        self.assertEqual(patched_approval.call_count, 1)

    def test_select_update_method(self):
        for i in [[{'force_interactive': False,
                    'force_change_set': False},
                   self.provider.interactive_update_stack],
                  [{'force_interactive': True,
                    'force_change_set': False},
                   self.provider.interactive_update_stack],
                  [{'force_interactive': False,
                    'force_change_set': True},
                   self.provider.interactive_update_stack],
                  [{'force_interactive': True,
                    'force_change_set': True},
                   self.provider.interactive_update_stack]]:
            self.assertEquals(
                self.provider.select_update_method(**i[0]),
                i[1]
            )
示例#16
0
class TestProviderDefaultMode(unittest.TestCase):
    def setUp(self):
        region = "us-east-1"
        self.session = get_session(region=region)
        self.provider = Provider(
            self.session, region=region, recreate_failed=False)
        self.stubber = Stubber(self.provider.cloudformation)

    def test_get_stack_stack_does_not_exist(self):
        stack_name = "MockStack"
        self.stubber.add_client_error(
            "describe_stacks",
            service_error_code="ValidationError",
            service_message="Stack with id %s does not exist" % stack_name,
            expected_params={"StackName": stack_name}
        )

        with self.assertRaises(exceptions.StackDoesNotExist):
            with self.stubber:
                self.provider.get_stack(stack_name)

    def test_get_stack_stack_exists(self):
        stack_name = "MockStack"
        stack_response = {
            "Stacks": [generate_describe_stacks_stack(stack_name)]
        }
        self.stubber.add_response(
            "describe_stacks",
            stack_response,
            expected_params={"StackName": stack_name}
        )

        with self.stubber:
            response = self.provider.get_stack(stack_name)

        self.assertEqual(response["StackName"], stack_name)

    def test_select_update_method(self):
        for i in [[{'force_interactive': True,
                    'force_change_set': False},
                   self.provider.interactive_update_stack],
                  [{'force_interactive': False,
                    'force_change_set': False},
                   self.provider.default_update_stack],
                  [{'force_interactive': False,
                    'force_change_set': True},
                   self.provider.noninteractive_changeset_update],
                  [{'force_interactive': True,
                    'force_change_set': True},
                   self.provider.interactive_update_stack]]:
            self.assertEquals(
                self.provider.select_update_method(**i[0]),
                i[1]
            )

    def test_prepare_stack_for_update_completed(self):
        stack_name = "MockStack"
        stack = generate_describe_stacks_stack(
            stack_name, stack_status="UPDATE_COMPLETE")

        with self.stubber:
            self.assertTrue(
                self.provider.prepare_stack_for_update(stack, []))

    def test_prepare_stack_for_update_in_progress(self):
        stack_name = "MockStack"
        stack = generate_describe_stacks_stack(
            stack_name, stack_status="UPDATE_IN_PROGRESS")

        with self.assertRaises(exceptions.StackUpdateBadStatus) as raised:
            with self.stubber:
                self.provider.prepare_stack_for_update(stack, [])

            self.assertIn('in-progress', str(raised.exception))

    def test_prepare_stack_for_update_non_recreatable(self):
        stack_name = "MockStack"
        stack = generate_describe_stacks_stack(
            stack_name, stack_status="REVIEW_IN_PROGRESS")

        with self.assertRaises(exceptions.StackUpdateBadStatus) as raised:
            with self.stubber:
                self.provider.prepare_stack_for_update(stack, [])

        self.assertIn('Unsupported state', str(raised.exception))

    def test_prepare_stack_for_update_disallowed(self):
        stack_name = "MockStack"
        stack = generate_describe_stacks_stack(
            stack_name, stack_status="ROLLBACK_COMPLETE")

        with self.assertRaises(exceptions.StackUpdateBadStatus) as raised:
            with self.stubber:
                self.provider.prepare_stack_for_update(stack, [])

        self.assertIn('re-creation is disabled', str(raised.exception))
        # Ensure we point out to the user how to enable re-creation
        self.assertIn('--recreate-failed', str(raised.exception))

    def test_prepare_stack_for_update_bad_tags(self):
        stack_name = "MockStack"
        stack = generate_describe_stacks_stack(
            stack_name, stack_status="ROLLBACK_COMPLETE")

        self.provider.recreate_failed = True

        with self.assertRaises(exceptions.StackUpdateBadStatus) as raised:
            with self.stubber:
                self.provider.prepare_stack_for_update(
                    stack,
                    tags=[{'Key': 'stacker_namespace', 'Value': 'test'}])

        self.assertIn('tags differ', str(raised.exception).lower())

    def test_prepare_stack_for_update_recreate(self):
        stack_name = "MockStack"
        stack = generate_describe_stacks_stack(
            stack_name, stack_status="ROLLBACK_COMPLETE")

        self.stubber.add_response(
            "delete_stack",
            {},
            expected_params={"StackName": stack_name}
        )

        self.provider.recreate_failed = True

        with self.stubber:
            self.assertFalse(
                self.provider.prepare_stack_for_update(stack, []))

    def test_noninteractive_changeset_update_no_stack_policy(self):
        stack_name = "MockStack"

        self.stubber.add_response(
            "create_change_set",
            {'Id': 'CHANGESETID', 'StackId': 'STACKID'}
        )
        changes = []
        changes.append(generate_change())

        self.stubber.add_response(
            "describe_change_set",
            generate_change_set_response(
                status="CREATE_COMPLETE", execution_status="AVAILABLE",
                changes=changes,
            )
        )

        self.stubber.add_response("execute_change_set", {})

        with self.stubber:
            self.provider.noninteractive_changeset_update(
                fqn=stack_name,
                template=Template(url="http://fake.template.url.com/"),
                old_parameters=[],
                parameters=[], stack_policy=None, tags=[],
            )

    def test_noninteractive_changeset_update_with_stack_policy(self):
        stack_name = "MockStack"

        self.stubber.add_response(
            "create_change_set",
            {'Id': 'CHANGESETID', 'StackId': 'STACKID'}
        )
        changes = []
        changes.append(generate_change())

        self.stubber.add_response(
            "describe_change_set",
            generate_change_set_response(
                status="CREATE_COMPLETE", execution_status="AVAILABLE",
                changes=changes,
            )
        )

        self.stubber.add_response("set_stack_policy", {})

        self.stubber.add_response("execute_change_set", {})

        with self.stubber:
            self.provider.noninteractive_changeset_update(
                fqn=stack_name,
                template=Template(url="http://fake.template.url.com/"),
                old_parameters=[],
                parameters=[], stack_policy=Template(body="{}"), tags=[],
            )

    def test_tail_stack_retry_on_missing_stack(self):
        stack_name = "SlowToCreateStack"
        stack = MagicMock(spec=Stack)
        stack.fqn = "my-namespace-{}".format(stack_name)

        default.TAIL_RETRY_SLEEP = .01

        # Ensure the stack never appears before we run out of retries
        for i in range(MAX_TAIL_RETRIES + 5):
            self.stubber.add_client_error(
                "describe_stack_events",
                service_error_code="ValidationError",
                service_message="Stack [{}] does not exist".format(stack_name),
                http_status_code=400,
                response_meta={"attempt": i + 1},
            )

        with self.stubber:
            try:
                self.provider.tail_stack(stack, threading.Event())
            except ClientError as exc:
                self.assertEqual(
                    exc.response["ResponseMetadata"]["attempt"],
                    MAX_TAIL_RETRIES
                )

    def test_tail_stack_retry_on_missing_stack_eventual_success(self):
        stack_name = "SlowToCreateStack"
        stack = MagicMock(spec=Stack)
        stack.fqn = "my-namespace-{}".format(stack_name)

        default.TAIL_RETRY_SLEEP = .01
        default.GET_EVENTS_SLEEP = .01

        rcvd_events = []

        def mock_log_func(e):
            rcvd_events.append(e)

        def valid_event_response(stack, event_id):
            return {
                "StackEvents": [
                    {
                        "StackId": stack.fqn + "12345",
                        "EventId": event_id,
                        "StackName": stack.fqn,
                        "Timestamp": datetime.now()
                    },
                ]
            }

        # Ensure the stack never appears before we run out of retries
        for i in range(3):
            self.stubber.add_client_error(
                "describe_stack_events",
                service_error_code="ValidationError",
                service_message="Stack [{}] does not exist".format(stack_name),
                http_status_code=400,
                response_meta={"attempt": i + 1},
            )

        self.stubber.add_response(
            "describe_stack_events",
            valid_event_response(stack, "InitialEvents")
        )

        self.stubber.add_response(
            "describe_stack_events",
            valid_event_response(stack, "Event1")
        )

        with self.stubber:
            try:
                self.provider.tail_stack(stack, threading.Event(),
                                         log_func=mock_log_func)
            except UnStubbedResponseError:
                # Eventually we run out of responses - could not happen in
                # regular execution
                # normally this would just be dealt with when the threads were
                # shutdown, but doing so here is a little difficult because
                # we can't control the `tail_stack` loop
                pass

        self.assertEqual(rcvd_events[0]["EventId"], "Event1")