Skip to content

Commit

Permalink
Merge pull request #227 from ramirantala/master
Browse files Browse the repository at this point in the history
Possibility to read parameters from file
  • Loading branch information
hjacobs committed Jun 8, 2016
2 parents 39047e1 + a9fa379 commit 29ec65e
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 7 deletions.
38 changes: 31 additions & 7 deletions senza/cli.py
Expand Up @@ -155,6 +155,7 @@ def convert(self, value, param, ctx):

region_option = click.option('--region', envvar='AWS_DEFAULT_REGION', metavar='AWS_REGION_ID',
help='AWS region ID (e.g. eu-west-1)')
parameter_file_option = click.option('--parameter-file', help='Config file for params', metavar='PATH')
output_option = click.option('-o', '--output', type=click.Choice(['text', 'json', 'tsv']), default='text',
help='Use alternative output format')
json_output_option = click.option('-o', '--output', type=click.Choice(['json', 'yaml']), default='json',
Expand Down Expand Up @@ -399,6 +400,23 @@ def parse_args(input, region, version, parameter, account_info):
return args


def read_parameter_file(parameter_file):
paras = []
try:
ymlfile = open(parameter_file, 'r')
except OSError:
raise click.UsageError('Can\'t read parameter file "{}"'.format(parameter_file))

try:
cfg = yaml.safe_load(ymlfile)
for key, val in cfg.items():
paras.append("{}={}".format(key, val))
except yaml.YAMLError as e:
raise click.UsageError('Error {}'.format(e))

return tuple(paras)


def get_region(region):
if not region:
region = GLOBAL_OPTIONS.get('region')
Expand Down Expand Up @@ -519,15 +537,16 @@ def list_stacks(region, stack_ref, all, output, w, watch):
@click.argument('version', callback=validate_version)
@click.argument('parameter', nargs=-1)
@region_option
@parameter_file_option
@click.option('--disable-rollback', is_flag=True, help='Disable Cloud Formation rollback on failure')
@click.option('--dry-run', is_flag=True, help='No-op mode: show what would be created')
@click.option('-f', '--force', is_flag=True, help='Ignore failing validation checks')
@click.option('-t', '--tag', help='Tags to associate with the stack.', multiple=True)
def create(definition, region, version, parameter, disable_rollback, dry_run, force, tag):
def create(definition, region, version, parameter, disable_rollback, dry_run, force, tag, parameter_file):
'''Create a new Cloud Formation stack from the given Senza definition file'''

region = get_region(region)
data = create_cf_template(definition, region, version, parameter, force)
data = create_cf_template(definition, region, version, parameter, force, parameter_file)

for tag_kv in tag:
try:
Expand Down Expand Up @@ -557,13 +576,14 @@ def create(definition, region, version, parameter, disable_rollback, dry_run, fo
@click.argument('version', callback=validate_version)
@click.argument('parameter', nargs=-1)
@region_option
@parameter_file_option
@click.option('--disable-rollback', is_flag=True, help='Disable Cloud Formation rollback on failure')
@click.option('--dry-run', is_flag=True, help='No-op mode: show what would be created')
@click.option('-f', '--force', is_flag=True, help='Ignore failing validation checks')
def update(definition, region, version, parameter, disable_rollback, dry_run, force):
def update(definition, region, version, parameter, disable_rollback, dry_run, force, parameter_file):
'''Update an existing Cloud Formation stack from the given Senza definition file'''
region = get_region(region)
data = create_cf_template(definition, region, version, parameter, force)
data = create_cf_template(definition, region, version, parameter, force, parameter_file)
cf = boto3.client('cloudformation', region)

with Action('Updating Cloud Formation stack {}..'.format(data['StackName'])) as act:
Expand All @@ -582,17 +602,21 @@ def update(definition, region, version, parameter, disable_rollback, dry_run, fo
@click.argument('version', callback=validate_version)
@click.argument('parameter', nargs=-1)
@region_option
@parameter_file_option
@json_output_option
@click.option('-f', '--force', is_flag=True, help='Ignore failing validation checks')
def print_cfjson(definition, region, version, parameter, output, force):
def print_cfjson(definition, region, version, parameter, output, force, parameter_file):
'''Print the generated Cloud Formation template'''
region = get_region(region)
data = create_cf_template(definition, region, version, parameter, force)
data = create_cf_template(definition, region, version, parameter, force, parameter_file)
print_json(data['TemplateBody'], output)


def create_cf_template(definition, region, version, parameter, force):
def create_cf_template(definition, region, version, parameter, force, parameter_file):
region = get_region(region)
if parameter_file:
parameter_from_file = read_parameter_file(parameter_file)
parameter = parameter + parameter_from_file
check_credentials(region)
account_info = AccountArguments(region=region)
args = parse_args(definition, region, version, parameter, account_info)
Expand Down
50 changes: 50 additions & 0 deletions tests/test_cli.py
Expand Up @@ -40,6 +40,56 @@ def test_file_not_found():
assert '"notfound.yaml" not found' in result.output


def test_parameter_file_not_found():
data = {'SenzaInfo': {'StackName': 'test'}, 'Resources': {'MyQueue': {'Type': 'AWS::SQS::Queue'}}}

runner = CliRunner()

with runner.isolated_filesystem():
with open('myapp.yaml', 'w') as fd:
yaml.dump(data, fd)

result = runner.invoke(cli, ['print', '--parameter-file', 'notfound.yaml', 'myapp.yaml', '--region=myregion', '123'], catch_exceptions=False)

assert 'read parameter file "notfound.yaml"' in result.output


def test_parameter_file_found(monkeypatch):
monkeypatch.setattr('boto3.client', lambda *args: MagicMock())

data = {'SenzaInfo': {'StackName': 'test', 'Parameters': [{'ApplicationId': {'Description': 'Application ID from kio'}}]}, 'Resources': {'MyQueue': {'Type': 'AWS::SQS::Queue'}}}
param_data = { 'ApplicationId': 'test-app-id' }

runner = CliRunner()

with runner.isolated_filesystem():
with open('myapp.yaml', 'w') as fd:
yaml.dump(data, fd)
with open('parameter.yaml', 'w') as fd:
yaml.dump(param_data, fd)

result = runner.invoke(cli, ['print', '--parameter-file', 'parameter.yaml', 'myapp.yaml', '--region=myregion', '123'], catch_exceptions=False)

assert 'Generating Cloud Formation template.. OK' in result.output


def test_parameter_file_syntax_error():
data = {'SenzaInfo': {'StackName': 'test', 'Parameters': [{'ApplicationId': {'Description': 'Application ID from kio'}}]}, 'Resources': {'MyQueue': {'Type': 'AWS::SQS::Queue'}}}
param_data = "'ApplicationId': ["

runner = CliRunner()

with runner.isolated_filesystem():
with open('myapp.yaml', 'w') as fd:
yaml.dump(data, fd)
with open('parameter.yaml', 'w') as fd:
fd.write(param_data)

result = runner.invoke(cli, ['print', '--parameter-file', 'parameter.yaml', 'myapp.yaml', '--region=myregion', '123'], catch_exceptions=False)

assert 'Error: Error while parsing a flow node' in result.output


def test_version():
runner = CliRunner()
result = runner.invoke(cli, ['--version'])
Expand Down

0 comments on commit 29ec65e

Please sign in to comment.