Skip to content

Commit

Permalink
[Resolves #1139] Provide useful info on invalid jinja file (#1142)
Browse files Browse the repository at this point in the history
The jinja API[1] says it will raise a TemplateNotFound exception
when a template does not exist however that does not seem to be
happening so we need to check before attempting to render the
template.

[1] http://code.nabla.net/doc/jinja2/api/jinja2/environment/jinja2.environment.Environment.html#jinja2.environment.Environment.get_template
  • Loading branch information
zaro0508 committed Oct 28, 2021
1 parent 5c0ab39 commit c7988b9
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 53 deletions.
7 changes: 7 additions & 0 deletions sceptre/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,10 @@ class TemplateHandlerArgumentsInvalidError(SceptreException):
Error raised when the arguments passed to a Template Handler do not
adhere to the specified JSON schema.
"""


class TemplateNotFoundError(SceptreException):
"""
Error raised when a Template file is not found
"""
pass
11 changes: 4 additions & 7 deletions sceptre/template_handlers/file.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import os
# -*- coding: utf-8 -*-
import sceptre.template_handlers.helper as helper

from pathlib import Path
Expand Down Expand Up @@ -42,12 +42,9 @@ def handle(self):
with open(path) as template_file:
return template_file.read()
elif input_path.suffix in self.jinja_template_extensions:
return helper.render_jinja_template(
os.path.dirname(path),
os.path.basename(path),
{"sceptre_user_data": self.sceptre_user_data},
self.stack_group_config.get("j2_environment", {})
)
return helper.render_jinja_template(path,
{"sceptre_user_data": self.sceptre_user_data},
self.stack_group_config.get("j2_environment", {}))
elif input_path.suffix in self.python_template_extensions:
return helper.call_sceptre_handler(path,
self.sceptre_user_data)
Expand Down
22 changes: 12 additions & 10 deletions sceptre/template_handlers/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

from importlib.machinery import SourceFileLoader
from jinja2 import Environment, select_autoescape, FileSystemLoader, StrictUndefined
from sceptre.exceptions import TemplateSceptreHandlerError
from pathlib import Path
from sceptre.exceptions import TemplateSceptreHandlerError, TemplateNotFoundError
from sceptre.config import strategies

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -46,7 +47,7 @@ def call_sceptre_handler(path, sceptre_user_data):
)

if not os.path.isfile(path):
raise IOError("No such file or directory: '%s'", path)
raise TemplateNotFoundError("No such template file: '%s'", path)

module = SourceFileLoader(path, path).load_module()

Expand Down Expand Up @@ -104,17 +105,15 @@ def _print_frame(filename, line, fcn, line_text):
)


def render_jinja_template(template_dir, filename, jinja_vars, j2_environment):
def render_jinja_template(path, jinja_vars, j2_environment):
"""
Renders a jinja template.
Sceptre supports passing sceptre_user_data to JSON and YAML
CloudFormation templates using Jinja2 templating.
:param template_dir: The directory containing the template.
:type template_dir: str
:param filename: The name of the template file.
:type filename: str
:param path: The path to the template file.
:type path: str
:param jinja_vars: Dict of variables to render into the template.
:type jinja_vars: dict
:param j2_environment: The jinja2 environment.
Expand All @@ -123,22 +122,25 @@ def render_jinja_template(template_dir, filename, jinja_vars, j2_environment):
:returns: The body of the CloudFormation template.
:rtype: str
"""
logger.debug("%s Rendering CloudFormation template", filename)
path = Path(path)
if not path.exists():
raise TemplateNotFoundError("No such template file: '%s'", path)

logger.debug("%s Rendering CloudFormation template", path)
default_j2_environment_config = {
"autoescape": select_autoescape(
disabled_extensions=('j2',),
default=True,
),
"loader": FileSystemLoader(template_dir),
"loader": FileSystemLoader(path.parent),
"undefined": StrictUndefined
}
j2_environment_config = strategies.dict_merge(
default_j2_environment_config, j2_environment
)
j2_environment = Environment(**j2_environment_config)

template = j2_environment.get_template(filename)
template = j2_environment.get_template(path.name)

body = template.render(**jinja_vars)
return body
10 changes: 3 additions & 7 deletions sceptre/template_handlers/http.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
import pathlib
import os
import requests
import tempfile
import sceptre.template_handlers.helper as helper
Expand Down Expand Up @@ -52,12 +51,9 @@ def handle(self):
f.seek(0)
f.read()
if path.suffix in self.jinja_template_extensions:
template = helper.render_jinja_template(
os.path.dirname(f.name),
os.path.basename(f.name),
{"sceptre_user_data": self.sceptre_user_data},
self.stack_group_config.get("j2_environment", {})
)
template = helper.render_jinja_template(f.name,
{"sceptre_user_data": self.sceptre_user_data},
self.stack_group_config.get("j2_environment", {}))
elif path.suffix in self.python_template_extensions:
template = helper.call_sceptre_handler(
f.name,
Expand Down
13 changes: 4 additions & 9 deletions sceptre/template_handlers/s3.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import logging
# -*- coding: utf-8 -*-
import pathlib
import os
import tempfile
import sceptre.template_handlers.helper as helper

Expand All @@ -17,7 +16,6 @@ class S3(TemplateHandler):
"""

def __init__(self, *args, **kwargs):
self.logger = logging.getLogger(__name__)
super(S3, self).__init__(*args, **kwargs)

def schema(self):
Expand Down Expand Up @@ -56,12 +54,9 @@ def handle(self):
f.seek(0)
f.read()
if path.suffix in jinja_template_suffix:
template = helper.render_jinja_template(
os.path.dirname(f.name),
os.path.basename(f.name),
{"sceptre_user_data": self.sceptre_user_data},
self.stack_group_config.get("j2_environment", {})
)
template = helper.render_jinja_template(f.name,
{"sceptre_user_data": self.sceptre_user_data},
self.stack_group_config.get("j2_environment", {}))
elif path.suffix in python_template_suffix:
template = helper.call_sceptre_handler(
f.name,
Expand Down
4 changes: 2 additions & 2 deletions tests/test_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from sceptre.template import Template
from sceptre.connection_manager import ConnectionManager
from sceptre.exceptions import UnsupportedTemplateFileTypeError
from sceptre.exceptions import TemplateSceptreHandlerError
from sceptre.exceptions import TemplateSceptreHandlerError, TemplateNotFoundError
from sceptre.template_handlers import TemplateHandler


Expand Down Expand Up @@ -260,7 +260,7 @@ def test_body_with_chdir_template(self):

def test_body_with_missing_file(self):
self.template.handler_config["path"] = "incorrect/template/path.py"
with pytest.raises(IOError):
with pytest.raises(TemplateNotFoundError):
self.template.body

def test_body_with_python_template(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import yaml

import sceptre.template_handlers.helper as helper
from sceptre.exceptions import TemplateNotFoundError
from unittest.mock import patch


Expand Down Expand Up @@ -52,17 +53,20 @@
"""
)
])
def test_render_jinja_template(filename, sceptre_user_data, expected):
jinja_template_dir = os.path.join(
@patch("pathlib.Path.exists")
def test_render_jinja_template(mock_pathlib,
filename,
sceptre_user_data,
expected):
mock_pathlib.return_value = True
jinja_template_path = os.path.join(
os.getcwd(),
"tests/fixtures/templates"
)
result = helper.render_jinja_template(
template_dir=jinja_template_dir,
filename=filename,
jinja_vars={"sceptre_user_data": sceptre_user_data},
j2_environment={}
"tests/fixtures/templates",
filename
)
result = helper.render_jinja_template(path=jinja_template_path,
jinja_vars={"sceptre_user_data": sceptre_user_data},
j2_environment={})
expected_yaml = yaml.safe_load(expected)
result_yaml = yaml.safe_load(result)
assert expected_yaml == result_yaml
Expand All @@ -77,17 +81,28 @@ def test_render_jinja_template(filename, sceptre_user_data, expected):
["autoescape", "loader", "undefined", "lstrip_blocks", "extensions"])
])
@patch("sceptre.template_handlers.helper.Environment")
def test_render_jinja_template_j2_environment_config(mock_environment, j2_environment, expected_keys):
@patch("pathlib.Path.exists")
def test_render_jinja_template_j2_environment_config(mock_pathlib,
mock_environment,
j2_environment,
expected_keys):
mock_pathlib.return_value = True
filename = "vpc.j2"
sceptre_user_data = {"vpc_id": "10.0.0.0/16"}
jinja_template_dir = os.path.join(
jinja_template_path = os.path.join(
os.getcwd(),
"tests/fixtures/templates"
)
_ = helper.render_jinja_template(
template_dir=jinja_template_dir,
filename=filename,
jinja_vars={"sceptre_user_data": sceptre_user_data},
j2_environment=j2_environment
"tests/fixtures/templates",
filename
)
_ = helper.render_jinja_template(path=jinja_template_path,
jinja_vars={"sceptre_user_data": sceptre_user_data},
j2_environment=j2_environment)
assert list(mock_environment.call_args.kwargs) == expected_keys


def test_render_jinja_template_non_existing_file():
jinja_template_path = os.path.join("/ref/to/nowhere/boom.j2")
with pytest.raises(TemplateNotFoundError):
helper.render_jinja_template(path=jinja_template_path,
jinja_vars={"sceptre_user_data": {}},
j2_environment={})

0 comments on commit c7988b9

Please sign in to comment.