示例#1
0
    def setUp(self):
        client = botocore.session.get_session().create_client(
            'cloudformation', region_name="us-east-1")
        self.stub_client = Stubber(client)

        self.changeset_prefix = "some-changeset-prefix"
        self.deployer = Deployer(client, self.changeset_prefix)
示例#2
0
    def test_wait_for_changeset_failed_to_create_changeset(self):
        stack_name = "stack_name"
        changeset_id = "changeset-id"

        mock_client = mock.Mock()
        mock_deployer = Deployer(mock_client)
        mock_waiter = mock.Mock()
        mock_client.get_waiter.return_value = mock_waiter

        response = {"Status": "FAILED", "StatusReason": "some reason"}

        waiter_error = botocore.exceptions.WaiterError(name="name",
                                                       reason="reason",
                                                       last_response=response)
        mock_waiter.wait.side_effect = waiter_error

        with self.assertRaises(RuntimeError):
            mock_deployer.wait_for_changeset(changeset_id, stack_name)

        waiter_config = {'Delay': 5}
        mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id,
                                                 StackName=stack_name,
                                                 WaiterConfig=waiter_config)

        mock_client.get_waiter.assert_called_once_with(
            "change_set_create_complete")
示例#3
0
    def test_wait_for_changeset_failed_to_create_changeset(self):
        stack_name = "stack_name"
        changeset_id = "changeset-id"

        mock_client = Mock()
        mock_deployer = Deployer(mock_client)
        mock_waiter = Mock()
        mock_client.get_waiter.return_value = mock_waiter

        response = {
            "Status": "FAILED",
            "StatusReason": "some reason"
        }

        waiter_error = botocore.exceptions.WaiterError(name="name",
                                                       reason="reason",
                                                       last_response=response)
        mock_waiter.wait.side_effect = waiter_error

        with self.assertRaises(RuntimeError):
            mock_deployer.wait_for_changeset(changeset_id, stack_name)

        mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id,
                                            StackName=stack_name)

        mock_client.get_waiter.assert_called_once_with(
                "change_set_create_complete")
示例#4
0
    def test_wait_for_changeset_no_changes(self):
        stack_name = "stack_name"
        changeset_id = "changeset-id"

        mock_client = Mock()
        mock_deployer = Deployer(mock_client)
        mock_waiter = Mock()
        mock_client.get_waiter.return_value = mock_waiter

        response = {
            "Status": "FAILED",
            "StatusReason": "No updates are to be performed"
        }

        waiter_error = botocore.exceptions.WaiterError(name="name",
                                                       reason="reason",
                                                       last_response=response)
        mock_waiter.wait.side_effect = waiter_error

        with self.assertRaises(exceptions.ChangeEmptyError):
            mock_deployer.wait_for_changeset(changeset_id, stack_name)

        mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id,
                                                 StackName=stack_name)

        mock_client.get_waiter.assert_called_once_with(
                "change_set_create_complete")
示例#5
0
    def test_wait_for_execute_no_changes(self):
        stack_name = "stack_name"
        changeset_type = "CREATE"

        mock_client = Mock()
        mock_deployer = Deployer(mock_client)
        mock_waiter = Mock()
        mock_client.get_waiter.return_value = mock_waiter

        waiter_error = botocore.exceptions.WaiterError(name="name",
                                                       reason="reason",
                                                       last_response={})
        mock_waiter.wait.side_effect = waiter_error

        with self.assertRaises(exceptions.DeployFailedError):
            mock_deployer.wait_for_execute(stack_name, changeset_type)

        waiter_config = {
            'Delay': 5,
            'MaxAttempts': 720,
        }
        mock_waiter.wait.assert_called_once_with(StackName=stack_name,
                                                 WaiterConfig=waiter_config)

        mock_client.get_waiter.assert_called_once_with(
                "stack_create_complete")
示例#6
0
    def test_wait_for_changeset_no_changes(self):
        stack_name = "stack_name"
        changeset_id = "changeset-id"

        mock_client = Mock()
        mock_deployer = Deployer(mock_client)
        mock_waiter = Mock()
        mock_client.get_waiter.return_value = mock_waiter

        response = {
            "Status": "FAILED",
            "StatusReason": "No updates are to be performed"
        }

        waiter_error = botocore.exceptions.WaiterError(name="name",
                                                       reason="reason",
                                                       last_response=response)
        mock_waiter.wait.side_effect = waiter_error

        with self.assertRaises(exceptions.ChangeEmptyError):
            mock_deployer.wait_for_changeset(changeset_id, stack_name)

        waiter_config = {'Delay': 5}
        mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id,
                                                 StackName=stack_name,
                                                 WaiterConfig=waiter_config)

        mock_client.get_waiter.assert_called_once_with(
                "change_set_create_complete")
示例#7
0
    def test_wait_for_execute_no_changes(self):
        stack_name = "stack_name"
        changeset_type = "CREATE"

        mock_client = mock.Mock()
        mock_deployer = Deployer(mock_client)
        mock_waiter = mock.Mock()
        mock_client.get_waiter.return_value = mock_waiter

        waiter_error = botocore.exceptions.WaiterError(name="name",
                                                       reason="reason",
                                                       last_response={})
        mock_waiter.wait.side_effect = waiter_error

        with self.assertRaises(exceptions.DeployFailedError):
            mock_deployer.wait_for_execute(stack_name, changeset_type)

        waiter_config = {
            'Delay': 30,
            'MaxAttempts': 120,
        }
        mock_waiter.wait.assert_called_once_with(StackName=stack_name,
                                                 WaiterConfig=waiter_config)

        mock_client.get_waiter.assert_called_once_with("stack_create_complete")
示例#8
0
    def setUp(self):
        client = botocore.session.get_session().create_client('cloudformation',
                                                              region_name="us-east-1")
        self.stub_client = Stubber(client)

        self.changeset_prefix = "some-changeset-prefix"
        self.deployer = Deployer(client, self.changeset_prefix)
示例#9
0
    def _run_main(self, parsed_args, parsed_globals):
        cloudformation_client = \
            self._session.create_client(
                    'cloudformation', region_name=parsed_globals.region,
                    endpoint_url=parsed_globals.endpoint_url,
                    verify=parsed_globals.verify_ssl)

        template_path = parsed_args.template_file
        if not os.path.isfile(template_path):
            raise exceptions.InvalidTemplatePathError(
                    template_path=template_path)

        # Parse parameters
        with open(template_path, "r") as handle:
            template_str = handle.read()

        stack_name = parsed_args.stack_name
        parameter_overrides = self.parse_parameter_arg(
                parsed_args.parameter_overrides)

        template_dict = yaml_parse(template_str)

        parameters = self.merge_parameters(template_dict, parameter_overrides)

        deployer = Deployer(cloudformation_client)
        return self.deploy(deployer, stack_name, template_str,
                           parameters, parsed_args.capabilities,
                           parsed_args.execute_changeset, parsed_args.role_arn,
                           parsed_args.notification_arns,
                           parsed_args.fail_on_empty_changeset)
示例#10
0
    def deploy(self):
        """
        [option]

        option:
        stack-name: CloudFormation stack name
        template-path: CloudFormation template file path
        params-path: CloudFormation template parameters file path.
                     allowed file type: json, jsonnet
        """
        client = boto3.client('cloudformation')

        if not os.path.isfile(self.template_path):
            raise exceptions.InvalidTemplatePathError(template_path=self.template_path)
        with open(self.template_path, "r") as f:
            template_str = f.read()

        params = self.__load_cfn_params(self.params_path)
        tags = []
        s3_uploader = None

        deployer = Deployer(client)
        self.__deploy(deployer, self.stack_name, template_str,
                      params, ['CAPABILITY_IAM', 'CAPABILITY_AUTO_EXPAND'],
                      True, None, None, s3_uploader, tags, False)
示例#11
0
    def setUp(self):
        self.session = mock.Mock()
        self.session.get_scoped_config.return_value = {}
        self.parsed_args = FakeArgs(
            template_file='./foo',
            stack_name="some_stack_name",
            parameter_overrides=["Key1=Value1", "Key2=Value2"],
            no_execute_changeset=False,
            execute_changeset=True,
            capabilities=None,
            role_arn=None,
            notification_arns=[],
            fail_on_empty_changeset=True,
            s3_bucket=None,
            s3_prefix="some prefix",
            kms_key_id="some kms key id",
            force_upload=True,
            tags=["tagkey1=tagvalue1"])
        self.parsed_globals = FakeArgs(region="us-east-1",
                                       endpoint_url=None,
                                       verify_ssl=None)
        self.deploy_command = DeployCommand(self.session)

        self.deployer = Deployer(Mock())
        self.deployer.create_and_wait_for_changeset = Mock()
        self.deployer.execute_changeset = Mock()
        self.deployer.wait_for_execute = Mock()
def deploy_template(session, config, packaged_yaml, approve=False):
    success = True
    print('\nDeploying...')
    client = session.create_client('cloudformation')
    stack_name = conventions.generate_stack_name(config['Parameters'])
    deployer = Deployer(client)
    tags = conventions.merge_tags(config.get('Tags', {}), config['Parameters'])

    try:
        result = deployer.create_and_wait_for_changeset(
            stack_name=stack_name,
            cfn_template=packaged_yaml,
            parameter_values=deploy_template_parameters_builder(
                config['Parameters']),
            capabilities=['CAPABILITY_NAMED_IAM'],
            role_arn=None,
            notification_arns=None,
            s3_uploader=None,
            tags=deploy_template_tags_builder(tags))
        display_changeset(session, stack_name, result.changeset_id)
        if not approve:
            utils.get_confirmation()

        execute_changeset_token = str(uuid.uuid4())
        client.execute_change_set(ChangeSetName=result.changeset_id,
                                  StackName=stack_name,
                                  ClientRequestToken=execute_changeset_token)
        deployer.wait_for_execute(stack_name, result.changeset_type)
    except exceptions.ChangeEmptyError as ex:
        logging.error(ex)
        success = False
    except exceptions.DeployFailedError as ex:
        logging.error(ex)
        # print(colored(ex.response['Error']['Message'], 'red'))
        stack_events = get_stack_events(session, stack_name,
                                        execute_changeset_token)
        aws.display_cfn_stack_events(stack_events)
        success = False
    except botocore.exceptions.ClientError as ex:
        logging.error(ex)
        success = False

    print('Done.')
    return success
示例#13
0
    def _run_main(self, parsed_args, parsed_globals):
        cloudformation_client = \
            self._session.create_client(
                    'cloudformation', region_name=parsed_globals.region,
                    endpoint_url=parsed_globals.endpoint_url,
                    verify=parsed_globals.verify_ssl)

        template_path = parsed_args.template_file
        if not os.path.isfile(template_path):
            raise exceptions.InvalidTemplatePathError(
                    template_path=template_path)

        # Parse parameters
        with open(template_path, "r") as handle:
            template_str = handle.read()

        stack_name = parsed_args.stack_name
        parameter_overrides = self.parse_key_value_arg(
                parsed_args.parameter_overrides,
                self.PARAMETER_OVERRIDE_CMD)

        tags_dict = self.parse_key_value_arg(parsed_args.tags, self.TAGS_CMD)
        tags = [{"Key": key, "Value": value}
                for key, value in tags_dict.items()]

        template_dict = yaml_parse(template_str)

        parameters = self.merge_parameters(template_dict, parameter_overrides)

        template_size = os.path.getsize(parsed_args.template_file)
        if template_size > 51200 and not parsed_args.s3_bucket:
            raise exceptions.DeployBucketRequiredError()

        bucket = parsed_args.s3_bucket
        if bucket:
            s3_client = self._session.create_client(
                "s3",
                config=Config(signature_version='s3v4'),
                region_name=parsed_globals.region,
                verify=parsed_globals.verify_ssl)

            s3_uploader = S3Uploader(s3_client,
                                      bucket,
                                      parsed_args.s3_prefix,
                                      parsed_args.kms_key_id,
                                      parsed_args.force_upload)
        else:
            s3_uploader = None

        deployer = Deployer(cloudformation_client)
        return self.deploy(deployer, stack_name, template_str,
                           parameters, parsed_args.capabilities,
                           parsed_args.execute_changeset, parsed_args.role_arn,
                           parsed_args.notification_arns, s3_uploader,
                           tags,
                           parsed_args.fail_on_empty_changeset)
示例#14
0
 def publish(self, stack_name):
     d = Deployer(boto3.client('cloudformation'))
     result = d.create_and_wait_for_changeset(
         stack_name=stack_name,
         cfn_template=self.get_template(),
         parameter_values=[],
         capabilities=['CAPABILITY_IAM'])
     d.execute_changeset(result.changeset_id, stack_name)
     d.wait_for_execute(stack_name, result.changeset_type)
示例#15
0
    def setUp(self):
        self.session = mock.Mock()
        self.session.get_scoped_config.return_value = {}
        self.parsed_args = FakeArgs(
            template_file='./foo',
            stack_name="some_stack_name",
            parameter_overrides=["Key1=Value1", "Key2=Value2"],
            no_execute_changeset=False,
            execute_changeset=True,
            capabilities=None)
        self.parsed_globals = FakeArgs(region="us-east-1",
                                       endpoint_url=None,
                                       verify_ssl=None)
        self.deploy_command = DeployCommand(self.session)

        self.deployer = Deployer(Mock())
        self.deployer.create_and_wait_for_changeset = Mock()
        self.deployer.execute_changeset = Mock()
        self.deployer.wait_for_execute = Mock()
示例#16
0
class TestDeployer(unittest.TestCase):
    def setUp(self):
        client = botocore.session.get_session().create_client(
            'cloudformation', region_name="us-east-1")
        self.stub_client = Stubber(client)

        self.changeset_prefix = "some-changeset-prefix"
        self.deployer = Deployer(client, self.changeset_prefix)

    def test_has_stack_success(self):
        stack_name = "stack_name"

        expected_params = {"StackName": stack_name}

        response = {"Stacks": [make_stack_obj(stack_name)]}

        self.stub_client.add_response('describe_stacks', response,
                                      expected_params)

        with self.stub_client:
            response = self.deployer.has_stack(stack_name)
            self.assertTrue(response)

    def test_has_stack_no_stack(self):
        stack_name = "stack_name"
        expected_params = {"StackName": stack_name}

        # Response contains NO stack
        no_stack_response = {"Stacks": []}
        self.stub_client.add_response('describe_stacks', no_stack_response,
                                      expected_params)
        with self.stub_client:
            response = self.deployer.has_stack(stack_name)
            self.assertFalse(response)

        # Response is a ClientError with a message that the stack does not exist
        self.stub_client.add_client_error(
            'describe_stacks', "ClientError",
            "Stack with id {0} does not exist".format(stack_name))
        with self.stub_client:
            response = self.deployer.has_stack(stack_name)
            self.assertFalse(response)

    def test_has_stack_review_in_progress(self):
        stack_name = "stack_name"
        expected_params = {"StackName": stack_name}

        # Response contains NO stack
        review_in_progress_response = {
            "Stacks": [make_stack_obj(stack_name, "REVIEW_IN_PROGRESS")]
        }
        self.stub_client.add_response('describe_stacks',
                                      review_in_progress_response,
                                      expected_params)
        with self.stub_client:
            response = self.deployer.has_stack(stack_name)
            self.assertFalse(response)

    def test_has_stack_exception(self):
        self.stub_client.add_client_error('describe_stacks', "ValidationError",
                                          "Service is bad")
        with self.stub_client:
            with self.assertRaises(botocore.exceptions.ClientError):
                self.deployer.has_stack("stack_name")

    def test_create_changeset_success(self):
        stack_name = "stack_name"
        template = "template"
        parameters = [
            {
                "ParameterKey": "Key1",
                "ParameterValue": "Value"
            },
            {
                "ParameterKey": "Key2",
                "UsePreviousValue": True
            },
            {
                "ParameterKey": "Key3",
                "UsePreviousValue": False
            },
        ]
        # Parameters that Use Previous Value will be removed on stack creation
        # to either force CloudFormation to use the Default value, or ask user to specify a parameter
        filtered_parameters = [
            {
                "ParameterKey": "Key1",
                "ParameterValue": "Value"
            },
            {
                "ParameterKey": "Key3",
                "UsePreviousValue": False
            },
        ]
        capabilities = ["capabilities"]
        role_arn = "arn:aws:iam::1234567890:role"
        notification_arns = ["arn:aws:sns:region:1234567890:notify"]

        # Case 1: Stack DOES NOT exist
        self.deployer.has_stack = Mock()
        self.deployer.has_stack.return_value = False

        expected_params = {
            "ChangeSetName": botocore.stub.ANY,
            "StackName": stack_name,
            "TemplateBody": template,
            "ChangeSetType": "CREATE",
            "Parameters": filtered_parameters,
            "Capabilities": capabilities,
            "Description": botocore.stub.ANY,
            "RoleARN": role_arn,
            "NotificationARNs": notification_arns
        }

        response = {"Id": "changeset ID"}

        self.stub_client.add_response("create_change_set", response,
                                      expected_params)
        with self.stub_client:
            result = self.deployer.create_changeset(stack_name, template,
                                                    parameters, capabilities,
                                                    role_arn,
                                                    notification_arns)
            self.assertEquals(response["Id"], result.changeset_id)
            self.assertEquals("CREATE", result.changeset_type)

        # Case 2: Stack exists. We are updating it
        self.deployer.has_stack.return_value = True
        expected_params["ChangeSetType"] = "UPDATE"
        expected_params["Parameters"] = parameters
        self.stub_client.add_response("create_change_set", response,
                                      expected_params)
        with self.stub_client:
            result = self.deployer.create_changeset(stack_name, template,
                                                    parameters, capabilities,
                                                    role_arn,
                                                    notification_arns)
            self.assertEquals(response["Id"], result.changeset_id)
            self.assertEquals("UPDATE", result.changeset_type)

    def test_create_changeset_exception(self):
        stack_name = "stack_name"
        template = "template"
        parameters = [{
            "ParameterKey": "Key1",
            "ParameterValue": "Value",
            "UsePreviousValue": True
        }]
        capabilities = ["capabilities"]
        role_arn = "arn:aws:iam::1234567890:role"
        notification_arns = ["arn:aws:sns:region:1234567890:notify"]

        self.deployer.has_stack = Mock()
        self.deployer.has_stack.return_value = False

        self.stub_client.add_client_error('create_change_set',
                                          "Somethign is wrong",
                                          "Service is bad")
        with self.stub_client:
            with self.assertRaises(botocore.exceptions.ClientError):
                self.deployer.create_changeset(stack_name, template,
                                               parameters, capabilities,
                                               role_arn, notification_arns)

    def test_execute_changeset(self):
        stack_name = "stack_name"
        changeset_id = "changeset_id"

        expected_params = {
            "ChangeSetName": changeset_id,
            "StackName": stack_name
        }

        self.stub_client.add_response("execute_change_set", {},
                                      expected_params)
        with self.stub_client:
            self.deployer.execute_changeset(changeset_id, stack_name)

    def test_execute_changeset_exception(self):
        stack_name = "stack_name"
        changeset_id = "changeset_id"

        self.stub_client.add_client_error('execute_change_set',
                                          "Somethign is wrong",
                                          "Service is bad")
        with self.stub_client:
            with self.assertRaises(botocore.exceptions.ClientError):
                self.deployer.execute_changeset(changeset_id, stack_name)

    def test_create_and_wait_for_changeset_successful(self):
        stack_name = "stack_name"
        template = "template"
        parameters = [{
            "ParameterKey": "Key1",
            "ParameterValue": "Value",
            "UsePreviousValue": True
        }]
        capabilities = ["capabilities"]
        changeset_id = "changeset id"
        changeset_type = "changeset type"
        role_arn = "arn:aws:iam::1234567890:role"
        notification_arns = ["arn:aws:sns:region:1234567890:notify"]

        self.deployer.create_changeset = Mock()
        self.deployer.create_changeset.return_value = ChangeSetResult(
            changeset_id, changeset_type)

        self.deployer.wait_for_changeset = Mock()

        result = self.deployer.create_and_wait_for_changeset(
            stack_name, template, parameters, capabilities, role_arn,
            notification_arns)
        self.assertEquals(result.changeset_id, changeset_id)
        self.assertEquals(result.changeset_type, changeset_type)

    def test_create_and_wait_for_changeset_error_waiting_for_changeset(self):
        stack_name = "stack_name"
        template = "template"
        parameters = [{
            "ParameterKey": "Key1",
            "ParameterValue": "Value",
            "UsePreviousValue": True
        }]
        capabilities = ["capabilities"]
        changeset_id = "changeset id"
        changeset_type = "changeset type"
        role_arn = "arn:aws:iam::1234567890:role"
        notification_arns = ["arn:aws:sns:region:1234567890:notify"]

        self.deployer.create_changeset = Mock()
        self.deployer.create_changeset.return_value = ChangeSetResult(
            changeset_id, changeset_type)

        self.deployer.wait_for_changeset = Mock()
        self.deployer.wait_for_changeset.side_effect = RuntimeError

        with self.assertRaises(RuntimeError):
            result = self.deployer.create_and_wait_for_changeset(
                stack_name, template, parameters, capabilities, role_arn,
                notification_arns)

    def test_wait_for_changeset_no_changes(self):
        stack_name = "stack_name"
        changeset_id = "changeset-id"

        mock_client = Mock()
        mock_deployer = Deployer(mock_client)
        mock_waiter = Mock()
        mock_client.get_waiter.return_value = mock_waiter

        response = {
            "Status": "FAILED",
            "StatusReason": "The submitted information didn't contain changes."
        }

        waiter_error = botocore.exceptions.WaiterError(name="name",
                                                       reason="reason",
                                                       last_response=response)
        mock_waiter.wait.side_effect = waiter_error

        with self.assertRaises(exceptions.ChangeEmptyError):
            mock_deployer.wait_for_changeset(changeset_id, stack_name)

        waiter_config = {'Delay': 5}
        mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id,
                                                 StackName=stack_name,
                                                 WaiterConfig=waiter_config)

        mock_client.get_waiter.assert_called_once_with(
            "change_set_create_complete")

    def test_wait_for_changeset_failed_to_create_changeset(self):
        stack_name = "stack_name"
        changeset_id = "changeset-id"

        mock_client = Mock()
        mock_deployer = Deployer(mock_client)
        mock_waiter = Mock()
        mock_client.get_waiter.return_value = mock_waiter

        response = {"Status": "FAILED", "StatusReason": "some reason"}

        waiter_error = botocore.exceptions.WaiterError(name="name",
                                                       reason="reason",
                                                       last_response=response)
        mock_waiter.wait.side_effect = waiter_error

        with self.assertRaises(RuntimeError):
            mock_deployer.wait_for_changeset(changeset_id, stack_name)

        waiter_config = {'Delay': 5}
        mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id,
                                                 StackName=stack_name,
                                                 WaiterConfig=waiter_config)

        mock_client.get_waiter.assert_called_once_with(
            "change_set_create_complete")

    def test_wait_for_execute_no_changes(self):
        stack_name = "stack_name"
        changeset_type = "CREATE"

        mock_client = Mock()
        mock_deployer = Deployer(mock_client)
        mock_waiter = Mock()
        mock_client.get_waiter.return_value = mock_waiter

        waiter_error = botocore.exceptions.WaiterError(name="name",
                                                       reason="reason",
                                                       last_response={})
        mock_waiter.wait.side_effect = waiter_error

        with self.assertRaises(exceptions.DeployFailedError):
            mock_deployer.wait_for_execute(stack_name, changeset_type)

        waiter_config = {
            'Delay': 5,
            'MaxAttempts': 720,
        }
        mock_waiter.wait.assert_called_once_with(StackName=stack_name,
                                                 WaiterConfig=waiter_config)

        mock_client.get_waiter.assert_called_once_with("stack_create_complete")
示例#17
0
class TestDeployer(unittest.TestCase):
    def setUp(self):
        client = botocore.session.get_session().create_client(
            'cloudformation', region_name="us-east-1")
        self.stub_client = Stubber(client)

        self.changeset_prefix = "some-changeset-prefix"
        self.deployer = Deployer(client, self.changeset_prefix)

    def test_has_stack_success(self):
        stack_name = "stack_name"

        expected_params = {"StackName": stack_name}

        response = {"Stacks": [make_stack_obj(stack_name)]}

        self.stub_client.add_response('describe_stacks', response,
                                      expected_params)

        with self.stub_client:
            response = self.deployer.has_stack(stack_name)
            self.assertTrue(response)

    def test_has_stack_no_stack(self):
        stack_name = "stack_name"
        expected_params = {"StackName": stack_name}

        # Response contains NO stack
        no_stack_response = {"Stacks": []}
        self.stub_client.add_response('describe_stacks', no_stack_response,
                                      expected_params)
        with self.stub_client:
            response = self.deployer.has_stack(stack_name)
            self.assertFalse(response)

        # Response is a ClientError with a message that the stack does not exist
        self.stub_client.add_client_error(
            'describe_stacks', "ClientError",
            "Stack with id {0} does not exist".format(stack_name))
        with self.stub_client:
            response = self.deployer.has_stack(stack_name)
            self.assertFalse(response)

    def test_has_stack_review_in_progress(self):
        stack_name = "stack_name"
        expected_params = {"StackName": stack_name}

        # Response contains NO stack
        review_in_progress_response = {
            "Stacks": [make_stack_obj(stack_name, "REVIEW_IN_PROGRESS")]
        }
        self.stub_client.add_response('describe_stacks',
                                      review_in_progress_response,
                                      expected_params)
        with self.stub_client:
            response = self.deployer.has_stack(stack_name)
            self.assertFalse(response)

    def test_has_stack_exception(self):
        self.stub_client.add_client_error('describe_stacks', "ValidationError",
                                          "Service is bad")
        with self.stub_client:
            with self.assertRaises(botocore.exceptions.ClientError):
                self.deployer.has_stack("stack_name")

    def test_create_changeset_success(self):
        stack_name = "stack_name"
        template = "template"
        parameters = [{
            "ParameterKey": "Key1",
            "ParameterValue": "Value",
            "UsePreviousValue": True
        }]
        capabilities = ["capabilities"]

        # Case 1: Stack DOES NOT exist
        self.deployer.has_stack = Mock()
        self.deployer.has_stack.return_value = False

        expected_params = {
            "ChangeSetName": botocore.stub.ANY,
            "StackName": stack_name,
            "TemplateBody": template,
            "ChangeSetType": "CREATE",
            "Parameters": parameters,
            "Capabilities": capabilities,
            "Description": botocore.stub.ANY
        }

        response = {"Id": "changeset ID"}

        self.stub_client.add_response("create_change_set", response,
                                      expected_params)
        with self.stub_client:
            result = self.deployer.create_changeset(stack_name, template,
                                                    parameters, capabilities)
            self.assertEquals(response["Id"], result.changeset_id)
            self.assertEquals("CREATE", result.changeset_type)

        # Case 2: Stack exists. We are updating it
        self.deployer.has_stack.return_value = True
        expected_params["ChangeSetType"] = "UPDATE"
        self.stub_client.add_response("create_change_set", response,
                                      expected_params)
        with self.stub_client:
            result = self.deployer.create_changeset(stack_name, template,
                                                    parameters, capabilities)
            self.assertEquals(response["Id"], result.changeset_id)
            self.assertEquals("UPDATE", result.changeset_type)

    def test_create_changeset_exception(self):
        stack_name = "stack_name"
        template = "template"
        parameters = [{
            "ParameterKey": "Key1",
            "ParameterValue": "Value",
            "UsePreviousValue": True
        }]
        capabilities = ["capabilities"]

        self.deployer.has_stack = Mock()
        self.deployer.has_stack.return_value = False

        self.stub_client.add_client_error('create_change_set',
                                          "Somethign is wrong",
                                          "Service is bad")
        with self.stub_client:
            with self.assertRaises(botocore.exceptions.ClientError):
                self.deployer.create_changeset(stack_name, template,
                                               parameters, capabilities)

    def test_execute_changeset(self):
        stack_name = "stack_name"
        changeset_id = "changeset_id"

        expected_params = {
            "ChangeSetName": changeset_id,
            "StackName": stack_name
        }

        self.stub_client.add_response("execute_change_set", {},
                                      expected_params)
        with self.stub_client:
            self.deployer.execute_changeset(changeset_id, stack_name)

    def test_execute_changeset_exception(self):
        stack_name = "stack_name"
        changeset_id = "changeset_id"

        self.stub_client.add_client_error('execute_change_set',
                                          "Somethign is wrong",
                                          "Service is bad")
        with self.stub_client:
            with self.assertRaises(botocore.exceptions.ClientError):
                self.deployer.execute_changeset(changeset_id, stack_name)

    def test_create_and_wait_for_changeset_successful(self):
        stack_name = "stack_name"
        template = "template"
        parameters = [{
            "ParameterKey": "Key1",
            "ParameterValue": "Value",
            "UsePreviousValue": True
        }]
        capabilities = ["capabilities"]
        changeset_id = "changeset id"
        changeset_type = "changeset type"

        self.deployer.create_changeset = Mock()
        self.deployer.create_changeset.return_value = ChangeSetResult(
            changeset_id, changeset_type)

        self.deployer.wait_for_changeset = Mock()

        result = self.deployer.create_and_wait_for_changeset(
            stack_name, template, parameters, capabilities)
        self.assertEquals(result.changeset_id, changeset_id)
        self.assertEquals(result.changeset_type, changeset_type)

    def test_create_and_wait_for_changeset_error_waiting_for_changeset(self):
        stack_name = "stack_name"
        template = "template"
        parameters = [{
            "ParameterKey": "Key1",
            "ParameterValue": "Value",
            "UsePreviousValue": True
        }]
        capabilities = ["capabilities"]
        changeset_id = "changeset id"
        changeset_type = "changeset type"

        self.deployer.create_changeset = Mock()
        self.deployer.create_changeset.return_value = ChangeSetResult(
            changeset_id, changeset_type)

        self.deployer.wait_for_changeset = Mock()
        self.deployer.wait_for_changeset.side_effect = RuntimeError

        with self.assertRaises(RuntimeError):
            result = self.deployer.create_and_wait_for_changeset(
                stack_name, template, parameters, capabilities)

    def test_wait_for_changeset_no_changes(self):
        stack_name = "stack_name"
        changeset_id = "changeset-id"

        mock_client = Mock()
        mock_deployer = Deployer(mock_client)
        mock_waiter = Mock()
        mock_client.get_waiter.return_value = mock_waiter

        response = {
            "Status": "FAILED",
            "StatusReason": "No updates are to be performed"
        }

        waiter_error = botocore.exceptions.WaiterError(name="name",
                                                       reason="reason",
                                                       last_response=response)
        mock_waiter.wait.side_effect = waiter_error

        with self.assertRaises(exceptions.ChangeEmptyError):
            mock_deployer.wait_for_changeset(changeset_id, stack_name)

        mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id,
                                                 StackName=stack_name)

        mock_client.get_waiter.assert_called_once_with(
            "change_set_create_complete")

    def test_wait_for_changeset_failed_to_create_changeset(self):
        stack_name = "stack_name"
        changeset_id = "changeset-id"

        mock_client = Mock()
        mock_deployer = Deployer(mock_client)
        mock_waiter = Mock()
        mock_client.get_waiter.return_value = mock_waiter

        response = {"Status": "FAILED", "StatusReason": "some reason"}

        waiter_error = botocore.exceptions.WaiterError(name="name",
                                                       reason="reason",
                                                       last_response=response)
        mock_waiter.wait.side_effect = waiter_error

        with self.assertRaises(RuntimeError):
            mock_deployer.wait_for_changeset(changeset_id, stack_name)

        mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id,
                                                 StackName=stack_name)

        mock_client.get_waiter.assert_called_once_with(
            "change_set_create_complete")

    def test_wait_for_execute_no_changes(self):
        stack_name = "stack_name"
        changeset_type = "CREATE"

        mock_client = Mock()
        mock_deployer = Deployer(mock_client)
        mock_waiter = Mock()
        mock_client.get_waiter.return_value = mock_waiter

        waiter_error = botocore.exceptions.WaiterError(name="name",
                                                       reason="reason",
                                                       last_response={})
        mock_waiter.wait.side_effect = waiter_error

        with self.assertRaises(exceptions.DeployFailedError):
            mock_deployer.wait_for_execute(stack_name, changeset_type)

        mock_waiter.wait.assert_called_once_with(StackName=stack_name)

        mock_client.get_waiter.assert_called_once_with("stack_create_complete")
示例#18
0
class TestDeployer(unittest.TestCase):

    def setUp(self):
        client = botocore.session.get_session().create_client('cloudformation',
                                                              region_name="us-east-1")
        self.stub_client = Stubber(client)

        self.changeset_prefix = "some-changeset-prefix"
        self.deployer = Deployer(client, self.changeset_prefix)

    def test_has_stack_success(self):
        stack_name = "stack_name"

        expected_params = {
            "StackName": stack_name
        }

        response = {
            "Stacks": [
                make_stack_obj(stack_name)
            ]
        }

        self.stub_client.add_response('describe_stacks', response,
                                      expected_params)

        with self.stub_client:
            response = self.deployer.has_stack(stack_name)
            self.assertTrue(response)

    def test_has_stack_no_stack(self):
        stack_name = "stack_name"
        expected_params = {
            "StackName": stack_name
        }

        # Response contains NO stack
        no_stack_response = {
            "Stacks": []
        }
        self.stub_client.add_response('describe_stacks', no_stack_response,
                                      expected_params)
        with self.stub_client:
            response = self.deployer.has_stack(stack_name)
            self.assertFalse(response)

        # Response is a ClientError with a message that the stack does not exist
        self.stub_client.add_client_error('describe_stacks', "ClientError",
                                          "Stack with id {0} does not exist"
                                          .format(stack_name))
        with self.stub_client:
            response = self.deployer.has_stack(stack_name)
            self.assertFalse(response)

    def test_has_stack_review_in_progress(self):
        stack_name = "stack_name"
        expected_params = {
            "StackName": stack_name
        }

        # Response contains NO stack
        review_in_progress_response = {
            "Stacks": [make_stack_obj(stack_name, "REVIEW_IN_PROGRESS")]
        }
        self.stub_client.add_response('describe_stacks',
                                      review_in_progress_response,
                                      expected_params)
        with self.stub_client:
            response = self.deployer.has_stack(stack_name)
            self.assertFalse(response)

    def test_has_stack_exception(self):
        self.stub_client.add_client_error('describe_stacks', "ValidationError",
                                          "Service is bad")
        with self.stub_client:
            with self.assertRaises(botocore.exceptions.ClientError):
                self.deployer.has_stack("stack_name")

    def test_create_changeset_success(self):
        stack_name = "stack_name"
        template = "template"
        parameters = [{"ParameterKey": "Key1", "ParameterValue": "Value",
                       "UsePreviousValue": True}]
        capabilities = ["capabilities"]

        # Case 1: Stack DOES NOT exist
        self.deployer.has_stack = Mock()
        self.deployer.has_stack.return_value = False

        expected_params = {
            "ChangeSetName": botocore.stub.ANY,
            "StackName": stack_name,
            "TemplateBody": template,
            "ChangeSetType": "CREATE",
            "Parameters": parameters,
            "Capabilities": capabilities,
            "Description": botocore.stub.ANY
        }

        response = {
            "Id": "changeset ID"
        }

        self.stub_client.add_response("create_change_set", response,
                                      expected_params)
        with self.stub_client:
            result = self.deployer.create_changeset(
                    stack_name, template, parameters, capabilities)
            self.assertEquals(response["Id"], result.changeset_id)
            self.assertEquals("CREATE", result.changeset_type)

        # Case 2: Stack exists. We are updating it
        self.deployer.has_stack.return_value = True
        expected_params["ChangeSetType"] = "UPDATE"
        self.stub_client.add_response("create_change_set", response,
                                      expected_params)
        with self.stub_client:
            result = self.deployer.create_changeset(
                    stack_name, template, parameters, capabilities)
            self.assertEquals(response["Id"], result.changeset_id)
            self.assertEquals("UPDATE", result.changeset_type)

    def test_create_changeset_exception(self):
        stack_name = "stack_name"
        template = "template"
        parameters = [{"ParameterKey": "Key1", "ParameterValue": "Value",
                       "UsePreviousValue": True}]
        capabilities = ["capabilities"]

        self.deployer.has_stack = Mock()
        self.deployer.has_stack.return_value = False

        self.stub_client.add_client_error(
                'create_change_set', "Somethign is wrong", "Service is bad")
        with self.stub_client:
            with self.assertRaises(botocore.exceptions.ClientError):
                self.deployer.create_changeset(stack_name, template, parameters, capabilities)

    def test_execute_changeset(self):
        stack_name = "stack_name"
        changeset_id = "changeset_id"

        expected_params = {
            "ChangeSetName": changeset_id,
            "StackName": stack_name
        }

        self.stub_client.add_response("execute_change_set", {}, expected_params)
        with self.stub_client:
            self.deployer.execute_changeset(changeset_id, stack_name)

    def test_execute_changeset_exception(self):
        stack_name = "stack_name"
        changeset_id = "changeset_id"

        self.stub_client.add_client_error(
                'execute_change_set', "Somethign is wrong", "Service is bad")
        with self.stub_client:
            with self.assertRaises(botocore.exceptions.ClientError):
                self.deployer.execute_changeset(changeset_id, stack_name)

    def test_create_and_wait_for_changeset_successful(self):
        stack_name = "stack_name"
        template = "template"
        parameters = [{"ParameterKey": "Key1", "ParameterValue": "Value",
                       "UsePreviousValue": True}]
        capabilities = ["capabilities"]
        changeset_id = "changeset id"
        changeset_type = "changeset type"

        self.deployer.create_changeset = Mock()
        self.deployer.create_changeset.return_value = ChangeSetResult(changeset_id, changeset_type)

        self.deployer.wait_for_changeset = Mock()

        result = self.deployer.create_and_wait_for_changeset(
                stack_name, template, parameters, capabilities)
        self.assertEquals(result.changeset_id, changeset_id)
        self.assertEquals(result.changeset_type, changeset_type)

    def test_create_and_wait_for_changeset_error_waiting_for_changeset(self):
        stack_name = "stack_name"
        template = "template"
        parameters = [{"ParameterKey": "Key1", "ParameterValue": "Value",
                       "UsePreviousValue": True}]
        capabilities = ["capabilities"]
        changeset_id = "changeset id"
        changeset_type = "changeset type"

        self.deployer.create_changeset = Mock()
        self.deployer.create_changeset.return_value = ChangeSetResult(changeset_id, changeset_type)

        self.deployer.wait_for_changeset = Mock()
        self.deployer.wait_for_changeset.side_effect = RuntimeError

        with self.assertRaises(RuntimeError):
            result = self.deployer.create_and_wait_for_changeset(
                    stack_name, template, parameters, capabilities)

    def test_wait_for_changeset_no_changes(self):
        stack_name = "stack_name"
        changeset_id = "changeset-id"

        mock_client = Mock()
        mock_deployer = Deployer(mock_client)
        mock_waiter = Mock()
        mock_client.get_waiter.return_value = mock_waiter

        response = {
            "Status": "FAILED",
            "StatusReason": "No updates are to be performed"
        }

        waiter_error = botocore.exceptions.WaiterError(name="name",
                                                       reason="reason",
                                                       last_response=response)
        mock_waiter.wait.side_effect = waiter_error

        with self.assertRaises(exceptions.ChangeEmptyError):
            mock_deployer.wait_for_changeset(changeset_id, stack_name)

        mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id,
                                                 StackName=stack_name)

        mock_client.get_waiter.assert_called_once_with(
                "change_set_create_complete")

    def test_wait_for_changeset_failed_to_create_changeset(self):
        stack_name = "stack_name"
        changeset_id = "changeset-id"

        mock_client = Mock()
        mock_deployer = Deployer(mock_client)
        mock_waiter = Mock()
        mock_client.get_waiter.return_value = mock_waiter

        response = {
            "Status": "FAILED",
            "StatusReason": "some reason"
        }

        waiter_error = botocore.exceptions.WaiterError(name="name",
                                                       reason="reason",
                                                       last_response=response)
        mock_waiter.wait.side_effect = waiter_error

        with self.assertRaises(RuntimeError):
            mock_deployer.wait_for_changeset(changeset_id, stack_name)

        mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id,
                                            StackName=stack_name)

        mock_client.get_waiter.assert_called_once_with(
                "change_set_create_complete")

    def test_wait_for_execute_no_changes(self):
        stack_name = "stack_name"
        changeset_type = "CREATE"

        mock_client = Mock()
        mock_deployer = Deployer(mock_client)
        mock_waiter = Mock()
        mock_client.get_waiter.return_value = mock_waiter

        waiter_error = botocore.exceptions.WaiterError(name="name",
                                                       reason="reason",
                                                       last_response={})
        mock_waiter.wait.side_effect = waiter_error

        with self.assertRaises(exceptions.DeployFailedError):
            mock_deployer.wait_for_execute(stack_name, changeset_type)

        mock_waiter.wait.assert_called_once_with(StackName=stack_name)

        mock_client.get_waiter.assert_called_once_with(
                "stack_create_complete")
示例#19
0
class TestDeployer(unittest.TestCase):

    def setUp(self):
        client = botocore.session.get_session().create_client('cloudformation',
                                                              region_name="us-east-1")
        self.stub_client = Stubber(client)

        self.changeset_prefix = "some-changeset-prefix"
        self.deployer = Deployer(client, self.changeset_prefix)

    def test_has_stack_success(self):
        stack_name = "stack_name"

        expected_params = {
            "StackName": stack_name
        }

        response = {
            "Stacks": [
                make_stack_obj(stack_name)
            ]
        }

        self.stub_client.add_response('describe_stacks', response,
                                      expected_params)

        with self.stub_client:
            response = self.deployer.has_stack(stack_name)
            self.assertTrue(response)

    def test_has_stack_no_stack(self):
        stack_name = "stack_name"
        expected_params = {
            "StackName": stack_name
        }

        # Response contains NO stack
        no_stack_response = {
            "Stacks": []
        }
        self.stub_client.add_response('describe_stacks', no_stack_response,
                                      expected_params)
        with self.stub_client:
            response = self.deployer.has_stack(stack_name)
            self.assertFalse(response)

        # Response is a ClientError with a message that the stack does not exist
        self.stub_client.add_client_error('describe_stacks', "ClientError",
                                          "Stack with id {0} does not exist"
                                          .format(stack_name))
        with self.stub_client:
            response = self.deployer.has_stack(stack_name)
            self.assertFalse(response)

    def test_has_stack_review_in_progress(self):
        stack_name = "stack_name"
        expected_params = {
            "StackName": stack_name
        }

        # Response contains NO stack
        review_in_progress_response = {
            "Stacks": [make_stack_obj(stack_name, "REVIEW_IN_PROGRESS")]
        }
        self.stub_client.add_response('describe_stacks',
                                      review_in_progress_response,
                                      expected_params)
        with self.stub_client:
            response = self.deployer.has_stack(stack_name)
            self.assertFalse(response)

    def test_has_stack_exception(self):
        self.stub_client.add_client_error('describe_stacks', "ValidationError",
                                          "Service is bad")
        with self.stub_client:
            with self.assertRaises(botocore.exceptions.ClientError):
                self.deployer.has_stack("stack_name")

    def test_create_changeset_success(self):
        stack_name = "stack_name"
        template = "template"
        parameters = [
            {"ParameterKey": "Key1", "ParameterValue": "Value"},
            {"ParameterKey": "Key2", "UsePreviousValue": True},
            {"ParameterKey": "Key3", "UsePreviousValue": False},
        ]
        # Parameters that Use Previous Value will be removed on stack creation
        # to either force CloudFormation to use the Default value, or ask user to specify a parameter
        filtered_parameters = [
            {"ParameterKey": "Key1", "ParameterValue": "Value"},
            {"ParameterKey": "Key3", "UsePreviousValue": False},
        ]
        capabilities = ["capabilities"]
        role_arn = "arn:aws:iam::1234567890:role"
        notification_arns = ["arn:aws:sns:region:1234567890:notify"]
        s3_uploader = None

        tags = [{"Key":"key1", "Value": "val1"}]

        # Case 1: Stack DOES NOT exist
        self.deployer.has_stack = Mock()
        self.deployer.has_stack.return_value = False

        expected_params = {
            "ChangeSetName": botocore.stub.ANY,
            "StackName": stack_name,
            "TemplateBody": template,
            "ChangeSetType": "CREATE",
            "Parameters": filtered_parameters,
            "Capabilities": capabilities,
            "Description": botocore.stub.ANY,
            "RoleARN": role_arn,
            "NotificationARNs": notification_arns,
            "Tags": tags
        }

        response = {
            "Id": "changeset ID"
        }

        self.stub_client.add_response("create_change_set", response,
                                      expected_params)
        with self.stub_client:
            result = self.deployer.create_changeset(
                    stack_name, template, parameters, capabilities, role_arn,
                    notification_arns, s3_uploader, tags)
            self.assertEquals(response["Id"], result.changeset_id)
            self.assertEquals("CREATE", result.changeset_type)

        # Case 2: Stack exists. We are updating it
        self.deployer.has_stack.return_value = True
        self.stub_client.add_response("get_template_summary",
            {"Parameters": [{"ParameterKey": parameter["ParameterKey"]}
                for parameter in parameters]},
            {"StackName": stack_name})
        expected_params["ChangeSetType"] = "UPDATE"
        expected_params["Parameters"] = parameters
        self.stub_client.add_response("create_change_set", response,
                                      expected_params)
        # template has new parameter but should not be included in
        # expected_params as no previous value
        parameters = list(parameters) + \
            [{"ParameterKey": "New", "UsePreviousValue": True}]
        with self.stub_client:
            result = self.deployer.create_changeset(
                    stack_name, template, parameters, capabilities, role_arn,
                    notification_arns, s3_uploader, tags)
            self.assertEquals(response["Id"], result.changeset_id)
            self.assertEquals("UPDATE", result.changeset_type)

    def test_create_changeset_success_s3_bucket(self):
        stack_name = "stack_name"
        template = "template"
        template_url = "https://s3.amazonaws.com/bucket/file"
        parameters = [
            {"ParameterKey": "Key1", "ParameterValue": "Value"},
            {"ParameterKey": "Key2", "UsePreviousValue": True},
            {"ParameterKey": "Key3", "UsePreviousValue": False},
        ]
        # Parameters that Use Previous Value will be removed on stack creation
        # to either force CloudFormation to use the Default value, or ask user to specify a parameter
        filtered_parameters = [
            {"ParameterKey": "Key1", "ParameterValue": "Value"},
            {"ParameterKey": "Key3", "UsePreviousValue": False},
        ]
        capabilities = ["capabilities"]
        role_arn = "arn:aws:iam::1234567890:role"
        notification_arns = ["arn:aws:sns:region:1234567890:notify"]

        s3_uploader = Mock()
        def to_path_style_s3_url(some_string, Version=None):
            return "https://s3.amazonaws.com/bucket/file"
        s3_uploader.to_path_style_s3_url = to_path_style_s3_url
        def upload_with_dedup(filename,extension):
            return "s3://bucket/file"
        s3_uploader.upload_with_dedup = upload_with_dedup

        # Case 1: Stack DOES NOT exist
        self.deployer.has_stack = Mock()
        self.deployer.has_stack.return_value = False

        expected_params = {
            "ChangeSetName": botocore.stub.ANY,
            "StackName": stack_name,
            "TemplateURL": template_url,
            "ChangeSetType": "CREATE",
            "Parameters": filtered_parameters,
            "Capabilities": capabilities,
            "Description": botocore.stub.ANY,
            "RoleARN": role_arn,
            "Tags": [],
            "NotificationARNs": notification_arns
        }

        response = {
            "Id": "changeset ID"
        }

        self.stub_client.add_response("create_change_set", response,
                                      expected_params)
        with self.stub_client:
            result = self.deployer.create_changeset(
                stack_name, template, parameters, capabilities, role_arn,
                notification_arns, s3_uploader, [])
            self.assertEquals(response["Id"], result.changeset_id)
            self.assertEquals("CREATE", result.changeset_type)

        # Case 2: Stack exists. We are updating it
        self.deployer.has_stack.return_value = True
        self.stub_client.add_response("get_template_summary",
            {"Parameters": [{"ParameterKey": parameter["ParameterKey"]}
                for parameter in parameters]},
            {"StackName": stack_name})
        expected_params["ChangeSetType"] = "UPDATE"
        expected_params["Parameters"] = parameters
        # template has new parameter but should not be included in
        # expected_params as no previous value
        parameters = list(parameters) + \
            [{"ParameterKey": "New", "UsePreviousValue": True}]
        self.stub_client.add_response("create_change_set", response,
                                      expected_params)
        with self.stub_client:
            result = self.deployer.create_changeset(
                    stack_name, template, parameters, capabilities, role_arn,
                    notification_arns, s3_uploader, [])
            self.assertEquals(response["Id"], result.changeset_id)
            self.assertEquals("UPDATE", result.changeset_type)

    def test_create_changeset_exception(self):
        stack_name = "stack_name"
        template = "template"
        parameters = [{"ParameterKey": "Key1", "ParameterValue": "Value",
                       "UsePreviousValue": True}]
        capabilities = ["capabilities"]
        role_arn = "arn:aws:iam::1234567890:role"
        notification_arns = ["arn:aws:sns:region:1234567890:notify"]
        s3_uploader = None
        tags = [{"Key":"key1", "Value": "val1"}]

        self.deployer.has_stack = Mock()
        self.deployer.has_stack.return_value = False

        self.stub_client.add_client_error(
                'create_change_set', "Somethign is wrong", "Service is bad")
        with self.stub_client:
            with self.assertRaises(botocore.exceptions.ClientError):
                self.deployer.create_changeset(stack_name, template, parameters,
                                               capabilities, role_arn, notification_arns, None, tags)

    def test_execute_changeset(self):
        stack_name = "stack_name"
        changeset_id = "changeset_id"

        expected_params = {
            "ChangeSetName": changeset_id,
            "StackName": stack_name
        }

        self.stub_client.add_response("execute_change_set", {}, expected_params)
        with self.stub_client:
            self.deployer.execute_changeset(changeset_id, stack_name)

    def test_execute_changeset_exception(self):
        stack_name = "stack_name"
        changeset_id = "changeset_id"

        self.stub_client.add_client_error(
                'execute_change_set', "Somethign is wrong", "Service is bad")
        with self.stub_client:
            with self.assertRaises(botocore.exceptions.ClientError):
                self.deployer.execute_changeset(changeset_id, stack_name)

    def test_create_and_wait_for_changeset_successful(self):
        stack_name = "stack_name"
        template = "template"
        parameters = [{"ParameterKey": "Key1", "ParameterValue": "Value",
                       "UsePreviousValue": True}]
        capabilities = ["capabilities"]
        changeset_id = "changeset id"
        changeset_type = "changeset type"
        role_arn = "arn:aws:iam::1234567890:role"
        notification_arns = ["arn:aws:sns:region:1234567890:notify"]
        s3_uploader = None
        tags = [{"Key":"key1", "Value": "val1"}]

        self.deployer.create_changeset = Mock()
        self.deployer.create_changeset.return_value = ChangeSetResult(changeset_id, changeset_type)

        self.deployer.wait_for_changeset = Mock()

        result = self.deployer.create_and_wait_for_changeset(
                stack_name, template, parameters, capabilities, role_arn,
                notification_arns, s3_uploader, tags)
        self.assertEquals(result.changeset_id, changeset_id)
        self.assertEquals(result.changeset_type, changeset_type)

    def test_create_and_wait_for_changeset_error_waiting_for_changeset(self):
        stack_name = "stack_name"
        template = "template"
        parameters = [{"ParameterKey": "Key1", "ParameterValue": "Value",
                       "UsePreviousValue": True}]
        capabilities = ["capabilities"]
        changeset_id = "changeset id"
        changeset_type = "changeset type"
        role_arn = "arn:aws:iam::1234567890:role"
        notification_arns = ["arn:aws:sns:region:1234567890:notify"]
        s3_uploader = None
        tags = [{"Key":"key1", "Value": "val1"}]

        self.deployer.create_changeset = Mock()
        self.deployer.create_changeset.return_value = ChangeSetResult(changeset_id, changeset_type)

        self.deployer.wait_for_changeset = Mock()
        self.deployer.wait_for_changeset.side_effect = RuntimeError

        with self.assertRaises(RuntimeError):
            result = self.deployer.create_and_wait_for_changeset(
                    stack_name, template, parameters, capabilities, role_arn,
                    notification_arns, s3_uploader, tags)

    def test_wait_for_changeset_no_changes(self):
        stack_name = "stack_name"
        changeset_id = "changeset-id"

        mock_client = Mock()
        mock_deployer = Deployer(mock_client)
        mock_waiter = Mock()
        mock_client.get_waiter.return_value = mock_waiter

        response = {
            "Status": "FAILED",
            "StatusReason": "The submitted information didn't contain changes."
        }

        waiter_error = botocore.exceptions.WaiterError(name="name",
                                                       reason="reason",
                                                       last_response=response)
        mock_waiter.wait.side_effect = waiter_error

        with self.assertRaises(exceptions.ChangeEmptyError):
            mock_deployer.wait_for_changeset(changeset_id, stack_name)

        waiter_config = {'Delay': 5}
        mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id,
                                                 StackName=stack_name,
                                                 WaiterConfig=waiter_config)

        mock_client.get_waiter.assert_called_once_with(
                "change_set_create_complete")

    def test_wait_for_changeset_no_changes_with_another_error_msg(self):
        stack_name = "stack_name"
        changeset_id = "changeset-id"

        mock_client = Mock()
        mock_deployer = Deployer(mock_client)
        mock_waiter = Mock()
        mock_client.get_waiter.return_value = mock_waiter

        response = {
            "Status": "FAILED",
            "StatusReason": "No updates are to be performed"
        }

        waiter_error = botocore.exceptions.WaiterError(name="name",
                                                       reason="reason",
                                                       last_response=response)
        mock_waiter.wait.side_effect = waiter_error

        with self.assertRaises(exceptions.ChangeEmptyError):
            mock_deployer.wait_for_changeset(changeset_id, stack_name)

        waiter_config = {'Delay': 5}
        mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id,
                                                 StackName=stack_name,
                                                 WaiterConfig=waiter_config)

        mock_client.get_waiter.assert_called_once_with(
                "change_set_create_complete")

    def test_wait_for_changeset_failed_to_create_changeset(self):
        stack_name = "stack_name"
        changeset_id = "changeset-id"

        mock_client = Mock()
        mock_deployer = Deployer(mock_client)
        mock_waiter = Mock()
        mock_client.get_waiter.return_value = mock_waiter

        response = {
            "Status": "FAILED",
            "StatusReason": "some reason"
        }

        waiter_error = botocore.exceptions.WaiterError(name="name",
                                                       reason="reason",
                                                       last_response=response)
        mock_waiter.wait.side_effect = waiter_error

        with self.assertRaises(RuntimeError):
            mock_deployer.wait_for_changeset(changeset_id, stack_name)

        waiter_config = {'Delay': 5}
        mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id,
                                                 StackName=stack_name,
                                                 WaiterConfig=waiter_config)

        mock_client.get_waiter.assert_called_once_with(
                "change_set_create_complete")

    def test_wait_for_execute_no_changes(self):
        stack_name = "stack_name"
        changeset_type = "CREATE"

        mock_client = Mock()
        mock_deployer = Deployer(mock_client)
        mock_waiter = Mock()
        mock_client.get_waiter.return_value = mock_waiter

        waiter_error = botocore.exceptions.WaiterError(name="name",
                                                       reason="reason",
                                                       last_response={})
        mock_waiter.wait.side_effect = waiter_error

        with self.assertRaises(exceptions.DeployFailedError):
            mock_deployer.wait_for_execute(stack_name, changeset_type)

        waiter_config = {
            'Delay': 5,
            'MaxAttempts': 720,
        }
        mock_waiter.wait.assert_called_once_with(StackName=stack_name,
                                                 WaiterConfig=waiter_config)

        mock_client.get_waiter.assert_called_once_with(
                "stack_create_complete")
示例#20
0
class TestDeployer(unittest.TestCase):
    def setUp(self):
        client = botocore.session.get_session().create_client(
            'cloudformation', region_name="us-east-1")
        self.stub_client = Stubber(client)

        self.changeset_prefix = "some-changeset-prefix"
        self.deployer = Deployer(client, self.changeset_prefix)

    def test_has_stack_success(self):
        stack_name = "stack_name"

        expected_params = {"StackName": stack_name}

        response = {"Stacks": [make_stack_obj(stack_name)]}

        self.stub_client.add_response('describe_stacks', response,
                                      expected_params)

        with self.stub_client:
            response = self.deployer.has_stack(stack_name)
            self.assertTrue(response)

    def test_has_stack_no_stack(self):
        stack_name = "stack_name"
        expected_params = {"StackName": stack_name}

        # Response contains NO stack
        no_stack_response = {"Stacks": []}
        self.stub_client.add_response('describe_stacks', no_stack_response,
                                      expected_params)
        with self.stub_client:
            response = self.deployer.has_stack(stack_name)
            self.assertFalse(response)

        # Response is a ClientError with a message that the stack does not exist
        self.stub_client.add_client_error(
            'describe_stacks', "ClientError",
            "Stack with id {0} does not exist".format(stack_name))
        with self.stub_client:
            response = self.deployer.has_stack(stack_name)
            self.assertFalse(response)

    def test_has_stack_review_in_progress(self):
        stack_name = "stack_name"
        expected_params = {"StackName": stack_name}

        # Response contains NO stack
        review_in_progress_response = {
            "Stacks": [make_stack_obj(stack_name, "REVIEW_IN_PROGRESS")]
        }
        self.stub_client.add_response('describe_stacks',
                                      review_in_progress_response,
                                      expected_params)
        with self.stub_client:
            response = self.deployer.has_stack(stack_name)
            self.assertFalse(response)

    def test_has_stack_exception(self):
        self.stub_client.add_client_error('describe_stacks', "ValidationError",
                                          "Service is bad")
        with self.stub_client:
            with self.assertRaises(botocore.exceptions.ClientError):
                self.deployer.has_stack("stack_name")

    def test_create_changeset_success(self):
        stack_name = "stack_name"
        template = "template"
        parameters = [
            {
                "ParameterKey": "Key1",
                "ParameterValue": "Value"
            },
            {
                "ParameterKey": "Key2",
                "UsePreviousValue": True
            },
            {
                "ParameterKey": "Key3",
                "UsePreviousValue": False
            },
        ]
        # Parameters that Use Previous Value will be removed on stack creation
        # to either force CloudFormation to use the Default value, or ask user to specify a parameter
        filtered_parameters = [
            {
                "ParameterKey": "Key1",
                "ParameterValue": "Value"
            },
            {
                "ParameterKey": "Key3",
                "UsePreviousValue": False
            },
        ]
        capabilities = ["capabilities"]
        role_arn = "arn:aws:iam::1234567890:role"
        notification_arns = ["arn:aws:sns:region:1234567890:notify"]
        s3_uploader = None

        tags = [{"Key": "key1", "Value": "val1"}]

        # Case 1: Stack DOES NOT exist
        self.deployer.has_stack = mock.Mock()
        self.deployer.has_stack.return_value = False

        expected_params = {
            "ChangeSetName": botocore.stub.ANY,
            "StackName": stack_name,
            "TemplateBody": template,
            "ChangeSetType": "CREATE",
            "Parameters": filtered_parameters,
            "Capabilities": capabilities,
            "Description": botocore.stub.ANY,
            "RoleARN": role_arn,
            "NotificationARNs": notification_arns,
            "Tags": tags
        }

        response = {"Id": "changeset ID"}

        self.stub_client.add_response("create_change_set", response,
                                      expected_params)
        with self.stub_client:
            result = self.deployer.create_changeset(stack_name, template,
                                                    parameters, capabilities,
                                                    role_arn,
                                                    notification_arns,
                                                    s3_uploader, tags)
            self.assertEqual(response["Id"], result.changeset_id)
            self.assertEqual("CREATE", result.changeset_type)

        # Case 2: Stack exists. We are updating it
        self.deployer.has_stack.return_value = True
        self.stub_client.add_response(
            "get_template_summary", {
                "Parameters": [{
                    "ParameterKey": parameter["ParameterKey"]
                } for parameter in parameters]
            }, {"StackName": stack_name})
        expected_params["ChangeSetType"] = "UPDATE"
        expected_params["Parameters"] = parameters
        self.stub_client.add_response("create_change_set", response,
                                      expected_params)
        # template has new parameter but should not be included in
        # expected_params as no previous value
        parameters = list(parameters) + \
            [{"ParameterKey": "New", "UsePreviousValue": True}]
        with self.stub_client:
            result = self.deployer.create_changeset(stack_name, template,
                                                    parameters, capabilities,
                                                    role_arn,
                                                    notification_arns,
                                                    s3_uploader, tags)
            self.assertEqual(response["Id"], result.changeset_id)
            self.assertEqual("UPDATE", result.changeset_type)

    def test_create_changeset_success_s3_bucket(self):
        stack_name = "stack_name"
        template = "template"
        template_url = "https://s3.amazonaws.com/bucket/file"
        parameters = [
            {
                "ParameterKey": "Key1",
                "ParameterValue": "Value"
            },
            {
                "ParameterKey": "Key2",
                "UsePreviousValue": True
            },
            {
                "ParameterKey": "Key3",
                "UsePreviousValue": False
            },
        ]
        # Parameters that Use Previous Value will be removed on stack creation
        # to either force CloudFormation to use the Default value, or ask user to specify a parameter
        filtered_parameters = [
            {
                "ParameterKey": "Key1",
                "ParameterValue": "Value"
            },
            {
                "ParameterKey": "Key3",
                "UsePreviousValue": False
            },
        ]
        capabilities = ["capabilities"]
        role_arn = "arn:aws:iam::1234567890:role"
        notification_arns = ["arn:aws:sns:region:1234567890:notify"]

        s3_uploader = mock.Mock()

        def to_path_style_s3_url(some_string, Version=None):
            return "https://s3.amazonaws.com/bucket/file"

        s3_uploader.to_path_style_s3_url = to_path_style_s3_url

        def upload_with_dedup(filename, extension):
            return "s3://bucket/file"

        s3_uploader.upload_with_dedup = upload_with_dedup

        # Case 1: Stack DOES NOT exist
        self.deployer.has_stack = mock.Mock()
        self.deployer.has_stack.return_value = False

        expected_params = {
            "ChangeSetName": botocore.stub.ANY,
            "StackName": stack_name,
            "TemplateURL": template_url,
            "ChangeSetType": "CREATE",
            "Parameters": filtered_parameters,
            "Capabilities": capabilities,
            "Description": botocore.stub.ANY,
            "RoleARN": role_arn,
            "Tags": [],
            "NotificationARNs": notification_arns
        }

        response = {"Id": "changeset ID"}

        self.stub_client.add_response("create_change_set", response,
                                      expected_params)
        with self.stub_client:
            result = self.deployer.create_changeset(stack_name, template,
                                                    parameters, capabilities,
                                                    role_arn,
                                                    notification_arns,
                                                    s3_uploader, [])
            self.assertEqual(response["Id"], result.changeset_id)
            self.assertEqual("CREATE", result.changeset_type)

        # Case 2: Stack exists. We are updating it
        self.deployer.has_stack.return_value = True
        self.stub_client.add_response(
            "get_template_summary", {
                "Parameters": [{
                    "ParameterKey": parameter["ParameterKey"]
                } for parameter in parameters]
            }, {"StackName": stack_name})
        expected_params["ChangeSetType"] = "UPDATE"
        expected_params["Parameters"] = parameters
        # template has new parameter but should not be included in
        # expected_params as no previous value
        parameters = list(parameters) + \
            [{"ParameterKey": "New", "UsePreviousValue": True}]
        self.stub_client.add_response("create_change_set", response,
                                      expected_params)
        with self.stub_client:
            result = self.deployer.create_changeset(stack_name, template,
                                                    parameters, capabilities,
                                                    role_arn,
                                                    notification_arns,
                                                    s3_uploader, [])
            self.assertEqual(response["Id"], result.changeset_id)
            self.assertEqual("UPDATE", result.changeset_type)

    def test_create_changeset_exception(self):
        stack_name = "stack_name"
        template = "template"
        parameters = [{
            "ParameterKey": "Key1",
            "ParameterValue": "Value",
            "UsePreviousValue": True
        }]
        capabilities = ["capabilities"]
        role_arn = "arn:aws:iam::1234567890:role"
        notification_arns = ["arn:aws:sns:region:1234567890:notify"]
        s3_uploader = None
        tags = [{"Key": "key1", "Value": "val1"}]

        self.deployer.has_stack = mock.Mock()
        self.deployer.has_stack.return_value = False

        self.stub_client.add_client_error('create_change_set',
                                          "Somethign is wrong",
                                          "Service is bad")
        with self.stub_client:
            with self.assertRaises(botocore.exceptions.ClientError):
                self.deployer.create_changeset(stack_name, template,
                                               parameters, capabilities,
                                               role_arn, notification_arns,
                                               None, tags)

    def test_execute_changeset(self):
        stack_name = "stack_name"
        changeset_id = "changeset_id"

        expected_params = {
            "ChangeSetName": changeset_id,
            "StackName": stack_name,
            "DisableRollback": False
        }

        self.stub_client.add_response("execute_change_set", {},
                                      expected_params)
        with self.stub_client:
            self.deployer.execute_changeset(changeset_id, stack_name)

    def test_execute_changeset_disable_rollback(self):
        stack_name = "stack_name"
        changeset_id = "changeset_id"
        disable_rollback = True

        expected_params = {
            "ChangeSetName": changeset_id,
            "StackName": stack_name,
            "DisableRollback": disable_rollback
        }

        self.stub_client.add_response("execute_change_set", {},
                                      expected_params)
        with self.stub_client:
            self.deployer.execute_changeset(changeset_id, stack_name,
                                            disable_rollback)

    def test_execute_changeset_exception(self):
        stack_name = "stack_name"
        changeset_id = "changeset_id"

        self.stub_client.add_client_error('execute_change_set',
                                          "Somethign is wrong",
                                          "Service is bad")
        with self.stub_client:
            with self.assertRaises(botocore.exceptions.ClientError):
                self.deployer.execute_changeset(changeset_id, stack_name)

    def test_create_and_wait_for_changeset_successful(self):
        stack_name = "stack_name"
        template = "template"
        parameters = [{
            "ParameterKey": "Key1",
            "ParameterValue": "Value",
            "UsePreviousValue": True
        }]
        capabilities = ["capabilities"]
        changeset_id = "changeset id"
        changeset_type = "changeset type"
        role_arn = "arn:aws:iam::1234567890:role"
        notification_arns = ["arn:aws:sns:region:1234567890:notify"]
        s3_uploader = None
        tags = [{"Key": "key1", "Value": "val1"}]

        self.deployer.create_changeset = mock.Mock()
        self.deployer.create_changeset.return_value = ChangeSetResult(
            changeset_id, changeset_type)

        self.deployer.wait_for_changeset = mock.Mock()

        result = self.deployer.create_and_wait_for_changeset(
            stack_name, template, parameters, capabilities, role_arn,
            notification_arns, s3_uploader, tags)
        self.assertEqual(result.changeset_id, changeset_id)
        self.assertEqual(result.changeset_type, changeset_type)

    def test_create_and_wait_for_changeset_error_waiting_for_changeset(self):
        stack_name = "stack_name"
        template = "template"
        parameters = [{
            "ParameterKey": "Key1",
            "ParameterValue": "Value",
            "UsePreviousValue": True
        }]
        capabilities = ["capabilities"]
        changeset_id = "changeset id"
        changeset_type = "changeset type"
        role_arn = "arn:aws:iam::1234567890:role"
        notification_arns = ["arn:aws:sns:region:1234567890:notify"]
        s3_uploader = None
        tags = [{"Key": "key1", "Value": "val1"}]

        self.deployer.create_changeset = mock.Mock()
        self.deployer.create_changeset.return_value = ChangeSetResult(
            changeset_id, changeset_type)

        self.deployer.wait_for_changeset = mock.Mock()
        self.deployer.wait_for_changeset.side_effect = RuntimeError

        with self.assertRaises(RuntimeError):
            result = self.deployer.create_and_wait_for_changeset(
                stack_name, template, parameters, capabilities, role_arn,
                notification_arns, s3_uploader, tags)

    def test_wait_for_changeset_no_changes(self):
        stack_name = "stack_name"
        changeset_id = "changeset-id"

        mock_client = mock.Mock()
        mock_deployer = Deployer(mock_client)
        mock_waiter = mock.Mock()
        mock_client.get_waiter.return_value = mock_waiter

        response = {
            "Status": "FAILED",
            "StatusReason": "The submitted information didn't contain changes."
        }

        waiter_error = botocore.exceptions.WaiterError(name="name",
                                                       reason="reason",
                                                       last_response=response)
        mock_waiter.wait.side_effect = waiter_error

        with self.assertRaises(exceptions.ChangeEmptyError):
            mock_deployer.wait_for_changeset(changeset_id, stack_name)

        waiter_config = {'Delay': 5}
        mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id,
                                                 StackName=stack_name,
                                                 WaiterConfig=waiter_config)

        mock_client.get_waiter.assert_called_once_with(
            "change_set_create_complete")

    def test_wait_for_changeset_no_changes_with_another_error_msg(self):
        stack_name = "stack_name"
        changeset_id = "changeset-id"

        mock_client = mock.Mock()
        mock_deployer = Deployer(mock_client)
        mock_waiter = mock.Mock()
        mock_client.get_waiter.return_value = mock_waiter

        response = {
            "Status": "FAILED",
            "StatusReason": "No updates are to be performed"
        }

        waiter_error = botocore.exceptions.WaiterError(name="name",
                                                       reason="reason",
                                                       last_response=response)
        mock_waiter.wait.side_effect = waiter_error

        with self.assertRaises(exceptions.ChangeEmptyError):
            mock_deployer.wait_for_changeset(changeset_id, stack_name)

        waiter_config = {'Delay': 5}
        mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id,
                                                 StackName=stack_name,
                                                 WaiterConfig=waiter_config)

        mock_client.get_waiter.assert_called_once_with(
            "change_set_create_complete")

    def test_wait_for_changeset_failed_to_create_changeset(self):
        stack_name = "stack_name"
        changeset_id = "changeset-id"

        mock_client = mock.Mock()
        mock_deployer = Deployer(mock_client)
        mock_waiter = mock.Mock()
        mock_client.get_waiter.return_value = mock_waiter

        response = {"Status": "FAILED", "StatusReason": "some reason"}

        waiter_error = botocore.exceptions.WaiterError(name="name",
                                                       reason="reason",
                                                       last_response=response)
        mock_waiter.wait.side_effect = waiter_error

        with self.assertRaises(RuntimeError):
            mock_deployer.wait_for_changeset(changeset_id, stack_name)

        waiter_config = {'Delay': 5}
        mock_waiter.wait.assert_called_once_with(ChangeSetName=changeset_id,
                                                 StackName=stack_name,
                                                 WaiterConfig=waiter_config)

        mock_client.get_waiter.assert_called_once_with(
            "change_set_create_complete")

    def test_wait_for_execute_no_changes(self):
        stack_name = "stack_name"
        changeset_type = "CREATE"

        mock_client = mock.Mock()
        mock_deployer = Deployer(mock_client)
        mock_waiter = mock.Mock()
        mock_client.get_waiter.return_value = mock_waiter

        waiter_error = botocore.exceptions.WaiterError(name="name",
                                                       reason="reason",
                                                       last_response={})
        mock_waiter.wait.side_effect = waiter_error

        with self.assertRaises(exceptions.DeployFailedError):
            mock_deployer.wait_for_execute(stack_name, changeset_type)

        waiter_config = {
            'Delay': 30,
            'MaxAttempts': 120,
        }
        mock_waiter.wait.assert_called_once_with(StackName=stack_name,
                                                 WaiterConfig=waiter_config)

        mock_client.get_waiter.assert_called_once_with("stack_create_complete")