-
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
Self parse user data #306
Self parse user data #306
Conversation
the data file to base64 after it is processed. | ||
|
||
Args: | ||
raw_userdata (str of userdata): |
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.
Just str
instead of str of userdata
. Also, make sure you put the return statement in the docstring as well - see: http://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html
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.
Done.
variables = self.get_variables() | ||
parameters = self.get_cfn_parameters() | ||
|
||
for match in pattern.finditer(raw_user_data): |
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.
Lets go with the standard python string.Template
method of templating: https://docs.python.org/3/library/string.html#template-strings
Also, please double check that the parameters aren't availabe in get_variables
- seems strange, but worth double checking (@mhahn might know)
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.
-
I decided to stick to just using a regex because the standard delimiter in the Template strings is $ which is very commonly used in UserData for other purposes. We can subclass the Template class and define our own custom rules for the delimiters, but I would argue that what we have right now is cleaner.
-
Parameters are in get_variables, however, they are stored as CFNParameter objects. In my new commit I addressed this is a cleaner way.
start_index = match.end() | ||
|
||
user_data.append(raw_user_data[start_index:]) | ||
res = Join("", user_data) |
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.
Since this is just a template, and you're not doing anything w/ Ref, etc, you probably won't need any of this join logic - it'll just be a template.
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.
Done.
refs replaced with their resolved values. | ||
|
||
""" | ||
pattern = re.compile(r'{{([::|\w]+)}}') |
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.
One issue w/ building your own templating engine is that there's no way to escape this with the current implementation, right? Python actually has a second string formatting/templating system that you can use perhaps? https://docs.python.org/3/library/string.html#format-string-syntax
|
||
key = match.group(1) | ||
|
||
if key in variables: |
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.
I wouldn't usually check for the existence of a key - instead you should use try/except:
try:
v = variables[key]
except KeyError:
raise MissingVariable(self.name, key)
Then work with v for the rest of the logic, outside of the block.
|
||
res += raw_user_data[start_index:] | ||
|
||
return base64.b64encode(res) |
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.
It might be better to not encode this at all, letting the user choose whether to encode it either using base64 or the Base64 troposphere method. what do you think?
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.
I think that's fair. They could extend the script if they wanted to.
The way userdata templating works is fixed, it uses the default string template provided by python. The main problem with my previous attempt was the way I was passing in the userdata was as a plain string using the file lookup handler which was passing its output to the default output handler and causing all sorts of problems. Now, the function only requires the path and it reads the file itself. Example config file: test.yaml
Example template file: test.py
|
I can move this to stacker_blueprints, I think it may be a better fit. |
It supports referencing template variables to create userdata | ||
that's supplemented with information from the data, as commonly | ||
required when creating EC2 userdata files. Automatically, encodes | ||
the data file to base64 after it is processed. |
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.
I don't think this is true any longer, right?
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.
this indentation looks off
@@ -1,11 +1,13 @@ | |||
import copy | |||
import hashlib | |||
import logging | |||
from string import Template as StringTemplate |
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.
I would just use import string
here, and then use Template as string.Template
later
MissingVariable: Raised when a variable is in the user_data that | ||
is not given in the blueprint | ||
|
||
""" |
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.
This appears to be over indented - I think the whole docstring block is actually.
the data file to base64 after it is processed. | ||
|
||
Args: | ||
raw_user_data (str): the user data with the cloud-init info |
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.
missing some Args here.
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.
looks good overall
It supports referencing template variables to create userdata | ||
that's supplemented with information from the data, as commonly | ||
required when creating EC2 userdata files. Automatically, encodes | ||
the data file to base64 after it is processed. |
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.
this indentation looks off
@@ -220,7 +222,48 @@ def resolve_variable(var_name, var_def, provided_variable, blueprint_name): | |||
return value | |||
|
|||
|
|||
def parse_user_data(variables, raw_user_data, blueprint_name): | |||
"""Translate a userdata file to into the file contents. |
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.
this sentence doesn't make sense
|
||
try: | ||
res = template.substitute(variable_values) | ||
except Exception as e: |
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.
we should be catching the exact exception that gets raised
…r_data Keeping branch up to date with release-1.0
@@ -219,23 +220,28 @@ def resolve_variable(var_name, var_def, provided_variable, blueprint_name): | |||
|
|||
|
|||
def parse_user_data(variables, raw_user_data, blueprint_name): | |||
"""Translate a userdata file to into the file contents. | |||
"""Parses the user data file |
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.
Maybe Parse the given user data and render it as a template
|
||
res = "" | ||
|
||
try: | ||
res = template.substitute(variable_values) | ||
except Exception as e: | ||
raise MissingVariable(blueprint_name, e) | ||
except ValueError: |
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.
how does this get raised? Not sure I understand from the exception, seems similar to the MissingVariable?
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.
This gets raised when the placeholder name for the variable in the template is actually invalid. E.g ${100} raises a ValueError because the placeholder name (100) can not only be a number.
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.
This is also explained in the Raises section of the comments.
@@ -44,6 +54,14 @@ def __init__(self, blueprint_name, variable_name, *args, **kwargs): | |||
super(MissingVariable, self).__init__(message, *args, **kwargs) | |||
|
|||
|
|||
class InvalidUserdataParameter(Exception): |
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.
Where is this used?
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.
Minor changes requested, but this looks good to me.
is not valid. E.g ${100} would raise this. | ||
|
||
|
||
""" |
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.
This indentation is still off, should be flush with the start of the doc string.
MissingVariable: Raised when a variable is in the user_data that | ||
is not given in the blueprint | ||
|
||
InvalidUserdataPlaceholder: Raised when a placeholder name in user_data |
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.
user_data
-> raw_user_data
- this actually happens in multiple places in the doc string.
class InvalidUserdataPlaceholder(Exception): | ||
|
||
def __init__(self, blueprint_name, *args, **kwargs): | ||
message = "Could not parse userdata in blueprint \"%s\". " % ( |
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.
I'd include the text from the ValueError that raises this - would make it easier to track down the issues:
>>> t.substitute({"100": "blue", "a": "apple"})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/Cellar/python/2.7.12_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/string.py", line 176, in substitute
return self.pattern.sub(convert, self.template)
File "/usr/local/Cellar/python/2.7.12_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/string.py", line 173, in convert
self._invalid(mo)
File "/usr/local/Cellar/python/2.7.12_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/string.py", line 146, in _invalid
(lineno, colno))
ValueError: Invalid placeholder in string: line 2, col 1
The line # & col # seem useful. You can get that from .args
of the original error.
Self parse user data
This PR adds the ability to parse variables referenced in a userdata file.
UserData.yaml
stack.yaml
stack.py - somewhere where userdata needs to be passed in
this will convert all variables enclosed in {{ ... }} to their respective values and base64 encode the entire string.