Skip to content
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

Support for custom Jinja filters #477

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 46 additions & 5 deletions sceptre/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"""

from datetime import datetime
import glob
import imp
import logging
import os
Expand Down Expand Up @@ -37,7 +38,6 @@ class Template(object):

def __init__(self, path, sceptre_user_data):
self.logger = logging.getLogger(__name__)

self.path = path
self.sceptre_user_data = sceptre_user_data
self.name = os.path.basename(path).split(".")[0]
Expand Down Expand Up @@ -97,7 +97,7 @@ def _call_sceptre_handler(self):
# NB: this is a horrible hack...
relpath = os.path.relpath(self.path, os.getcwd()).split(os.path.sep)
relpaths_to_add = [
os.path.sep.join(relpath[:i+1])
os.path.sep.join(relpath[:i + 1])
for i in range(len(relpath[:-1]))
]
# Add any directory between the current working directory and where
Expand Down Expand Up @@ -270,8 +270,7 @@ def _create_bucket(self, region, bucket_name, connection_manager):
}
)

@staticmethod
def _render_jinja_template(template_dir, filename, jinja_vars):
def _render_jinja_template(self, template_dir, filename, jinja_vars):
"""
Renders a jinja template.

Expand All @@ -291,8 +290,50 @@ def _render_jinja_template(template_dir, filename, jinja_vars):
logger.debug("%s Rendering CloudFormation template", filename)
env = jinja2.Environment(
loader=jinja2.FileSystemLoader(template_dir),
undefined=jinja2.StrictUndefined
undefined=jinja2.StrictUndefined,
)
env.filters.update(self.jinja_filters)
template = env.get_template(filename)
body = template.render(**jinja_vars)
return body

@property
def jinja_filters(self):
"""
Returns cached jinja filters if already computed, or loads/caches
the filters from python files if this is the first time.

This (optional) feature relies on the environment variable
${SCEPTRE_JINJA_FILTER_ROOT} because we don't have easy access
from here to CLI parsing info or global configuration info.
"""
# give back cached jinja filters if available
if hasattr(self, '_jinja_filters'):
return self._jinja_filters
# load jinja filters if not already cached
env_var_name = 'SCEPTRE_JINJA_FILTER_ROOT'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should be reaching env_vars directly, can we read this as environment config for consistency

Copy link
Author

@mattvonrocketstein mattvonrocketstein Oct 19, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How exactly should that be done? Template() seems to only be instantiated with with scepre_user_data, not environment_config. See also this related issue.

Does a path from environment_config or stack_config actually help here? I think what is really needed is the exact value passed from the CLI argument --dir, because even given an environment or stack path, if env folders are nested then one couldn't be sure how far to go back up the directory tree to get to the sceptre root (i.e. the folder that holds config, template, hooks, resolvers, and by symmetry jinja_filters).

if env_var_name not in os.environ:
msg = '${} is not set, no extra jinja filters will be loaded'
self.logger.debug(msg.format(env_var_name))
else:
filter_dir = os.environ[env_var_name]
if not os.path.exists(filter_dir):
err = '${} is set, but directory does not exist!'
raise ValueError(err.format(env_var_name))
else:
msg = 'loading jinja filters from: {}'
self.logger.debug(msg.format(filter_dir))
self._jinja_filters = {}
for fpath in glob.glob(os.path.join(filter_dir, '*.py')):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally this would be done using entry points, as per the new hooks, resolves code

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an example of that in v1, or would this need backport from v2?

self.logger.debug(' loading filter: {}'.format(fpath))
mod = imp.load_source('dynamic_jinja_filters', fpath)
for name in dir(mod):
# ignore anything like private methods
if name.startswith('_'):
continue
else:
fxn = getattr(mod, name)
# ignore things that aren't callables
if callable(fxn):
self._jinja_filters[name] = fxn
return self._jinja_filters