예제 #1
0
def test_validate_config_missing_template():
    config = Config({
        'Environment': 'dev',
        'Region': 'us-east-1',
        'StackName': 'Name'
    })
    with pytest.raises(ValidationError, match='Template not set'):
        config.validate(check_template=True)
예제 #2
0
def test_validate_config_with_template_url():
    config = Config({
        'Environment': 'dev',
        'Region': 'us-east-1',
        'StackName': 'Name',
        'Template': 'https://s3.amazonaws.com/notarealurl'
    })
    config.validate(check_template=True)
예제 #3
0
def test_validate_config_with_template_file():
    config = Config({
        'Environment': 'dev',
        'Region': 'us-east-1',
        'StackName': 'Name',
        # Uses this file as the template file as it's just an exists check
        'Template': __file__
    })
    config.validate(check_template=True)
예제 #4
0
def dev_config(all_config):
    config = Config({
        'Environment': 'dev',
        'Region': 'us-east-1',
        'Parameters': {
            'This': 'TheOther',
            'Extra': '/Company/{{ Region }}/{{ Environment }}/Extra'
        },
        'Capabilities': ['CAPABILITIES_NAMED_IAM']
    })
    config.set_parent(all_config)
    return config
예제 #5
0
def test_chained_commands(config):
    args = ['-p', 'dev', '-r', 'us-east-1',
            'build-lambda', '-s', 'src/', '-o', '.', '--runtime', 'python3.8',
            'upload', '-b', 'bucket', '-k', 'key',
            'deploy', '-e', 'env', '-c', 'config.yml']

    arg_config = Config({
        'Region': 'us-east-1',
        'Parameters': {
            'LambdaBucket': 'bucket',
            'LambdaKey': 'key'
        }
    })

    with patch('stackmanager.packager.build_lambda') as build_lambda:
        build_lambda.return_value = 'src.zip'
        with patch('stackmanager.cli.create_uploader') as create_uploader:
            with patch('stackmanager.cli.load_config') as load_config:
                load_config.return_value = config
                with patch('stackmanager.cli.create_runner') as create_runner:
                    runner = CliRunner()
                    result = runner.invoke(cli, args)

                    assert result.exit_code == 0
                    build_lambda.assert_called_once_with('src/', '.', 'python3.8', None)
                    create_uploader.assert_called_once_with('dev', 'us-east-1')
                    create_uploader.return_value.upload.assert_called_once_with('src.zip', 'bucket', 'key')
                    load_config.assert_called_once_with('config.yml', arg_config, 'env',
                                                        Template=None, Parameters=(), PreviousParameters=(),
                                                        ChangeSetName=None, ExistingChanges='ALLOW', AutoApply=False)
                    create_runner.assert_called_once_with('dev', config)
                    create_runner.return_value.deploy.assert_called_once()
예제 #6
0
def test_termination_protection_true():
    config = Config({
        'Environment': 'dev',
        'Region': 'us-east-1',
        'TerminationProtection': True
    })
    assert config.termination_protection is True
예제 #7
0
def test_auto_apply():
    config = Config({
        'Environment': 'dev',
        'Region': 'us-east-1',
        'AutoApply': True
    })
    assert config.auto_apply is True
예제 #8
0
def test_change_set_id():
    config = Config({
        'Environment': 'dev',
        'Region': 'us-east-1',
        'ChangeSetId': 'abc123'
    })
    assert config.change_set_id == 'abc123'
예제 #9
0
def test_change_set_name():
    config = Config({
        'Environment': 'dev',
        'Region': 'us-east-1',
        'ChangeSetName': 'TestChangeSet'
    })
    assert config.change_set_name == 'TestChangeSet'
예제 #10
0
def dev_config(all_config):
    config = Config({
        'Environment': 'dev',
        'Region': 'us-east-1',
        'Parameters': {
            'Extra': '/Company/{{ Region }}/{{ Environment }}/Extra',
            'Override': 'dev-value'
        },
        'Variables': {
            'EnvironmentCode': 'd'
        },
        'Capabilities': ['CAPABILITIES_NAMED_IAM'],
        'Tags': {
            'CostCenter': 200
        }
    })
    config.parent = all_config
    return config
예제 #11
0
def test_status(config):
    with patch('stackmanager.cli.load_config') as load_config:
        load_config.return_value = config
        with patch('stackmanager.cli.create_runner') as create_runner:

            cli_runner = CliRunner()
            result = cli_runner.invoke(cli, ['-p', 'dev', 'status', '-r', 'us-east-1', '-e', 'env', '-c', 'config.yml'])

            assert result.exit_code == 0
            load_config.assert_called_once_with('config.yml', Config({'Region': 'us-east-1'}), 'env', False)
            create_runner.assert_called_once_with('dev', config)
            create_runner.return_value.status.assert_called_once_with(7)
예제 #12
0
def test_reject_change_set_name(config):
    with patch('stackmanager.cli.load_config') as load_config:
        load_config.return_value = config
        with patch('stackmanager.cli.create_runner') as create_runner:
            cli_runner = CliRunner()
            result = cli_runner.invoke(cli, ['-r', 'us-east-1', 'reject', '-p', 'dev', '-e', 'env', '-c', 'config.yml',
                                             '--change-set-name', 'testchangeset'])

            assert result.exit_code == 0
            load_config.assert_called_once_with('config.yml', Config({'Region': 'us-east-1'}), 'env', False,
                                                ChangeSetName='testchangeset')
            create_runner.assert_called_once_with('dev', config)
            create_runner.return_value.reject_change_set.assert_called_once()
예제 #13
0
def load_config(config_file, environment, region, template, parameters):
    """
    Build hierarchy of configurations by loading multi-document config file.
    There must be a matching config for the environment name and region.
    :param str config_file: Path to config file
    :param str environment: Environment being updated
    :param str region: Region for the Stack
    :param str template: Override value for Template from command line
    :param list parameters: Override values for Parameters from the command line
    :return: Top of Config hierarchy
    :raises validationException: If config file not found, matching environment not found in config or config is invalid
    """
    arg_config = create_arg_config(environment, region, template, parameters)

    try:
        with open(config_file) as c:
            raw_configs = yaml.safe_load_all(c)
            all_config = None
            env_config = None
            for rc in raw_configs:
                config = Config(rc)
                if Config.is_all(config.environment):
                    all_config = config
                elif config.environment == environment and config.region == region:
                    env_config = config

            if not env_config:
                raise ValidationError(
                    f'Environment {environment} for {region} not found in {config_file}'
                )

            env_config.set_parent(all_config)
            arg_config.set_parent(env_config)

            # Validate before returning
            arg_config.validate()
            return arg_config
    except FileNotFoundError:
        raise ValidationError(f'Config file {config_file} not found')
예제 #14
0
def test_loader_dev_overrides():
    previous_parameters = ('Previous', 'LambdaKey')
    override_parameters = (('SSMKey', '/Other/{{ Environment }}/Key'),
                           ('Domain', 'notdev.example.com'),
                           ('Extra', 'OverrideDefault'), ('LambdaKey',
                                                          'd/e/f'))

    arg_config = Config({})
    arg_config.region = 'us-east-1'
    arg_config.add_parameters({
        'LambdaBucket': 'mybucket',
        'LambdaKey': 'a/b/c'
    })
    config = load_config(config_file(),
                         arg_config,
                         'dev',
                         False,
                         Template='integration/config.yaml',
                         Parameters=override_parameters,
                         PreviousParameters=previous_parameters,
                         ChangeSetName='TestChangeSet',
                         AutoApply=True)

    assert config.environment == 'dev'
    assert config.region == 'us-east-1'
    assert config.template == 'integration/config.yaml'
    assert config.parameters == {
        'Environment': 'dev',
        'SSMKey': '/Other/dev/Key',
        'Domain': 'notdev.example.com',
        'KeyId': 'guid1',
        'Extra': 'OverrideDefault',
        'LambdaBucket': 'mybucket',
        'LambdaKey': 'd/e/f',
        'Previous': '<<UsePreviousValue>>'
    }
    assert config.change_set_name == 'TestChangeSet'
    assert config.auto_apply is True
예제 #15
0
def test_loader_prod():
    config = load_config(config_file(), Config({'Region': 'us-east-2'}),
                         'prod', False)

    assert config.environment == 'prod'
    assert config.region == 'us-east-2'
    assert config.template == 'integration/template.yaml'
    assert config.parameters == {
        'Environment': 'prod',
        'SSMKey': '/Company/p/us-east-2/Key',
        'Domain': 'prod.example.com',
        'KeyId': 'guid4'
    }
    assert config.tags == {'Application': 'Example', 'Environment': 'prod'}
    assert config.capabilities == ['CAPABILITY_NAMED_IAM']
예제 #16
0
def create_arg_config(environment, region, template, parameters):
    """
    Create a Configuration from the command line arguments, used as top of hierarchy to
    optionally override template and parameters.
    :param str environment: Environment
    :param str region: Region to deploy
    :param str template: Override value for Template from command line
    :param list parameters: Override values for Parameters from the command line
    :return: Argument Config
    """
    raw_config = {'Environment': environment, 'Region': region}
    if template:
        raw_config['Template'] = template
    if parameters:
        raw_config['Parameters'] = dict(parameters)
    return Config(raw_config)
예제 #17
0
def test_get_output(config):
    with patch('stackmanager.cli.load_config') as load_config:
        load_config.return_value = config
        with patch('stackmanager.cli.create_runner') as create_runner:
            create_runner.return_value.configure_mock(**{'get_output.return_value': 'TestOutputValue'})

            cli_runner = CliRunner()
            result = cli_runner.invoke(cli, ['-p', 'dev', 'get-output', '-r', 'us-east-1', '-e', 'env', '-c',
                                             'config.yml', '-o', 'TestOutputKey'])

            assert result.exit_code == 0
            load_config.assert_called_once_with('config.yml', Config({'Region': 'us-east-1'}), 'env', False)
            create_runner.assert_called_once_with('dev', config)
            create_runner.return_value.get_output.assert_called_once_with('TestOutputKey')

            assert result.output == 'TestOutputValue\n'
예제 #18
0
def test_deploy(config):
    with patch('stackmanager.cli.load_config') as load_config:
        load_config.return_value = config
        with patch('stackmanager.cli.create_runner') as create_runner:
            cli_runner = CliRunner()
            result = cli_runner.invoke(cli, ['deploy', '-p', 'dev', '-r', 'us-east-1', '-e', 'env', '-c', 'config.yml',
                                             '-t', 'template.yml', '--parameter', 'foo', 'bar',
                                             '--parameter-use-previous', 'keep', '--change-set-name',
                                             'testchangeset', '--auto-apply'])

            assert result.exit_code == 0
            load_config.assert_called_once_with('config.yml', Config({'Region': 'us-east-1'}), 'env',
                                                Template='template.yml', Parameters=(('foo', 'bar'),),
                                                PreviousParameters=('keep',), ChangeSetName='testchangeset',
                                                ExistingChanges='ALLOW', AutoApply=True)
            create_runner.assert_called_once_with('dev', config)
            create_runner.return_value.deploy.assert_called_once()
예제 #19
0
def all_config():
    return Config({
        'Environment': 'all',
        'StackName': '{{ Environment }}-ExampleStack',
        'Template': 'template.yaml',
        'Parameters': {
            'Environment': '{{ Environment }}',
            'Thing': '/Company/{{ Environment }}/{{ Region }}/Thing',
            'This': 'That'
        },
        'Tags': {
            'Team':
            'Ops',
            'TestFunction':
            '{{ Region|replace("us-", "u")|replace("east-","e")|replace("west-","w") }}'
        },
        'Capabilities': ['CAPABILITIES_IAM']
    })
예제 #20
0
    def build_change_set_args(self):
        """
        Build dictionary of arguments for creating a ChangeSet
        :return: Dictionary of arguments based upon Config
        """
        args = {
            'StackName': self.config.stack_name,
            'ChangeSetName': self.change_set_name,
            'ChangeSetType': 'CREATE' if StackStatus.is_creatable(self.stack) else 'UPDATE',
            'Parameters': self.build_parameters(),
            'Tags': self.build_tags()
        }
        if self.config.capabilities:
            args['Capabilities'] = self.config.capabilities

        if Config.is_template_url(self.config.template):
            args['TemplateURL'] = self.config.template
        else:
            with open(self.config.template) as t:
                args['TemplateBody'] = t.read()

        return args
예제 #21
0
def config():
    return Config({
        'StackName':
        'TestStack',
        'Environment':
        'test',
        'Region':
        'us-east-1',
        'Parameters': {
            'Param1': 'Value1',
            'Param2': USE_PREVIOUS_VALUE
        },
        'Tags': {
            'Tag1': 'Value1'
        },
        'Capabilities': ['CAPABILITY_IAM'],
        'Template':
        os.path.join(os.path.dirname(__file__), 'template.yaml'),
        'ExistingChanges':
        'ALLOW',
        'ChangeSetName':
        'TestChangeSet'
    })
예제 #22
0
def create_changeset_runner(profile, region, change_set_id):
    """
    Create a runner for processing a changeset using it's id.
    This first describes the changeset to get the stack name, and then the runner can be created with a dummy config.
    :param profile: AWS Profile from command line
    :param region: AWS Region from command line
    :param change_set_id: Change Set identifier
    :return: Runner instance
    """
    session = boto3.Session(profile_name=profile, region_name=region)
    client = session.client('cloudformation')
    try:
        change_set = client.describe_change_set(ChangeSetName=change_set_id)
        config = Config({
            'Environment': 'unknown',
            'Region': session.region_name,
            'StackName': change_set['StackName'],
            'ChangeSetName': change_set['ChangeSetName']
        })
        return create_runner(profile, config)

    except client.exceptions.ChangeSetNotFoundException:
        raise ValidationError(f'ChangeSet {change_set_id} not found')
예제 #23
0
def test_is_all():
    assert Config.is_all('all')
    assert not Config.is_all('dev')
예제 #24
0
def test_auto_apply_default():
    config = Config({'Environment': 'dev', 'Region': 'us-east-1'})
    assert config.auto_apply is False
예제 #25
0
def test_termination_protection_default():
    config = Config({'Environment': 'dev', 'Region': 'us-east-1'})
    assert config.termination_protection is True
예제 #26
0
def config():
    return Config({})
예제 #27
0
def test_loader_missing_region():
    with pytest.raises(ValidationError,
                       match='Environment dev for us-west-1 not found in .*'):
        load_config(config_file(), Config({'Region': 'us-west-1'}), 'dev')
예제 #28
0
 def __init__(self, profile, region):
     self.config = Config({})
     self.config.region = region
     self.profile = profile
     self.zip_file = None