-
Notifications
You must be signed in to change notification settings - Fork 167
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added support for loading files as parameter values, including interpolation #185
Merged
Merged
Changes from 9 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
31368aa
Added support for loading files as parameter values, including codecs…
ttarhan b77bb85
Merge branch 'master' of https://github.com/remind101/stacker into fi…
ttarhan 4aad59c
Upgraded to newer version of troposphere for #188
ttarhan c09e179
Merge branch 'master' of https://github.com/remind101/stacker into fi…
ttarhan c30e614
Added tests, and cleaned up, the file translator
ttarhan ecdd4e6
Merge branch 'master' of https://github.com/remind101/stacker into fi…
ttarhan 27b7ff5
Moving the file translator to a file lookup
ttarhan fac51d2
Merged upstream changes
ttarhan cc332f7
Updated docs for the file lookup
ttarhan 55e5452
Removed unused setUp method
ttarhan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
import re | ||
import base64 | ||
|
||
from ...util import read_value_from_path | ||
from troposphere import GenericHelperFn, Base64 | ||
|
||
TYPE_NAME = "file" | ||
|
||
|
||
def handler(value, **kwargs): | ||
"""Translate a filename into the file contents, optionally encoding or interpolating the input | ||
|
||
Fields should use the following format: | ||
|
||
<codec>:<path> | ||
|
||
For example: | ||
|
||
# We've written a file to /some/path: | ||
$ echo "hello there" > /some/path | ||
|
||
# In stacker we would reference the contents of this file with the following | ||
conf_key: ${file plain:file://some/path} | ||
|
||
# The above would resolve to | ||
conf_key: hello there | ||
|
||
# Or, if we used wanted a base64 encoded copy of the file data | ||
conf_key: ${file base64:file://some/path} | ||
|
||
# The above would resolve to | ||
conf_key: aGVsbG8gdGhlcmUK | ||
|
||
Supported codecs: | ||
- plain | ||
- base64 - encode the plain text file at the given path with base64 prior | ||
to returning it | ||
- parameterized - the same as plain, but additionally supports | ||
referencing template parameters to create userdata that's supplemented | ||
with information from the template, as is commonly needed in EC2 | ||
UserData. For example, given a template parameter of BucketName, the | ||
file could contain the following text: | ||
|
||
#!/bin/sh | ||
aws s3 sync s3://{{BucketName}}/somepath /somepath | ||
|
||
and then you could use something like this in the YAML config file: | ||
|
||
UserData: ${file parameterized:/path/to/file} | ||
|
||
resulting in the UserData parameter being defined as: | ||
|
||
{ "Fn::Join" : ["", [ | ||
"#!/bin/sh\naws s3 sync s3://", | ||
{"Ref" : "BucketName"}, | ||
"/somepath /somepath" | ||
]] } | ||
|
||
- parameterized-b64 - the same as parameterized, with the results additionally | ||
wrapped in { "Fn::Base64": ... } , which is what you actually need for | ||
EC2 UserData | ||
|
||
When using parameterized-b64 for UserData, you should use a variable defined | ||
as such: | ||
|
||
from troposphere import AWSHelperFn | ||
|
||
"UserData": { | ||
"type": AWSHelperFn, | ||
"description": "Instance user data", | ||
"default": Ref("AWS::NoValue") | ||
} | ||
|
||
and then assign UserData in a LaunchConfiguration or Instance to self.get_variables()["UserData"]. | ||
Note that we use AWSHelperFn as the type because the parameterized-b64 codec returns either a | ||
Base64 or a GenericHelperFn troposphere object | ||
""" | ||
|
||
try: | ||
codec, path = value.split(":", 1) | ||
except ValueError: | ||
raise TypeError( | ||
"File value must be of the format" | ||
" \"<codec>:<path>\" (got %s)" % (value) | ||
) | ||
|
||
value = read_value_from_path(path) | ||
|
||
return CODECS[codec](value) | ||
|
||
|
||
def parameterized_codec(raw, b64): | ||
pattern = re.compile(r'{{(\w+)}}') | ||
|
||
parts = [] | ||
s_index = 0 | ||
|
||
for match in pattern.finditer(raw): | ||
parts.append(raw[s_index:match.start()]) | ||
parts.append({"Ref": match.group(1)}) | ||
s_index = match.end() | ||
|
||
parts.append(raw[s_index:]) | ||
result = {"Fn::Join": ["", parts]} | ||
|
||
# Note, since we want a raw JSON object (not a string) output in the template, | ||
# we wrap the result in GenericHelperFn (not needed if we're using Base64) | ||
return Base64(result) if b64 else GenericHelperFn(result) | ||
|
||
|
||
CODECS = { | ||
"plain": lambda x: x, | ||
"base64": base64.b64encode, | ||
"parameterized": lambda x: parameterized_codec(x, False), | ||
"parameterized-b64": lambda x: parameterized_codec(x, True) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import unittest | ||
import mock | ||
import base64 | ||
import troposphere | ||
|
||
from stacker.lookups.handlers.file import parameterized_codec, handler | ||
|
||
|
||
class TestFileTranslator(unittest.TestCase): | ||
def setUp(self): | ||
pass | ||
|
||
def test_parameterized_codec_b64(self): | ||
expected = {'Fn::Base64': {'Fn::Join': ['', ['Test ', {'Ref': 'Interpolation'}, ' Here']]}} | ||
self.assertEqual(expected, parameterized_codec('Test {{Interpolation}} Here', True).data) | ||
|
||
def test_parameterized_codec_plain(self): | ||
expected = {'Fn::Join': ['', ['Test ', {'Ref': 'Interpolation'}, ' Here']]} | ||
self.assertEqual(expected, parameterized_codec('Test {{Interpolation}} Here', False).data) | ||
|
||
def test_file_loaded(self): | ||
with mock.patch('stacker.lookups.handlers.file.read_value_from_path', return_value='') as amock: | ||
handler('plain:file://tmp/test') | ||
amock.assert_called_with('file://tmp/test') | ||
|
||
def test_handler_plain(self): | ||
expected = 'Hello, world' | ||
with mock.patch('stacker.lookups.handlers.file.read_value_from_path', return_value=expected): | ||
out = handler('plain:file://tmp/test') | ||
self.assertEqual(expected, out) | ||
|
||
def test_handler_b64(self): | ||
expected = 'Hello, world' | ||
with mock.patch('stacker.lookups.handlers.file.read_value_from_path', return_value=expected): | ||
out = handler('base64:file://tmp/test') | ||
self.assertEqual(expected, base64.b64decode(out)) | ||
|
||
def test_handler_parameterized(self): | ||
expected = 'Hello, world' | ||
with mock.patch('stacker.lookups.handlers.file.read_value_from_path', return_value=expected): | ||
out = handler('parameterized:file://tmp/test') | ||
self.assertEqual(troposphere.GenericHelperFn, type(out)) | ||
|
||
def test_handler_parameterized_b64(self): | ||
expected = 'Hello, world' | ||
with mock.patch('stacker.lookups.handlers.file.read_value_from_path', return_value=expected): | ||
out = handler('parameterized-b64:file://tmp/test') | ||
self.assertEqual(troposphere.Base64, type(out)) | ||
|
||
def test_unknown_codec(self): | ||
expected = 'Hello, world' | ||
with mock.patch('stacker.lookups.handlers.file.read_value_from_path', return_value=expected): | ||
with self.assertRaises(KeyError): | ||
handler('bad:file://tmp/test') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can remove this, since you pass.