1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750 |
- """
- Tests for salt.utils.jinja
- """
- import ast
- import builtins
- import copy
- import datetime
- import os
- import pprint
- import random
- import re
- import tempfile
- import salt.config
- import salt.loader
- # dateutils is needed so that the strftime jinja filter is loaded
- import salt.utils.dateutils # pylint: disable=unused-import
- import salt.utils.files
- import salt.utils.json
- import salt.utils.stringutils
- import salt.utils.yaml
- from jinja2 import DictLoader, Environment, Markup, exceptions
- from salt.exceptions import SaltRenderError
- from salt.utils.decorators.jinja import JinjaFilter
- from salt.utils.jinja import (
- SaltCacheLoader,
- SerializerExtension,
- ensure_sequence_filter,
- indent,
- tojson,
- )
- from salt.utils.odict import OrderedDict
- from salt.utils.templates import JINJA, render_jinja_tmpl
- from tests.support.case import ModuleCase
- from tests.support.helpers import requires_network
- from tests.support.mock import MagicMock, Mock, patch
- from tests.support.runtests import RUNTIME_VARS
- from tests.support.unit import TestCase, skipIf
- try:
- import timelib # pylint: disable=W0611
- HAS_TIMELIB = True
- except ImportError:
- HAS_TIMELIB = False
- BLINESEP = salt.utils.stringutils.to_bytes(os.linesep)
- class JinjaTestCase(TestCase):
- def test_tojson(self):
- """
- Test the ported tojson filter. Non-ascii unicode content should be
- dumped with ensure_ascii=True.
- """
- data = {"Non-ascii words": ["süß", "спам", "яйца"]}
- result = tojson(data)
- expected = (
- '{"Non-ascii words": ["s\\u00fc\\u00df", '
- '"\\u0441\\u043f\\u0430\\u043c", '
- '"\\u044f\\u0439\\u0446\\u0430"]}'
- )
- assert result == expected, result
- def test_indent(self):
- """
- Test the indent filter with Markup object as input. Double-quotes
- should not be URL-encoded.
- """
- data = Markup('foo:\n "bar"')
- result = indent(data)
- expected = Markup('foo:\n "bar"')
- assert result == expected, result
- class MockFileClient:
- """
- Does not download files but records any file request for testing
- """
- def __init__(self, loader=None):
- if loader:
- loader._file_client = self
- self.requests = []
- def get_file(self, template, dest="", makedirs=False, saltenv="base"):
- self.requests.append(
- {"path": template, "dest": dest, "makedirs": makedirs, "saltenv": saltenv}
- )
- def _setup_test_dir(src_dir, test_dir):
- os.makedirs(test_dir)
- salt.utils.files.recursive_copy(src_dir, test_dir)
- filename = os.path.join(test_dir, "non_ascii")
- with salt.utils.files.fopen(filename, "wb") as fp:
- fp.write(b"Assun\xc3\xa7\xc3\xa3o" + BLINESEP)
- filename = os.path.join(test_dir, "hello_simple")
- with salt.utils.files.fopen(filename, "wb") as fp:
- fp.write(b"world" + BLINESEP)
- filename = os.path.join(test_dir, "hello_import")
- lines = [
- r"{% from 'macro' import mymacro -%}",
- r"{% from 'macro' import mymacro -%}",
- r"{{ mymacro('Hey') ~ mymacro(a|default('a'), b|default('b')) }}",
- ]
- with salt.utils.files.fopen(filename, "wb") as fp:
- for line in lines:
- fp.write(line.encode("utf-8") + BLINESEP)
- class TestSaltCacheLoader(TestCase):
- def setUp(self):
- self.tempdir = tempfile.mkdtemp()
- self.template_dir = os.path.join(self.tempdir, "files", "test")
- _setup_test_dir(
- os.path.join(RUNTIME_VARS.BASE_FILES, "templates"), self.template_dir
- )
- self.opts = {
- "file_buffer_size": 1048576,
- "cachedir": self.tempdir,
- "file_roots": {"test": [self.template_dir]},
- "pillar_roots": {"test": [self.template_dir]},
- "extension_modules": os.path.join(
- os.path.dirname(os.path.abspath(__file__)), "extmods"
- ),
- }
- super().setUp()
- def tearDown(self):
- salt.utils.files.rm_rf(self.tempdir)
- self.tempdir = self.template_dir = self.opts
- def test_searchpath(self):
- """
- The searchpath is based on the cachedir option and the saltenv parameter
- """
- tmp = tempfile.gettempdir()
- opts = copy.deepcopy(self.opts)
- opts.update({"cachedir": tmp})
- loader = self.get_loader(opts=opts, saltenv="test")
- assert loader.searchpath == [os.path.join(tmp, "files", "test")]
- def test_mockclient(self):
- """
- A MockFileClient is used that records all file requests normally sent
- to the master.
- """
- loader = self.get_loader(opts=self.opts, saltenv="test")
- res = loader.get_source(None, "hello_simple")
- assert len(res) == 3
- # res[0] on Windows is unicode and use os.linesep so it works cross OS
- self.assertEqual(str(res[0]), "world" + os.linesep)
- tmpl_dir = os.path.join(self.template_dir, "hello_simple")
- self.assertEqual(res[1], tmpl_dir)
- assert res[2](), "Template up to date?"
- assert loader._file_client.requests
- self.assertEqual(loader._file_client.requests[0]["path"], "salt://hello_simple")
- def get_loader(self, opts=None, saltenv="base"):
- """
- Now that we instantiate the client in the __init__, we need to mock it
- """
- if opts is None:
- opts = self.opts
- with patch.object(SaltCacheLoader, "file_client", Mock()):
- loader = SaltCacheLoader(opts, saltenv)
- self.addCleanup(setattr, SaltCacheLoader, "_cached_client", None)
- # Create a mock file client and attach it to the loader
- MockFileClient(loader)
- return loader
- def get_test_saltenv(self):
- """
- Setup a simple jinja test environment
- """
- loader = self.get_loader(saltenv="test")
- jinja = Environment(loader=loader)
- return loader._file_client, jinja
- def test_import(self):
- """
- You can import and use macros from other files
- """
- fc, jinja = self.get_test_saltenv()
- result = jinja.get_template("hello_import").render()
- self.assertEqual(result, "Hey world !a b !")
- assert len(fc.requests) == 2
- self.assertEqual(fc.requests[0]["path"], "salt://hello_import")
- self.assertEqual(fc.requests[1]["path"], "salt://macro")
- def test_relative_import(self):
- """
- You can import using relative paths
- issue-13889
- """
- fc, jinja = self.get_test_saltenv()
- tmpl = jinja.get_template(os.path.join("relative", "rhello"))
- result = tmpl.render()
- self.assertEqual(result, "Hey world !a b !")
- assert len(fc.requests) == 3
- self.assertEqual(
- fc.requests[0]["path"], os.path.join("salt://relative", "rhello")
- )
- self.assertEqual(
- fc.requests[1]["path"], os.path.join("salt://relative", "rmacro")
- )
- self.assertEqual(fc.requests[2]["path"], "salt://macro")
- # This must fail when rendered: attempts to import from outside file root
- template = jinja.get_template("relative/rescape")
- self.assertRaises(exceptions.TemplateNotFound, template.render)
- def test_include(self):
- """
- You can also include a template that imports and uses macros
- """
- fc, jinja = self.get_test_saltenv()
- result = jinja.get_template("hello_include").render()
- self.assertEqual(result, "Hey world !a b !")
- assert len(fc.requests) == 3
- self.assertEqual(fc.requests[0]["path"], "salt://hello_include")
- self.assertEqual(fc.requests[1]["path"], "salt://hello_import")
- self.assertEqual(fc.requests[2]["path"], "salt://macro")
- def test_include_context(self):
- """
- Context variables are passes to the included template by default.
- """
- _, jinja = self.get_test_saltenv()
- result = jinja.get_template("hello_include").render(a="Hi", b="Salt")
- self.assertEqual(result, "Hey world !Hi Salt !")
- def test_cached_file_client(self):
- """
- Multiple instantiations of SaltCacheLoader use the cached file client
- """
- with patch("salt.transport.client.ReqChannel.factory", Mock()):
- loader_a = SaltCacheLoader(self.opts)
- loader_b = SaltCacheLoader(self.opts)
- assert loader_a._file_client is loader_b._file_client
- def test_file_client_kwarg(self):
- """
- A file client can be passed to SaltCacheLoader overriding the any
- cached file client
- """
- mfc = MockFileClient()
- loader = SaltCacheLoader(self.opts, _file_client=mfc)
- assert loader._file_client is mfc
- def test_cache_loader_shutdown(self):
- """
- The shudown method can be called without raising an exception when the
- file_client does not have a destroy method
- """
- mfc = MockFileClient()
- assert not hasattr(mfc, "destroy")
- loader = SaltCacheLoader(self.opts, _file_client=mfc)
- assert loader._file_client is mfc
- # Shutdown method should not raise any exceptions
- loader.shutdown()
- class TestGetTemplate(TestCase):
- def setUp(self):
- self.tempdir = tempfile.mkdtemp()
- self.template_dir = os.path.join(self.tempdir, "files", "test")
- _setup_test_dir(
- os.path.join(RUNTIME_VARS.BASE_FILES, "templates"), self.template_dir
- )
- self.local_opts = {
- "file_buffer_size": 1048576,
- "cachedir": self.tempdir,
- "file_client": "local",
- "file_ignore_regex": None,
- "file_ignore_glob": None,
- "file_roots": {"test": [self.template_dir]},
- "pillar_roots": {"test": [self.template_dir]},
- "fileserver_backend": ["roots"],
- "hash_type": "md5",
- "extension_modules": os.path.join(
- os.path.dirname(os.path.abspath(__file__)), "extmods"
- ),
- }
- self.local_salt = {}
- super().setUp()
- def tearDown(self):
- salt.utils.files.rm_rf(self.tempdir)
- self.tempdir = self.template_dir = self.local_opts = self.local_salt = None
- def test_fallback(self):
- """
- A Template with a filesystem loader is returned as fallback
- if the file is not contained in the searchpath
- """
- fn_ = os.path.join(self.template_dir, "hello_simple")
- with salt.utils.files.fopen(fn_) as fp_:
- out = render_jinja_tmpl(
- salt.utils.stringutils.to_unicode(fp_.read()),
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(out, "world" + os.linesep)
- def test_fallback_noloader(self):
- """
- A Template with a filesystem loader is returned as fallback
- if the file is not contained in the searchpath
- """
- filename = os.path.join(self.template_dir, "hello_import")
- with salt.utils.files.fopen(filename) as fp_:
- out = render_jinja_tmpl(
- salt.utils.stringutils.to_unicode(fp_.read()),
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(out, "Hey world !a b !" + os.linesep)
- def test_saltenv(self):
- """
- If the template is within the searchpath it can
- import, include and extend other templates.
- The initial template is expected to be already cached
- get_template does not request it from the master again.
- """
- fc = MockFileClient()
- with patch.object(SaltCacheLoader, "file_client", MagicMock(return_value=fc)):
- filename = os.path.join(self.template_dir, "hello_import")
- with salt.utils.files.fopen(filename) as fp_:
- out = render_jinja_tmpl(
- salt.utils.stringutils.to_unicode(fp_.read()),
- dict(
- opts={
- "cachedir": self.tempdir,
- "file_client": "remote",
- "file_roots": self.local_opts["file_roots"],
- "pillar_roots": self.local_opts["pillar_roots"],
- },
- a="Hi",
- b="Salt",
- saltenv="test",
- salt=self.local_salt,
- ),
- )
- self.assertEqual(out, "Hey world !Hi Salt !" + os.linesep)
- self.assertEqual(fc.requests[0]["path"], "salt://macro")
- def test_macro_additional_log_for_generalexc(self):
- """
- If we failed in a macro because of e.g. a TypeError, get
- more output from trace.
- """
- expected = r"""Jinja error:.*division.*
- .*macrogeneral\(2\):
- ---
- \{% macro mymacro\(\) -%\}
- \{\{ 1/0 \}\} <======================
- \{%- endmacro %\}
- ---.*"""
- filename = os.path.join(self.template_dir, "hello_import_generalerror")
- fc = MockFileClient()
- with patch.object(SaltCacheLoader, "file_client", MagicMock(return_value=fc)):
- with salt.utils.files.fopen(filename) as fp_:
- self.assertRaisesRegex(
- SaltRenderError,
- expected,
- render_jinja_tmpl,
- salt.utils.stringutils.to_unicode(fp_.read()),
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- def test_macro_additional_log_for_undefined(self):
- """
- If we failed in a macro because of undefined variables, get
- more output from trace.
- """
- expected = r"""Jinja variable 'b' is undefined
- .*macroundefined\(2\):
- ---
- \{% macro mymacro\(\) -%\}
- \{\{b.greetee\}\} <-- error is here <======================
- \{%- endmacro %\}
- ---"""
- filename = os.path.join(self.template_dir, "hello_import_undefined")
- fc = MockFileClient()
- with patch.object(SaltCacheLoader, "file_client", MagicMock(return_value=fc)):
- with salt.utils.files.fopen(filename) as fp_:
- self.assertRaisesRegex(
- SaltRenderError,
- expected,
- render_jinja_tmpl,
- salt.utils.stringutils.to_unicode(fp_.read()),
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- def test_macro_additional_log_syntaxerror(self):
- """
- If we failed in a macro, get more output from trace.
- """
- expected = r"""Jinja syntax error: expected token .*end.*got '-'.*
- .*macroerror\(2\):
- ---
- # macro
- \{% macro mymacro\(greeting, greetee='world'\) -\} <-- error is here <======================
- \{\{ greeting ~ ' ' ~ greetee \}\} !
- \{%- endmacro %\}
- ---.*"""
- filename = os.path.join(self.template_dir, "hello_import_error")
- fc = MockFileClient()
- with patch.object(SaltCacheLoader, "file_client", MagicMock(return_value=fc)):
- with salt.utils.files.fopen(filename) as fp_:
- self.assertRaisesRegex(
- SaltRenderError,
- expected,
- render_jinja_tmpl,
- salt.utils.stringutils.to_unicode(fp_.read()),
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- def test_non_ascii_encoding(self):
- fc = MockFileClient()
- with patch.object(SaltCacheLoader, "file_client", MagicMock(return_value=fc)):
- filename = os.path.join(self.template_dir, "hello_import")
- with salt.utils.files.fopen(filename) as fp_:
- out = render_jinja_tmpl(
- salt.utils.stringutils.to_unicode(fp_.read()),
- dict(
- opts={
- "cachedir": self.tempdir,
- "file_client": "remote",
- "file_roots": self.local_opts["file_roots"],
- "pillar_roots": self.local_opts["pillar_roots"],
- },
- a="Hi",
- b="Sàlt",
- saltenv="test",
- salt=self.local_salt,
- ),
- )
- self.assertEqual(
- out,
- salt.utils.stringutils.to_unicode("Hey world !Hi Sàlt !" + os.linesep),
- )
- self.assertEqual(fc.requests[0]["path"], "salt://macro")
- filename = os.path.join(self.template_dir, "non_ascii")
- with salt.utils.files.fopen(filename, "rb") as fp_:
- out = render_jinja_tmpl(
- salt.utils.stringutils.to_unicode(fp_.read(), "utf-8"),
- dict(
- opts={
- "cachedir": self.tempdir,
- "file_client": "remote",
- "file_roots": self.local_opts["file_roots"],
- "pillar_roots": self.local_opts["pillar_roots"],
- },
- a="Hi",
- b="Sàlt",
- saltenv="test",
- salt=self.local_salt,
- ),
- )
- self.assertEqual("Assunção" + os.linesep, out)
- self.assertEqual(fc.requests[0]["path"], "salt://macro")
- @skipIf(HAS_TIMELIB is False, "The `timelib` library is not installed.")
- def test_strftime(self):
- response = render_jinja_tmpl(
- '{{ "2002/12/25"|strftime }}',
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(response, "2002-12-25")
- objects = (
- datetime.datetime(2002, 12, 25, 12, 00, 00, 00),
- "2002/12/25",
- 1040814000,
- "1040814000",
- )
- for object in objects:
- response = render_jinja_tmpl(
- "{{ object|strftime }}",
- dict(
- object=object,
- opts=self.local_opts,
- saltenv="test",
- salt=self.local_salt,
- ),
- )
- self.assertEqual(response, "2002-12-25")
- response = render_jinja_tmpl(
- '{{ object|strftime("%b %d, %Y") }}',
- dict(
- object=object,
- opts=self.local_opts,
- saltenv="test",
- salt=self.local_salt,
- ),
- )
- self.assertEqual(response, "Dec 25, 2002")
- response = render_jinja_tmpl(
- '{{ object|strftime("%y") }}',
- dict(
- object=object,
- opts=self.local_opts,
- saltenv="test",
- salt=self.local_salt,
- ),
- )
- self.assertEqual(response, "02")
- def test_non_ascii(self):
- fn = os.path.join(self.template_dir, "non_ascii")
- out = JINJA(fn, opts=self.local_opts, saltenv="test", salt=self.local_salt)
- with salt.utils.files.fopen(out["data"], "rb") as fp:
- result = salt.utils.stringutils.to_unicode(fp.read(), "utf-8")
- self.assertEqual(
- salt.utils.stringutils.to_unicode("Assunção" + os.linesep), result
- )
- def test_get_context_has_enough_context(self):
- template = "1\n2\n3\n4\n5\n6\n7\n8\n9\na\nb\nc\nd\ne\nf"
- context = salt.utils.stringutils.get_context(template, 8)
- expected = "---\n[...]\n3\n4\n5\n6\n7\n8\n9\na\nb\nc\nd\n[...]\n---"
- self.assertEqual(expected, context)
- def test_get_context_at_top_of_file(self):
- template = "1\n2\n3\n4\n5\n6\n7\n8\n9\na\nb\nc\nd\ne\nf"
- context = salt.utils.stringutils.get_context(template, 1)
- expected = "---\n1\n2\n3\n4\n5\n6\n[...]\n---"
- self.assertEqual(expected, context)
- def test_get_context_at_bottom_of_file(self):
- template = "1\n2\n3\n4\n5\n6\n7\n8\n9\na\nb\nc\nd\ne\nf"
- context = salt.utils.stringutils.get_context(template, 15)
- expected = "---\n[...]\na\nb\nc\nd\ne\nf\n---"
- self.assertEqual(expected, context)
- def test_get_context_2_context_lines(self):
- template = "1\n2\n3\n4\n5\n6\n7\n8\n9\na\nb\nc\nd\ne\nf"
- context = salt.utils.stringutils.get_context(template, 8, num_lines=2)
- expected = "---\n[...]\n6\n7\n8\n9\na\n[...]\n---"
- self.assertEqual(expected, context)
- def test_get_context_with_marker(self):
- template = "1\n2\n3\n4\n5\n6\n7\n8\n9\na\nb\nc\nd\ne\nf"
- context = salt.utils.stringutils.get_context(
- template, 8, num_lines=2, marker=" <---"
- )
- expected = "---\n[...]\n6\n7\n8 <---\n9\na\n[...]\n---"
- self.assertEqual(expected, context)
- def test_render_with_syntax_error(self):
- template = "hello\n\n{{ bad\n\nfoo"
- expected = r".*---\nhello\n\n{{ bad\n\nfoo <======================\n---"
- self.assertRaisesRegex(
- SaltRenderError,
- expected,
- render_jinja_tmpl,
- template,
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- def test_render_with_utf8_syntax_error(self):
- with patch.object(builtins, "__salt_system_encoding__", "utf-8"):
- template = "hello\n\n{{ bad\n\nfoo한"
- expected = salt.utils.stringutils.to_str(
- r".*---\nhello\n\n{{ bad\n\nfoo한 <======================\n---"
- )
- self.assertRaisesRegex(
- SaltRenderError,
- expected,
- render_jinja_tmpl,
- template,
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- def test_render_with_undefined_variable(self):
- template = "hello\n\n{{ foo }}\n\nfoo"
- expected = r"Jinja variable \'foo\' is undefined"
- self.assertRaisesRegex(
- SaltRenderError,
- expected,
- render_jinja_tmpl,
- template,
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- def test_render_with_undefined_variable_utf8(self):
- template = "hello\xed\x95\x9c\n\n{{ foo }}\n\nfoo"
- expected = r"Jinja variable \'foo\' is undefined"
- self.assertRaisesRegex(
- SaltRenderError,
- expected,
- render_jinja_tmpl,
- template,
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- def test_render_with_undefined_variable_unicode(self):
- template = "hello한\n\n{{ foo }}\n\nfoo"
- expected = r"Jinja variable \'foo\' is undefined"
- self.assertRaisesRegex(
- SaltRenderError,
- expected,
- render_jinja_tmpl,
- template,
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- class TestJinjaDefaultOptions(TestCase):
- @classmethod
- def setUpClass(cls):
- cls.local_opts = {
- "cachedir": os.path.join(RUNTIME_VARS.TMP, "jinja-template-cache"),
- "file_buffer_size": 1048576,
- "file_client": "local",
- "file_ignore_regex": None,
- "file_ignore_glob": None,
- "file_roots": {
- "test": [os.path.join(RUNTIME_VARS.BASE_FILES, "templates")]
- },
- "pillar_roots": {
- "test": [os.path.join(RUNTIME_VARS.BASE_FILES, "templates")]
- },
- "fileserver_backend": ["roots"],
- "hash_type": "md5",
- "extension_modules": os.path.join(
- os.path.dirname(os.path.abspath(__file__)), "extmods"
- ),
- "jinja_env": {"line_comment_prefix": "##", "line_statement_prefix": "%"},
- }
- cls.local_salt = {
- "myvar": "zero",
- "mylist": [0, 1, 2, 3],
- }
- @classmethod
- def tearDownClass(cls):
- cls.local_opts = cls.local_salt = None
- def test_comment_prefix(self):
- template = """
- %- set myvar = 'one'
- ## ignored comment 1
- {{- myvar -}}
- {%- set myvar = 'two' %} ## ignored comment 2
- {{- myvar }} ## ignored comment 3
- %- if myvar == 'two':
- %- set myvar = 'three'
- %- endif
- {{- myvar -}}
- """
- rendered = render_jinja_tmpl(
- template, dict(opts=self.local_opts, saltenv="test", salt=self.local_salt)
- )
- self.assertEqual(rendered, "onetwothree")
- def test_statement_prefix(self):
- template = """
- {%- set mylist = ['1', '2', '3'] %}
- %- set mylist = ['one', 'two', 'three']
- %- for item in mylist:
- {{- item }}
- %- endfor
- """
- rendered = render_jinja_tmpl(
- template, dict(opts=self.local_opts, saltenv="test", salt=self.local_salt)
- )
- self.assertEqual(rendered, "onetwothree")
- class TestCustomExtensions(TestCase):
- @classmethod
- def setUpClass(cls):
- cls.local_opts = {
- "cachedir": os.path.join(RUNTIME_VARS.TMP, "jinja-template-cache"),
- "file_buffer_size": 1048576,
- "file_client": "local",
- "file_ignore_regex": None,
- "file_ignore_glob": None,
- "file_roots": {
- "test": [os.path.join(RUNTIME_VARS.BASE_FILES, "templates")]
- },
- "pillar_roots": {
- "test": [os.path.join(RUNTIME_VARS.BASE_FILES, "templates")]
- },
- "fileserver_backend": ["roots"],
- "hash_type": "md5",
- "extension_modules": os.path.join(
- os.path.dirname(os.path.abspath(__file__)), "extmods"
- ),
- }
- cls.local_salt = {
- # 'dns.A': dnsutil.A,
- # 'dns.AAAA': dnsutil.AAAA,
- # 'file.exists': filemod.file_exists,
- # 'file.basename': filemod.basename,
- # 'file.dirname': filemod.dirname
- }
- @classmethod
- def tearDownClass(cls):
- cls.local_opts = cls.local_salt = None
- def test_regex_escape(self):
- dataset = "foo?:.*/\\bar"
- env = Environment(extensions=[SerializerExtension])
- env.filters.update(JinjaFilter.salt_jinja_filters)
- rendered = env.from_string("{{ dataset|regex_escape }}").render(dataset=dataset)
- self.assertEqual(rendered, re.escape(dataset))
- def test_unique_string(self):
- dataset = "foo"
- unique = set(dataset)
- env = Environment(extensions=[SerializerExtension])
- env.filters.update(JinjaFilter.salt_jinja_filters)
- rendered = (
- env.from_string("{{ dataset|unique }}")
- .render(dataset=dataset)
- .strip("'{}")
- .split("', '")
- )
- self.assertEqual(sorted(rendered), sorted(list(unique)))
- def test_unique_tuple(self):
- dataset = ("foo", "foo", "bar")
- unique = set(dataset)
- env = Environment(extensions=[SerializerExtension])
- env.filters.update(JinjaFilter.salt_jinja_filters)
- rendered = (
- env.from_string("{{ dataset|unique }}")
- .render(dataset=dataset)
- .strip("'{}")
- .split("', '")
- )
- self.assertEqual(sorted(rendered), sorted(list(unique)))
- def test_unique_list(self):
- dataset = ["foo", "foo", "bar"]
- unique = ["foo", "bar"]
- env = Environment(extensions=[SerializerExtension])
- env.filters.update(JinjaFilter.salt_jinja_filters)
- rendered = (
- env.from_string("{{ dataset|unique }}")
- .render(dataset=dataset)
- .strip("'[]")
- .split("', '")
- )
- self.assertEqual(rendered, unique)
- def test_serialize_json(self):
- dataset = {"foo": True, "bar": 42, "baz": [1, 2, 3], "qux": 2.0}
- env = Environment(extensions=[SerializerExtension])
- rendered = env.from_string("{{ dataset|json }}").render(dataset=dataset)
- self.assertEqual(dataset, salt.utils.json.loads(rendered))
- def test_serialize_yaml(self):
- dataset = {
- "foo": True,
- "bar": 42,
- "baz": [1, 2, 3],
- "qux": 2.0,
- "spam": OrderedDict([("foo", OrderedDict([("bar", "baz"), ("qux", 42)]))]),
- }
- env = Environment(extensions=[SerializerExtension])
- rendered = env.from_string("{{ dataset|yaml }}").render(dataset=dataset)
- self.assertEqual(dataset, salt.utils.yaml.safe_load(rendered))
- def test_serialize_yaml_str(self):
- dataset = "str value"
- env = Environment(extensions=[SerializerExtension])
- rendered = env.from_string("{{ dataset|yaml }}").render(dataset=dataset)
- self.assertEqual(dataset, rendered)
- def test_serialize_yaml_unicode(self):
- dataset = "str value"
- env = Environment(extensions=[SerializerExtension])
- rendered = env.from_string("{{ dataset|yaml }}").render(dataset=dataset)
- self.assertEqual("str value", rendered)
- def test_serialize_python(self):
- dataset = {"foo": True, "bar": 42, "baz": [1, 2, 3], "qux": 2.0}
- env = Environment(extensions=[SerializerExtension])
- rendered = env.from_string("{{ dataset|python }}").render(dataset=dataset)
- self.assertEqual(rendered, pprint.pformat(dataset))
- def test_load_yaml(self):
- env = Environment(extensions=[SerializerExtension])
- rendered = env.from_string(
- '{% set document = "{foo: it works}"|load_yaml %}{{ document.foo }}'
- ).render()
- self.assertEqual(rendered, "it works")
- rendered = env.from_string(
- "{% set document = document|load_yaml %}" "{{ document.foo }}"
- ).render(document="{foo: it works}")
- self.assertEqual(rendered, "it works")
- with self.assertRaises((TypeError, exceptions.TemplateRuntimeError)):
- env.from_string(
- "{% set document = document|load_yaml %}" "{{ document.foo }}"
- ).render(document={"foo": "it works"})
- def test_load_tag(self):
- env = Environment(extensions=[SerializerExtension])
- source = (
- "{{ bar }}, "
- + "{% load_yaml as docu %}{foo: it works, {{ bar }}: baz}{% endload %}"
- + "{{ docu.foo }}"
- )
- rendered = env.from_string(source).render(bar="barred")
- self.assertEqual(rendered, "barred, it works")
- source = (
- '{{ bar }}, {% load_json as docu %}{"foo": "it works", "{{ bar }}": "baz"}{% endload %}'
- + "{{ docu.foo }}"
- )
- rendered = env.from_string(source).render(bar="barred")
- self.assertEqual(rendered, "barred, it works")
- with self.assertRaises(exceptions.TemplateSyntaxError):
- env.from_string(
- "{% load_yamle as document %}{foo, bar: it works}{% endload %}"
- ).render()
- with self.assertRaises(exceptions.TemplateRuntimeError):
- env.from_string(
- "{% load_json as document %}{foo, bar: it works}{% endload %}"
- ).render()
- def test_load_json(self):
- env = Environment(extensions=[SerializerExtension])
- rendered = env.from_string(
- '{% set document = \'{"foo": "it works"}\'|load_json %}'
- "{{ document.foo }}"
- ).render()
- self.assertEqual(rendered, "it works")
- rendered = env.from_string(
- "{% set document = document|load_json %}" "{{ document.foo }}"
- ).render(document='{"foo": "it works"}')
- self.assertEqual(rendered, "it works")
- # bad quotes
- with self.assertRaises(exceptions.TemplateRuntimeError):
- env.from_string("{{ document|load_json }}").render(
- document="{'foo': 'it works'}"
- )
- # not a string
- with self.assertRaises(exceptions.TemplateRuntimeError):
- env.from_string("{{ document|load_json }}").render(
- document={"foo": "it works"}
- )
- def test_load_yaml_template(self):
- loader = DictLoader({"foo": '{bar: "my god is blue", foo: [1, 2, 3]}'})
- env = Environment(extensions=[SerializerExtension], loader=loader)
- rendered = env.from_string(
- '{% import_yaml "foo" as doc %}{{ doc.bar }}'
- ).render()
- self.assertEqual(rendered, "my god is blue")
- with self.assertRaises(exceptions.TemplateNotFound):
- env.from_string('{% import_yaml "does not exists" as doc %}').render()
- def test_load_json_template(self):
- loader = DictLoader({"foo": '{"bar": "my god is blue", "foo": [1, 2, 3]}'})
- env = Environment(extensions=[SerializerExtension], loader=loader)
- rendered = env.from_string(
- '{% import_json "foo" as doc %}{{ doc.bar }}'
- ).render()
- self.assertEqual(rendered, "my god is blue")
- with self.assertRaises(exceptions.TemplateNotFound):
- env.from_string('{% import_json "does not exists" as doc %}').render()
- def test_load_text_template(self):
- loader = DictLoader({"foo": "Foo!"})
- env = Environment(extensions=[SerializerExtension], loader=loader)
- rendered = env.from_string('{% import_text "foo" as doc %}{{ doc }}').render()
- self.assertEqual(rendered, "Foo!")
- with self.assertRaises(exceptions.TemplateNotFound):
- env.from_string('{% import_text "does not exists" as doc %}').render()
- def test_catalog(self):
- loader = DictLoader(
- {
- "doc1": '{bar: "my god is blue"}',
- "doc2": '{% import_yaml "doc1" as local2 %} never exported',
- "doc3": '{% load_yaml as local3 %}{"foo": "it works"}{% endload %} me neither',
- "main1": '{% from "doc2" import local2 %}{{ local2.bar }}',
- "main2": '{% from "doc3" import local3 %}{{ local3.foo }}',
- "main3": """
- {% import "doc2" as imported2 %}
- {% import "doc3" as imported3 %}
- {{ imported2.local2.bar }}
- """,
- "main4": """
- {% import "doc2" as imported2 %}
- {% import "doc3" as imported3 %}
- {{ imported3.local3.foo }}
- """,
- "main5": """
- {% from "doc2" import local2 as imported2 %}
- {% from "doc3" import local3 as imported3 %}
- {{ imported2.bar }}
- """,
- "main6": """
- {% from "doc2" import local2 as imported2 %}
- {% from "doc3" import local3 as imported3 %}
- {{ imported3.foo }}
- """,
- }
- )
- env = Environment(extensions=[SerializerExtension], loader=loader)
- rendered = env.get_template("main1").render()
- self.assertEqual(rendered, "my god is blue")
- rendered = env.get_template("main2").render()
- self.assertEqual(rendered, "it works")
- rendered = env.get_template("main3").render().strip()
- self.assertEqual(rendered, "my god is blue")
- rendered = env.get_template("main4").render().strip()
- self.assertEqual(rendered, "it works")
- rendered = env.get_template("main5").render().strip()
- self.assertEqual(rendered, "my god is blue")
- rendered = env.get_template("main6").render().strip()
- self.assertEqual(rendered, "it works")
- def test_nested_structures(self):
- env = Environment(extensions=[SerializerExtension])
- rendered = env.from_string("{{ data }}").render(data="foo")
- self.assertEqual(rendered, "foo")
- data = OrderedDict([("foo", OrderedDict([("bar", "baz"), ("qux", 42)]))])
- rendered = env.from_string("{{ data }}").render(data=data)
- self.assertEqual(
- rendered, "{'foo': {'bar': 'baz', 'qux': 42}}",
- )
- rendered = env.from_string("{{ data }}").render(
- data=[OrderedDict(foo="bar",), OrderedDict(baz=42,)]
- )
- self.assertEqual(
- rendered, "[{'foo': 'bar'}, {'baz': 42}]",
- )
- def test_set_dict_key_value(self):
- """
- Test the `set_dict_key_value` Jinja filter.
- """
- rendered = render_jinja_tmpl(
- "{{ {} | set_dict_key_value('foo:bar:baz', 42) }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "{'foo': {'bar': {'baz': 42}}}")
- rendered = render_jinja_tmpl(
- "{{ {} | set_dict_key_value('foo.bar.baz', 42, delimiter='.') }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "{'foo': {'bar': {'baz': 42}}}")
- def test_update_dict_key_value(self):
- """
- Test the `update_dict_key_value` Jinja filter.
- """
- # Use OrderedDicts to avoid random key-order-switches in the rendered string.
- expected = OrderedDict(
- [("bar", OrderedDict([("baz", OrderedDict([("qux", 1), ("quux", 3)]))]))]
- )
- dataset = OrderedDict(
- [("bar", OrderedDict([("baz", OrderedDict([("qux", 1)]))]))]
- )
- dataset_exp = OrderedDict([("quux", 3)])
- rendered = render_jinja_tmpl(
- "{{ foo | update_dict_key_value('bar:baz', exp) }}",
- dict(
- foo=dataset,
- exp=dataset_exp,
- opts=self.local_opts,
- saltenv="test",
- salt=self.local_salt,
- ),
- )
- self.assertEqual(
- rendered, "{'bar': {'baz': {'qux': 1, 'quux': 3}}}",
- )
- # Test incorrect usage
- for update_with in [42, "foo", [42]]:
- template = "{{ {} | update_dict_key_value('bar:baz', update_with) }}"
- expected = r"Cannot update {} with a {}.".format(
- type({}), type(update_with)
- )
- self.assertRaisesRegex(
- SaltRenderError,
- expected,
- render_jinja_tmpl,
- template,
- dict(
- update_with=update_with,
- opts=self.local_opts,
- saltenv="test",
- salt=self.local_salt,
- ),
- )
- def test_append_dict_key_value(self):
- """
- Test the `append_dict_key_value` Jinja filter.
- """
- rendered = render_jinja_tmpl(
- "{{ {} | append_dict_key_value('foo:bar:baz', 42) }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "{'foo': {'bar': {'baz': [42]}}}")
- rendered = render_jinja_tmpl(
- "{{ foo | append_dict_key_value('bar:baz', 42) }}",
- dict(
- foo={"bar": {"baz": [1, 2]}},
- opts=self.local_opts,
- saltenv="test",
- salt=self.local_salt,
- ),
- )
- self.assertEqual(
- rendered, "{'bar': {'baz': [1, 2, 42]}}",
- )
- def test_extend_dict_key_value(self):
- """
- Test the `extend_dict_key_value` Jinja filter.
- """
- rendered = render_jinja_tmpl(
- "{{ {} | extend_dict_key_value('foo:bar:baz', [42]) }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "{'foo': {'bar': {'baz': [42]}}}")
- rendered = render_jinja_tmpl(
- "{{ foo | extend_dict_key_value('bar:baz', [42, 43]) }}",
- dict(
- foo={"bar": {"baz": [1, 2]}},
- opts=self.local_opts,
- saltenv="test",
- salt=self.local_salt,
- ),
- )
- self.assertEqual(
- rendered, "{'bar': {'baz': [1, 2, 42, 43]}}",
- )
- # Edge cases
- rendered = render_jinja_tmpl(
- "{{ {} | extend_dict_key_value('foo:bar:baz', 'quux') }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "{'foo': {'bar': {'baz': ['q', 'u', 'u', 'x']}}}")
- # Beware! When supplying a dict, the list gets extended with the dict coerced to a list,
- # which will only contain the keys of the dict.
- rendered = render_jinja_tmpl(
- "{{ {} | extend_dict_key_value('foo:bar:baz', {'foo': 'bar'}) }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "{'foo': {'bar': {'baz': ['foo']}}}")
- # Test incorrect usage
- template = "{{ {} | extend_dict_key_value('bar:baz', 42) }}"
- expected = r"Cannot extend {} with a {}.".format(type([]), type(42))
- self.assertRaisesRegex(
- SaltRenderError,
- expected,
- render_jinja_tmpl,
- template,
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- def test_sequence(self):
- env = Environment()
- env.filters["sequence"] = ensure_sequence_filter
- rendered = env.from_string("{{ data | sequence | length }}").render(data="foo")
- self.assertEqual(rendered, "1")
- rendered = env.from_string("{{ data | sequence | length }}").render(
- data=["foo", "bar"]
- )
- self.assertEqual(rendered, "2")
- rendered = env.from_string("{{ data | sequence | length }}").render(
- data=("foo", "bar")
- )
- self.assertEqual(rendered, "2")
- rendered = env.from_string("{{ data | sequence | length }}").render(
- data={"foo", "bar"}
- )
- self.assertEqual(rendered, "2")
- rendered = env.from_string("{{ data | sequence | length }}").render(
- data={"foo": "bar"}
- )
- self.assertEqual(rendered, "1")
- def test_camel_to_snake_case(self):
- """
- Test the `to_snake_case` Jinja filter.
- """
- rendered = render_jinja_tmpl(
- "{{ 'abcdEfghhIjkLmnoP' | to_snake_case }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "abcd_efghh_ijk_lmno_p")
- def test_snake_to_camel_case(self):
- """
- Test the `to_camelcase` Jinja filter.
- """
- rendered = render_jinja_tmpl(
- "{{ 'the_fox_jumped_over_the_lazy_dog' | to_camelcase }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "theFoxJumpedOverTheLazyDog")
- rendered = render_jinja_tmpl(
- "{{ 'the_fox_jumped_over_the_lazy_dog' | to_camelcase(uppercamel=True) }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "TheFoxJumpedOverTheLazyDog")
- def test_is_ip(self):
- """
- Test the `is_ip` Jinja filter.
- """
- rendered = render_jinja_tmpl(
- "{{ '192.168.0.1' | is_ip }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "True")
- rendered = render_jinja_tmpl(
- "{{ 'FE80::' | is_ip }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "True")
- rendered = render_jinja_tmpl(
- "{{ 'random' | is_ip }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "False")
- def test_is_ipv4(self):
- """
- Test the `is_ipv4` Jinja filter.
- """
- rendered = render_jinja_tmpl(
- "{{ '192.168.0.1' | is_ipv4 }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "True")
- rendered = render_jinja_tmpl(
- "{{ 'FE80::' | is_ipv4 }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "False")
- rendered = render_jinja_tmpl(
- "{{ 'random' | is_ipv4 }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "False")
- def test_is_ipv6(self):
- """
- Test the `is_ipv6` Jinja filter.
- """
- rendered = render_jinja_tmpl(
- "{{ '192.168.0.1' | is_ipv6 }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "False")
- rendered = render_jinja_tmpl(
- "{{ 'fe80::20d:b9ff:fe01:ea8%eth0' | is_ipv6 }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "True")
- rendered = render_jinja_tmpl(
- "{{ 'FE80::' | is_ipv6 }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "True")
- rendered = render_jinja_tmpl(
- "{{ 'random' | is_ipv6 }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "False")
- def test_ipaddr(self):
- """
- Test the `ipaddr` Jinja filter.
- """
- rendered = render_jinja_tmpl(
- "{{ '::' | ipaddr }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "::")
- rendered = render_jinja_tmpl(
- "{{ '192.168.0.1' | ipaddr }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "192.168.0.1")
- # provides a list with valid IP addresses only
- rendered = render_jinja_tmpl(
- "{{ ['192.168.0.1', '172.17.17.1', 'foo', 'bar', '::'] | ipaddr | join(', ') }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "192.168.0.1, 172.17.17.1, ::")
- # return only multicast addresses
- rendered = render_jinja_tmpl(
- "{{ ['224.0.0.1', 'FF01::1', '::'] | ipaddr(options='multicast') | join(', ') }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "224.0.0.1, ff01::1")
- def test_ipv4(self):
- """
- Test the `ipv4` Jinja filter.
- """
- rendered = render_jinja_tmpl(
- "{{ '192.168.0.1' | ipv4 }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "192.168.0.1")
- rendered = render_jinja_tmpl(
- "{{ ['192.168.0.1', '172.17.17.1'] | ipv4 | join(', ')}}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "192.168.0.1, 172.17.17.1")
- rendered = render_jinja_tmpl(
- "{{ 'fe80::' | ipv4 }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "None")
- rendered = render_jinja_tmpl(
- "{{ 'random' | ipv4 }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "None")
- rendered = render_jinja_tmpl(
- "{{ '192.168.0.1' | ipv4(options='lo') }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "None")
- rendered = render_jinja_tmpl(
- "{{ '127.0.0.1' | ipv4(options='lo') }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "127.0.0.1")
- def test_ipv6(self):
- """
- Test the `ipv6` Jinja filter.
- """
- rendered = render_jinja_tmpl(
- "{{ '192.168.0.1' | ipv6 }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "None")
- rendered = render_jinja_tmpl(
- "{{ 'random' | ipv6 }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "None")
- # returns the standard format value
- rendered = render_jinja_tmpl(
- "{{ 'FE80:0:0::0' | ipv6 }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "fe80::")
- # fe80:: is link local therefore will be returned
- rendered = render_jinja_tmpl(
- "{{ 'fe80::' | ipv6(options='ll') }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "fe80::")
- # fe80:: is not loopback
- rendered = render_jinja_tmpl(
- "{{ 'fe80::' | ipv6(options='lo') }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "None")
- # returns only IPv6 addresses in the list
- rendered = render_jinja_tmpl(
- "{{ ['fe80::', '192.168.0.1'] | ipv6 | join(', ') }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "fe80::")
- rendered = render_jinja_tmpl(
- "{{ ['fe80::', '::'] | ipv6 | join(', ') }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "fe80::, ::")
- def test_network_hosts(self):
- """
- Test the `network_hosts` Jinja filter.
- """
- rendered = render_jinja_tmpl(
- "{{ '192.168.0.1/30' | network_hosts | join(', ') }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "192.168.0.1, 192.168.0.2")
- def test_network_size(self):
- """
- Test the `network_size` Jinja filter.
- """
- rendered = render_jinja_tmpl(
- "{{ '192.168.0.1' | network_size }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "1")
- rendered = render_jinja_tmpl(
- "{{ '192.168.0.1/8' | network_size }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "16777216")
- @requires_network()
- def test_http_query(self):
- """
- Test the `http_query` Jinja filter.
- """
- urls = (
- # These cannot be HTTPS urls since urllib2 chokes on those
- "http://saltstack.com",
- "http://community.saltstack.com",
- "http://google.com",
- "http://duckduckgo.com",
- )
- for backend in ("requests", "tornado", "urllib2"):
- rendered = render_jinja_tmpl(
- "{{ '"
- + random.choice(urls)
- + "' | http_query(backend='"
- + backend
- + "') }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertIsInstance(
- rendered, str, "Failed with rendered template: {}".format(rendered)
- )
- dict_reply = ast.literal_eval(rendered)
- self.assertIsInstance(
- dict_reply, dict, "Failed with rendered template: {}".format(rendered)
- )
- self.assertIn(
- "body",
- dict_reply,
- "'body' not found in request response({}). Rendered template: {!r}".format(
- dict_reply, rendered
- ),
- )
- self.assertIsInstance(
- dict_reply["body"],
- str,
- "Failed with rendered template: {}".format(rendered),
- )
- def test_to_bool(self):
- """
- Test the `to_bool` Jinja filter.
- """
- rendered = render_jinja_tmpl(
- "{{ 1 | to_bool }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "True")
- rendered = render_jinja_tmpl(
- "{{ 'True' | to_bool }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "True")
- rendered = render_jinja_tmpl(
- "{{ 0 | to_bool }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "False")
- rendered = render_jinja_tmpl(
- "{{ 'Yes' | to_bool }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "True")
- def test_quote(self):
- """
- Test the `quote` Jinja filter.
- """
- rendered = render_jinja_tmpl(
- "{{ 'random' | quote }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "random")
- def test_regex_search(self):
- """
- Test the `regex_search` Jinja filter.
- """
- rendered = render_jinja_tmpl(
- "{{ 'abcdefabcdef' | regex_search('BC(.*)', ignorecase=True) }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(
- rendered, "('defabcdef',)"
- ) # because search looks only at the beginning
- def test_regex_match(self):
- """
- Test the `regex_match` Jinja filter.
- """
- rendered = render_jinja_tmpl(
- "{{ 'abcdefabcdef' | regex_match('BC(.*)', ignorecase=True)}}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "None")
- def test_regex_replace(self):
- """
- Test the `regex_replace` Jinja filter.
- """
- rendered = render_jinja_tmpl(
- r"{{ 'lets replace spaces' | regex_replace('\s+', '__') }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "lets__replace__spaces")
- def test_uuid(self):
- """
- Test the `uuid` Jinja filter.
- """
- rendered = render_jinja_tmpl(
- "{{ 'random' | uuid }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "3652b285-26ad-588e-a5dc-c2ee65edc804")
- def test_min(self):
- """
- Test the `min` Jinja filter.
- """
- rendered = render_jinja_tmpl(
- "{{ [1, 2, 3] | min }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "1")
- def test_max(self):
- """
- Test the `max` Jinja filter.
- """
- rendered = render_jinja_tmpl(
- "{{ [1, 2, 3] | max }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "3")
- def test_avg(self):
- """
- Test the `avg` Jinja filter.
- """
- rendered = render_jinja_tmpl(
- "{{ [1, 2, 3] | avg }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "2.0")
- def test_union(self):
- """
- Test the `union` Jinja filter.
- """
- rendered = render_jinja_tmpl(
- "{{ [1, 2, 3] | union([2, 3, 4]) | join(', ') }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "1, 2, 3, 4")
- def test_intersect(self):
- """
- Test the `intersect` Jinja filter.
- """
- rendered = render_jinja_tmpl(
- "{{ [1, 2, 3] | intersect([2, 3, 4]) | join(', ') }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "2, 3")
- def test_difference(self):
- """
- Test the `difference` Jinja filter.
- """
- rendered = render_jinja_tmpl(
- "{{ [1, 2, 3] | difference([2, 3, 4]) | join(', ') }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "1")
- def test_symmetric_difference(self):
- """
- Test the `symmetric_difference` Jinja filter.
- """
- rendered = render_jinja_tmpl(
- "{{ [1, 2, 3] | symmetric_difference([2, 3, 4]) | join(', ') }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "1, 4")
- def test_method_call(self):
- """
- Test the `method_call` Jinja filter.
- """
- rendered = render_jinja_tmpl(
- "{{ 6|method_call('bit_length') }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "3")
- rendered = render_jinja_tmpl(
- "{{ 6.7|method_call('is_integer') }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "False")
- rendered = render_jinja_tmpl(
- "{{ 'absaltba'|method_call('strip', 'ab') }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "salt")
- rendered = render_jinja_tmpl(
- "{{ [1, 2, 1, 3, 4]|method_call('index', 1, 1, 3) }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "2")
- # have to use `dictsort` to keep test result deterministic
- rendered = render_jinja_tmpl(
- "{{ {}|method_call('fromkeys', ['a', 'b', 'c'], 0)|dictsort }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "[('a', 0), ('b', 0), ('c', 0)]")
- # missing object method test
- rendered = render_jinja_tmpl(
- "{{ 6|method_call('bit_width') }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "None")
- def test_md5(self):
- """
- Test the `md5` Jinja filter.
- """
- rendered = render_jinja_tmpl(
- "{{ 'random' | md5 }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "7ddf32e17a6ac5ce04a8ecbf782ca509")
- def test_sha256(self):
- """
- Test the `sha256` Jinja filter.
- """
- rendered = render_jinja_tmpl(
- "{{ 'random' | sha256 }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(
- rendered, "a441b15fe9a3cf56661190a0b93b9dec7d04127288cc87250967cf3b52894d11"
- )
- def test_sha512(self):
- """
- Test the `sha512` Jinja filter.
- """
- rendered = render_jinja_tmpl(
- "{{ 'random' | sha512 }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(
- rendered,
- str(
- "811a90e1c8e86c7b4c0eef5b2c0bf0ec1b19c4b1b5a242e6455be93787cb473cb7bc"
- "9b0fdeb960d00d5c6881c2094dd63c5c900ce9057255e2a4e271fc25fef1"
- ),
- )
- def test_hmac(self):
- """
- Test the `hmac` Jinja filter.
- """
- rendered = render_jinja_tmpl(
- "{{ 'random' | hmac('secret', 'blah') }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "False")
- rendered = render_jinja_tmpl(
- (
- "{{ 'get salted' | "
- "hmac('shared secret', 'eBWf9bstXg+NiP5AOwppB5HMvZiYMPzEM9W5YMm/AmQ=') }}"
- ),
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "True")
- def test_base64_encode(self):
- """
- Test the `base64_encode` Jinja filter.
- """
- rendered = render_jinja_tmpl(
- "{{ 'random' | base64_encode }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "cmFuZG9t")
- def test_base64_decode(self):
- """
- Test the `base64_decode` Jinja filter.
- """
- rendered = render_jinja_tmpl(
- "{{ 'cmFuZG9t' | base64_decode }}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "random")
- def test_json_query(self):
- """
- Test the `json_query` Jinja filter.
- """
- rendered = render_jinja_tmpl(
- "{{ [1, 2, 3] | json_query('[1]')}}",
- dict(opts=self.local_opts, saltenv="test", salt=self.local_salt),
- )
- self.assertEqual(rendered, "2")
- # def test_print(self):
- # env = Environment(extensions=[SerializerExtension])
- # source = '{% import_yaml "toto.foo" as docu %}'
- # name, filename = None, '<filename>'
- # parsed = env._parse(source, name, filename)
- # print parsed
- # print
- # compiled = env._generate(parsed, name, filename)
- # print compiled
- # return
- class TestDotNotationLookup(ModuleCase):
- """
- Tests to call Salt functions via Jinja with various lookup syntaxes
- """
- def setUp(self):
- functions = {
- "mocktest.ping": lambda: True,
- "mockgrains.get": lambda x: "jerry",
- }
- minion_opts = salt.config.minion_config(
- os.path.join(RUNTIME_VARS.TMP_CONF_DIR, "minion")
- )
- render = salt.loader.render(minion_opts, functions)
- self.jinja = render.get("jinja")
- def tearDown(self):
- del self.jinja
- def render(self, tmpl_str, context=None):
- return self.jinja(tmpl_str, context=context or {}, argline="-s").read()
- def test_normlookup(self):
- """
- Sanity-check the normal dictionary-lookup syntax for our stub function
- """
- tmpl_str = """Hello, {{ salt['mocktest.ping']() }}."""
- with patch.object(SaltCacheLoader, "file_client", Mock()):
- ret = self.render(tmpl_str)
- self.assertEqual(ret, "Hello, True.")
- def test_dotlookup(self):
- """
- Check calling a stub function using awesome dot-notation
- """
- tmpl_str = """Hello, {{ salt.mocktest.ping() }}."""
- with patch.object(SaltCacheLoader, "file_client", Mock()):
- ret = self.render(tmpl_str)
- self.assertEqual(ret, "Hello, True.")
- def test_shadowed_dict_method(self):
- """
- Check calling a stub function with a name that shadows a ``dict``
- method name
- """
- tmpl_str = """Hello, {{ salt.mockgrains.get('id') }}."""
- with patch.object(SaltCacheLoader, "file_client", Mock()):
- ret = self.render(tmpl_str)
- self.assertEqual(ret, "Hello, jerry.")
|