Skip to content

Commit

Permalink
Merge pull request #64 from ryansb/template-vars
Browse files Browse the repository at this point in the history
Add support for specifying Jinja2 vars
  • Loading branch information
flomotlik authored Sep 27, 2017
2 parents 107b013 + cc607fb commit 45de0bf
Show file tree
Hide file tree
Showing 10 changed files with 97 additions and 18 deletions.
3 changes: 2 additions & 1 deletion docs/commands/change.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ After the change was submitted a description of the changes will be printed. For
| --region REGION | The AWS region to use. |
| --parameters KEY1=Value KEY2=Value2 | Add a parameter. Repeat for multiple parameters |
| --tags KEY1=Value KEY2=Value2 | Add a stack tag. Repeat for multipe tags |
| --vars KEY1=Value KEY2=Value2 | Add a variable for use in Jinja2 templates. |
| --capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM | Set one or multiple stack capabilities |
| --config-file (-c) CONFIG_FILE | Set the config file to use |
| --config-file (-c) CONFIG_FILE | Set the config file to use |
3 changes: 2 additions & 1 deletion docs/commands/diff.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ Together with [`formica describe`](describe.md) you can understand exactly what
| --stack (-s) STACK | The stack you want to create. |
| --profile PROFILE | The AWS profile to use. |
| --region REGION | The AWS region to use. |
| --config-file (-c) CONFIG_FILE | Set the config file to use |
| --vars KEY1=Value KEY2=Value2 | Add a variable for use in Jinja2 templates. |
| --config-file (-c) CONFIG_FILE | Set the config file to use |
9 changes: 5 additions & 4 deletions docs/commands/new.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ After the change was submitted a description of the changes will be printed. For
| --stack (-s) STACK | The stack you want to create. |
| --profile PROFILE | The AWS profile to use. |
| --region REGION | The AWS region to use. |
| --parameters KEY1=Value KEY2=Value2 | Add a parameter. Repeat for multiple parameters |
| --tags KEY1=Value KEY2=Value2 | Add a stack tag. Repeat for multipe tags |
| --capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM | Set one or multiple stack capabilities |
| --config-file (-c) CONFIG_FILE | Set the config file to use |
| --parameters KEY1=Value KEY2=Value2 | Add a parameter. Repeat for multiple parameters |
| --tags KEY1=Value KEY2=Value2 | Add a stack tag. Repeat for multipe tags |
| --vars KEY1=Value KEY2=Value2 | Add a variable for use in Jinja2 templates. |
| --capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM | Set one or multiple stack capabilities |
| --config-file (-c) CONFIG_FILE | Set the config file to use |
4 changes: 3 additions & 1 deletion docs/config-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,6 @@ capabilities:
- CAPABILITY_NAMED_IAM
region: us-east-1
profile: production
```
vars:
domain: flomotlik.me
```
35 changes: 34 additions & 1 deletion docs/template-files.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,39 @@ To be able to change files dynamically we use [Jinja2](http://jinja.pocoo.org/do
engine. It allows you to iterate over values, define variables or use filters to change text, e.g. when a value has to
be alphanumeric and you want to strip special characters.

## Enhancing Templates

When using Formica, there are three places that variables can be set. The first is to use inline `{% set foo = "bar" %}`
Jinja2 syntax. More commonly, you'll want to vary the values based on which stack you're deploying. All commands that
take parameters and stack changes (`diff`, `new`, `change`, etc) accept a `--vars foo=bar baz=box` syntax. More
complex or nested values can be specified in a stack config file:

```
stack: my-cool-resources
vars:
foo: bar
complex_thing:
a:
- 1
- 2
- 3
b: something
max_size: 10
```

Any `--vars` CLI options will override the stack config, similar to parameters. You can then loop or base
conditionals on these custom values.

```
{% for i in complex_thing.a %}myrsrc{{ i }}{% endfor %}
{% if max_size > 3 %}
{{ max_size }}
{% else %}
3
{% endif %}
```

## Available AWS Resources

As Formica uses the official CloudFormation syntax directly all CloudFormation resources or options are supported.
Expand Down Expand Up @@ -166,4 +199,4 @@ Resources:
Code:
Zipfile: {{ code | code_escape }}
Runtime: "python2.7"
```
```
21 changes: 16 additions & 5 deletions formica/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
'parameters': dict,
'region': str,
'profile': str,
'capabilities': list
'capabilities': list,
'vars': dict,
}


Expand Down Expand Up @@ -57,6 +58,8 @@ def main(cli_args):

# Template Command Arguments
template_parser = subparsers.add_parser('template', description='Print the current template')
add_config_file_argument(template_parser)
add_stack_variables_argument(template_parser)
template_parser.add_argument('-y', '--yaml', help="print output as yaml", action="store_true")
template_parser.set_defaults(func=template)

Expand All @@ -74,6 +77,7 @@ def main(cli_args):
add_stack_tags_argument(new_parser)
add_capabilities_argument(new_parser)
add_config_file_argument(new_parser)
add_stack_variables_argument(new_parser)
new_parser.set_defaults(func=new)

# Change Command Arguments
Expand All @@ -84,6 +88,7 @@ def main(cli_args):
add_stack_tags_argument(change_parser)
add_capabilities_argument(change_parser)
add_config_file_argument(change_parser)
add_stack_variables_argument(change_parser)
change_parser.set_defaults(func=change)

# Deploy Command Arguments
Expand All @@ -105,6 +110,7 @@ def main(cli_args):
add_aws_arguments(diff_parser)
add_stack_argument(diff_parser)
add_config_file_argument(diff_parser)
add_stack_variables_argument(diff_parser)
diff_parser.set_defaults(func=diff)

# Resources Command Arguments
Expand Down Expand Up @@ -175,6 +181,11 @@ def add_stack_parameters_argument(parser):
nargs='*', action=SplitEqualsAction, metavar='KEY=Value')


def add_stack_variables_argument(parser):
parser.add_argument('--vars', help='Add one or multiple Jinja2 variables',
nargs='*', action=SplitEqualsAction, metavar='KEY=Value')


def add_stack_tags_argument(parser):
parser.add_argument('--tags', help='Add one or multiple stack tags', nargs='*',
action=SplitEqualsAction, metavar='KEY=Value')
Expand All @@ -190,7 +201,7 @@ def add_config_file_argument(parser):


def template(args):
loader = Loader()
loader = Loader(variables=args.vars)
loader.load()
if args.yaml:
logger.info(
Expand Down Expand Up @@ -221,7 +232,7 @@ def stacks(args):

@requires_stack
def diff(args):
Diff(AWS.current_session()).run(args.stack)
Diff(AWS.current_session()).run(args.stack, args.vars)


@requires_stack
Expand Down Expand Up @@ -254,7 +265,7 @@ def resources(args):
@requires_stack
def change(args):
client = AWS.current_session().client('cloudformation')
loader = Loader()
loader = Loader(variables=args.vars)
loader.load()

change_set = ChangeSet(stack=args.stack, client=client)
Expand Down Expand Up @@ -284,7 +295,7 @@ def remove(args):
@requires_stack
def new(args):
client = AWS.current_session().client('cloudformation')
loader = Loader()
loader = Loader(variables=args.vars)
loader.load()
logger.info('Creating change set for new stack, ...')
change_set = ChangeSet(stack=args.stack, client=client)
Expand Down
4 changes: 2 additions & 2 deletions formica/diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,14 @@ class Diff(AWSBase):
def __init__(self, session):
super(Diff, self).__init__(session)

def run(self, stack):
def run(self, stack, variables=None):
client = self.cf_client()

result = client.get_template(
StackName=stack,
)

loader = Loader()
loader = Loader(variables=variables)
loader.load()
deployed_template = convert(result['TemplateBody'])
if isinstance(deployed_template, str):
Expand Down
10 changes: 9 additions & 1 deletion formica/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,22 @@ def merge(self, template, file):
for module in template[key]:
module_path = module.get('path')
file_name = module.get('template', '*')
vars = module.get('vars', {})
vars = self.merge_variables(module.get('vars', {}))
loader = Loader(self.path + '/' + module_path, file_name, vars)
loader.load()
self.merge(loader.template_dictionary(), file=file_name)
else:
logger.info("Key '{}' in file {} is not valid".format(key, file))
sys.exit(1)

def merge_variables(self, module_vars):
merged_vars = {}
for k, v in module_vars.items():
merged_vars[k] = v
for k, v in self.variables.items():
merged_vars[k] = v
return merged_vars

def load(self):
files = []

Expand Down
15 changes: 13 additions & 2 deletions tests/unit/test_diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,22 @@ def test_request_returns_string(loader, client, diff, logger):
def test_diff_cli_call(mocker, session):
aws = mocker.patch('formica.cli.AWS')
aws.current_session.return_value = session
print(session)

diff = mocker.patch('formica.cli.Diff')

cli.main(['diff', '--stack', STACK])

diff.assert_called_with(session)
diff.return_value.run.assert_called_with(STACK)
diff.return_value.run.assert_called_with(STACK, mocker.ANY)


def test_diff_cli_with_vars(mocker, session):
aws = mocker.patch('formica.cli.AWS')
aws.current_session.return_value = session

diff = mocker.patch('formica.cli.Diff')

cli.main(['diff', '--stack', STACK, '--vars', 'abc=def'])

diff.assert_called_with(session)
diff.return_value.run.assert_called_with(STACK, {'abc': 'def'})
11 changes: 11 additions & 0 deletions tests/unit/test_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,17 @@ def test_supports_jinja_templates(load, tmpdir):
assert actual == {"Description": "Test"}


def test_supports_extra_jinja_vars(tmpdir):
load = Loader(variables={'test': 'bar'})
example = '{"Description": "{{ test | title }}"}'
with Path(tmpdir):
with open('test.template.json', 'w') as f:
f.write(example)
load.load()
actual = json.loads(load.template())
assert actual == {"Description": "Bar"}


def test_supports_resouce_command(load, tmpdir):
example = '{"Description": "{{ \'ABC%123.\' | resource }}"}'
with Path(tmpdir):
Expand Down

0 comments on commit 45de0bf

Please sign in to comment.