test_jinja.py 54 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348
  1. # -*- coding: utf-8 -*-
  2. '''
  3. Tests for salt.utils.jinja
  4. '''
  5. # Import Python libs
  6. from __future__ import absolute_import, unicode_literals, print_function
  7. from jinja2 import Environment, DictLoader, exceptions
  8. import ast
  9. import copy
  10. import datetime
  11. import os
  12. import pprint
  13. import re
  14. import tempfile
  15. # Import Salt Testing libs
  16. from tests.support.unit import skipIf, TestCase
  17. from tests.support.case import ModuleCase
  18. from tests.support.helpers import flaky
  19. from tests.support.mock import NO_MOCK, NO_MOCK_REASON, patch, MagicMock, Mock
  20. from tests.support.paths import BASE_FILES, TMP, TMP_CONF_DIR
  21. # Import Salt libs
  22. import salt.config
  23. import salt.loader
  24. from salt.exceptions import SaltRenderError
  25. from salt.ext import six
  26. from salt.ext.six.moves import builtins
  27. import salt.utils.json
  28. from salt.utils.decorators.jinja import JinjaFilter
  29. from salt.utils.jinja import (
  30. SaltCacheLoader,
  31. SerializerExtension,
  32. ensure_sequence_filter,
  33. tojson
  34. )
  35. from salt.utils.odict import OrderedDict
  36. from salt.utils.templates import JINJA, render_jinja_tmpl
  37. # dateutils is needed so that the strftime jinja filter is loaded
  38. import salt.utils.dateutils # pylint: disable=unused-import
  39. import salt.utils.files
  40. import salt.utils.stringutils
  41. import salt.utils.yaml
  42. # Import 3rd party libs
  43. try:
  44. import timelib # pylint: disable=W0611
  45. HAS_TIMELIB = True
  46. except ImportError:
  47. HAS_TIMELIB = False
  48. CACHEDIR = os.path.join(TMP, 'jinja-template-cache')
  49. BLINESEP = salt.utils.stringutils.to_bytes(os.linesep)
  50. class JinjaTestCase(TestCase):
  51. def test_tojson(self):
  52. '''
  53. Test the tojson filter for those using Jinja < 2.9. Non-ascii unicode
  54. content should be dumped with ensure_ascii=True.
  55. '''
  56. data = {'Non-ascii words': ['süß', 'спам', 'яйца']}
  57. result = tojson(data)
  58. expected = '{"Non-ascii words": ["s\\u00fc\\u00df", "\\u0441\\u043f\\u0430\\u043c", "\\u044f\\u0439\\u0446\\u0430"]}'
  59. assert result == expected, result
  60. class MockFileClient(object):
  61. '''
  62. Does not download files but records any file request for testing
  63. '''
  64. def __init__(self, loader=None):
  65. if loader:
  66. loader._file_client = self
  67. self.requests = []
  68. def get_file(self, template, dest='', makedirs=False, saltenv='base'):
  69. self.requests.append({
  70. 'path': template,
  71. 'dest': dest,
  72. 'makedirs': makedirs,
  73. 'saltenv': saltenv
  74. })
  75. def _setup_test_dir(src_dir, test_dir):
  76. os.makedirs(test_dir)
  77. salt.utils.files.recursive_copy(src_dir, test_dir)
  78. filename = os.path.join(test_dir, 'non_ascii')
  79. with salt.utils.files.fopen(filename, 'wb') as fp:
  80. fp.write(b'Assun\xc3\xa7\xc3\xa3o' + BLINESEP)
  81. filename = os.path.join(test_dir, 'hello_simple')
  82. with salt.utils.files.fopen(filename, 'wb') as fp:
  83. fp.write(b'world' + BLINESEP)
  84. filename = os.path.join(test_dir, 'hello_import')
  85. lines = [
  86. r"{% from 'macro' import mymacro -%}",
  87. r"{% from 'macro' import mymacro -%}",
  88. r"{{ mymacro('Hey') ~ mymacro(a|default('a'), b|default('b')) }}",
  89. ]
  90. with salt.utils.files.fopen(filename, 'wb') as fp:
  91. for line in lines:
  92. fp.write(line.encode('utf-8') + BLINESEP)
  93. class TestSaltCacheLoader(TestCase):
  94. def setUp(self):
  95. self.tempdir = tempfile.mkdtemp()
  96. self.template_dir = os.path.join(self.tempdir, 'files', 'test')
  97. _setup_test_dir(
  98. os.path.join(BASE_FILES, 'templates'),
  99. self.template_dir
  100. )
  101. self.opts = {
  102. 'cachedir': self.tempdir,
  103. 'file_roots': {
  104. 'test': [self.template_dir]
  105. },
  106. 'pillar_roots': {
  107. 'test': [self.template_dir]
  108. }
  109. }
  110. super(TestSaltCacheLoader, self).setUp()
  111. def tearDown(self):
  112. salt.utils.files.rm_rf(self.tempdir)
  113. def test_searchpath(self):
  114. '''
  115. The searchpath is based on the cachedir option and the saltenv parameter
  116. '''
  117. tmp = tempfile.gettempdir()
  118. opts = copy.deepcopy(self.opts)
  119. opts.update({'cachedir': tmp})
  120. loader = self.get_loader(opts=opts, saltenv='test')
  121. assert loader.searchpath == [os.path.join(tmp, 'files', 'test')]
  122. def test_mockclient(self):
  123. '''
  124. A MockFileClient is used that records all file requests normally sent
  125. to the master.
  126. '''
  127. loader = self.get_loader(opts=self.opts, saltenv='test')
  128. res = loader.get_source(None, 'hello_simple')
  129. assert len(res) == 3
  130. # res[0] on Windows is unicode and use os.linesep so it works cross OS
  131. self.assertEqual(six.text_type(res[0]), 'world' + os.linesep)
  132. tmpl_dir = os.path.join(self.template_dir, 'hello_simple')
  133. self.assertEqual(res[1], tmpl_dir)
  134. assert res[2](), 'Template up to date?'
  135. assert len(loader._file_client.requests)
  136. self.assertEqual(loader._file_client.requests[0]['path'], 'salt://hello_simple')
  137. def get_loader(self, opts=None, saltenv='base'):
  138. '''
  139. Now that we instantiate the client in the __init__, we need to mock it
  140. '''
  141. if opts is None:
  142. opts = self.opts
  143. with patch.object(SaltCacheLoader, 'file_client', Mock()):
  144. loader = SaltCacheLoader(opts, saltenv)
  145. # Create a mock file client and attach it to the loader
  146. MockFileClient(loader)
  147. return loader
  148. def get_test_saltenv(self):
  149. '''
  150. Setup a simple jinja test environment
  151. '''
  152. loader = self.get_loader(saltenv='test')
  153. jinja = Environment(loader=loader)
  154. return loader._file_client, jinja
  155. def test_import(self):
  156. '''
  157. You can import and use macros from other files
  158. '''
  159. fc, jinja = self.get_test_saltenv()
  160. result = jinja.get_template('hello_import').render()
  161. self.assertEqual(result, 'Hey world !a b !')
  162. assert len(fc.requests) == 2
  163. self.assertEqual(fc.requests[0]['path'], 'salt://hello_import')
  164. self.assertEqual(fc.requests[1]['path'], 'salt://macro')
  165. def test_relative_import(self):
  166. '''
  167. You can import using relative paths
  168. issue-13889
  169. '''
  170. fc, jinja = self.get_test_saltenv()
  171. tmpl = jinja.get_template(os.path.join('relative', 'rhello'))
  172. result = tmpl.render()
  173. self.assertEqual(result, 'Hey world !a b !')
  174. assert len(fc.requests) == 3
  175. self.assertEqual(fc.requests[0]['path'], os.path.join('salt://relative', 'rhello'))
  176. self.assertEqual(fc.requests[1]['path'], os.path.join('salt://relative', 'rmacro'))
  177. self.assertEqual(fc.requests[2]['path'], 'salt://macro')
  178. # This must fail when rendered: attempts to import from outside file root
  179. template = jinja.get_template('relative/rescape')
  180. self.assertRaises(exceptions.TemplateNotFound, template.render)
  181. def test_include(self):
  182. '''
  183. You can also include a template that imports and uses macros
  184. '''
  185. fc, jinja = self.get_test_saltenv()
  186. result = jinja.get_template('hello_include').render()
  187. self.assertEqual(result, 'Hey world !a b !')
  188. assert len(fc.requests) == 3
  189. self.assertEqual(fc.requests[0]['path'], 'salt://hello_include')
  190. self.assertEqual(fc.requests[1]['path'], 'salt://hello_import')
  191. self.assertEqual(fc.requests[2]['path'], 'salt://macro')
  192. def test_include_context(self):
  193. '''
  194. Context variables are passes to the included template by default.
  195. '''
  196. _, jinja = self.get_test_saltenv()
  197. result = jinja.get_template('hello_include').render(a='Hi', b='Salt')
  198. self.assertEqual(result, 'Hey world !Hi Salt !')
  199. class TestGetTemplate(TestCase):
  200. def setUp(self):
  201. self.tempdir = tempfile.mkdtemp()
  202. self.template_dir = os.path.join(self.tempdir, 'files', 'test')
  203. _setup_test_dir(
  204. os.path.join(BASE_FILES, 'templates'),
  205. self.template_dir
  206. )
  207. self.local_opts = {
  208. 'cachedir': self.tempdir,
  209. 'file_client': 'local',
  210. 'file_ignore_regex': None,
  211. 'file_ignore_glob': None,
  212. 'file_roots': {
  213. 'test': [self.template_dir]
  214. },
  215. 'pillar_roots': {
  216. 'test': [self.template_dir]
  217. },
  218. 'fileserver_backend': ['roots'],
  219. 'hash_type': 'md5',
  220. 'extension_modules': os.path.join(
  221. os.path.dirname(os.path.abspath(__file__)),
  222. 'extmods'),
  223. }
  224. self.local_salt = {}
  225. super(TestGetTemplate, self).setUp()
  226. def tearDown(self):
  227. salt.utils.files.rm_rf(self.tempdir)
  228. def test_fallback(self):
  229. '''
  230. A Template with a filesystem loader is returned as fallback
  231. if the file is not contained in the searchpath
  232. '''
  233. fn_ = os.path.join(self.template_dir, 'hello_simple')
  234. with salt.utils.files.fopen(fn_) as fp_:
  235. out = render_jinja_tmpl(
  236. salt.utils.stringutils.to_unicode(fp_.read()),
  237. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt)
  238. )
  239. self.assertEqual(out, 'world' + os.linesep)
  240. def test_fallback_noloader(self):
  241. '''
  242. A Template with a filesystem loader is returned as fallback
  243. if the file is not contained in the searchpath
  244. '''
  245. filename = os.path.join(self.template_dir, 'hello_import')
  246. with salt.utils.files.fopen(filename) as fp_:
  247. out = render_jinja_tmpl(
  248. salt.utils.stringutils.to_unicode(fp_.read()),
  249. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt)
  250. )
  251. self.assertEqual(out, 'Hey world !a b !' + os.linesep)
  252. def test_saltenv(self):
  253. '''
  254. If the template is within the searchpath it can
  255. import, include and extend other templates.
  256. The initial template is expected to be already cached
  257. get_template does not request it from the master again.
  258. '''
  259. fc = MockFileClient()
  260. with patch.object(SaltCacheLoader, 'file_client', MagicMock(return_value=fc)):
  261. filename = os.path.join(self.template_dir, 'hello_import')
  262. with salt.utils.files.fopen(filename) as fp_:
  263. out = render_jinja_tmpl(
  264. salt.utils.stringutils.to_unicode(fp_.read()),
  265. dict(opts={'cachedir': self.tempdir, 'file_client': 'remote',
  266. 'file_roots': self.local_opts['file_roots'],
  267. 'pillar_roots': self.local_opts['pillar_roots']},
  268. a='Hi', b='Salt', saltenv='test', salt=self.local_salt))
  269. self.assertEqual(out, 'Hey world !Hi Salt !' + os.linesep)
  270. self.assertEqual(fc.requests[0]['path'], 'salt://macro')
  271. def test_macro_additional_log_for_generalexc(self):
  272. '''
  273. If we failed in a macro because of e.g. a TypeError, get
  274. more output from trace.
  275. '''
  276. expected = r'''Jinja error:.*division.*
  277. .*macrogeneral\(2\):
  278. ---
  279. \{% macro mymacro\(\) -%\}
  280. \{\{ 1/0 \}\} <======================
  281. \{%- endmacro %\}
  282. ---.*'''
  283. filename = os.path.join(self.template_dir, 'hello_import_generalerror')
  284. fc = MockFileClient()
  285. with patch.object(SaltCacheLoader, 'file_client', MagicMock(return_value=fc)):
  286. with salt.utils.files.fopen(filename) as fp_:
  287. self.assertRaisesRegex(
  288. SaltRenderError,
  289. expected,
  290. render_jinja_tmpl,
  291. salt.utils.stringutils.to_unicode(fp_.read()),
  292. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  293. def test_macro_additional_log_for_undefined(self):
  294. '''
  295. If we failed in a macro because of undefined variables, get
  296. more output from trace.
  297. '''
  298. expected = r'''Jinja variable 'b' is undefined
  299. .*macroundefined\(2\):
  300. ---
  301. \{% macro mymacro\(\) -%\}
  302. \{\{b.greetee\}\} <-- error is here <======================
  303. \{%- endmacro %\}
  304. ---'''
  305. filename = os.path.join(self.template_dir, 'hello_import_undefined')
  306. fc = MockFileClient()
  307. with patch.object(SaltCacheLoader, 'file_client', MagicMock(return_value=fc)):
  308. with salt.utils.files.fopen(filename) as fp_:
  309. self.assertRaisesRegex(
  310. SaltRenderError,
  311. expected,
  312. render_jinja_tmpl,
  313. salt.utils.stringutils.to_unicode(fp_.read()),
  314. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  315. def test_macro_additional_log_syntaxerror(self):
  316. '''
  317. If we failed in a macro, get more output from trace.
  318. '''
  319. expected = r'''Jinja syntax error: expected token .*end.*got '-'.*
  320. .*macroerror\(2\):
  321. ---
  322. # macro
  323. \{% macro mymacro\(greeting, greetee='world'\) -\} <-- error is here <======================
  324. \{\{ greeting ~ ' ' ~ greetee \}\} !
  325. \{%- endmacro %\}
  326. ---.*'''
  327. filename = os.path.join(self.template_dir, 'hello_import_error')
  328. fc = MockFileClient()
  329. with patch.object(SaltCacheLoader, 'file_client', MagicMock(return_value=fc)):
  330. with salt.utils.files.fopen(filename) as fp_:
  331. self.assertRaisesRegex(
  332. SaltRenderError,
  333. expected,
  334. render_jinja_tmpl,
  335. salt.utils.stringutils.to_unicode(fp_.read()),
  336. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  337. def test_non_ascii_encoding(self):
  338. fc = MockFileClient()
  339. with patch.object(SaltCacheLoader, 'file_client', MagicMock(return_value=fc)):
  340. filename = os.path.join(self.template_dir, 'hello_import')
  341. with salt.utils.files.fopen(filename) as fp_:
  342. out = render_jinja_tmpl(
  343. salt.utils.stringutils.to_unicode(fp_.read()),
  344. dict(opts={'cachedir': self.tempdir, 'file_client': 'remote',
  345. 'file_roots': self.local_opts['file_roots'],
  346. 'pillar_roots': self.local_opts['pillar_roots']},
  347. a='Hi', b='Sàlt', saltenv='test', salt=self.local_salt))
  348. self.assertEqual(out, salt.utils.stringutils.to_unicode('Hey world !Hi Sàlt !' + os.linesep))
  349. self.assertEqual(fc.requests[0]['path'], 'salt://macro')
  350. filename = os.path.join(self.template_dir, 'non_ascii')
  351. with salt.utils.files.fopen(filename, 'rb') as fp_:
  352. out = render_jinja_tmpl(
  353. salt.utils.stringutils.to_unicode(fp_.read(), 'utf-8'),
  354. dict(opts={'cachedir': self.tempdir, 'file_client': 'remote',
  355. 'file_roots': self.local_opts['file_roots'],
  356. 'pillar_roots': self.local_opts['pillar_roots']},
  357. a='Hi', b='Sàlt', saltenv='test', salt=self.local_salt))
  358. self.assertEqual('Assunção' + os.linesep, out)
  359. self.assertEqual(fc.requests[0]['path'], 'salt://macro')
  360. @skipIf(HAS_TIMELIB is False, 'The `timelib` library is not installed.')
  361. def test_strftime(self):
  362. response = render_jinja_tmpl(
  363. '{{ "2002/12/25"|strftime }}',
  364. dict(
  365. opts=self.local_opts,
  366. saltenv='test',
  367. salt=self.local_salt
  368. ))
  369. self.assertEqual(response, '2002-12-25')
  370. objects = (
  371. datetime.datetime(2002, 12, 25, 12, 00, 00, 00),
  372. '2002/12/25',
  373. 1040814000,
  374. '1040814000'
  375. )
  376. for object in objects:
  377. response = render_jinja_tmpl(
  378. '{{ object|strftime }}',
  379. dict(
  380. object=object,
  381. opts=self.local_opts,
  382. saltenv='test',
  383. salt=self.local_salt
  384. ))
  385. self.assertEqual(response, '2002-12-25')
  386. response = render_jinja_tmpl(
  387. '{{ object|strftime("%b %d, %Y") }}',
  388. dict(
  389. object=object,
  390. opts=self.local_opts,
  391. saltenv='test',
  392. salt=self.local_salt
  393. ))
  394. self.assertEqual(response, 'Dec 25, 2002')
  395. response = render_jinja_tmpl(
  396. '{{ object|strftime("%y") }}',
  397. dict(
  398. object=object,
  399. opts=self.local_opts,
  400. saltenv='test',
  401. salt=self.local_salt
  402. ))
  403. self.assertEqual(response, '02')
  404. def test_non_ascii(self):
  405. fn = os.path.join(self.template_dir, 'non_ascii')
  406. out = JINJA(
  407. fn,
  408. opts=self.local_opts,
  409. saltenv='test',
  410. salt=self.local_salt
  411. )
  412. with salt.utils.files.fopen(out['data'], 'rb') as fp:
  413. result = salt.utils.stringutils.to_unicode(fp.read(), 'utf-8')
  414. self.assertEqual(salt.utils.stringutils.to_unicode('Assunção' + os.linesep), result)
  415. def test_get_context_has_enough_context(self):
  416. template = '1\n2\n3\n4\n5\n6\n7\n8\n9\na\nb\nc\nd\ne\nf'
  417. context = salt.utils.stringutils.get_context(template, 8)
  418. expected = '---\n[...]\n3\n4\n5\n6\n7\n8\n9\na\nb\nc\nd\n[...]\n---'
  419. self.assertEqual(expected, context)
  420. def test_get_context_at_top_of_file(self):
  421. template = '1\n2\n3\n4\n5\n6\n7\n8\n9\na\nb\nc\nd\ne\nf'
  422. context = salt.utils.stringutils.get_context(template, 1)
  423. expected = '---\n1\n2\n3\n4\n5\n6\n[...]\n---'
  424. self.assertEqual(expected, context)
  425. def test_get_context_at_bottom_of_file(self):
  426. template = '1\n2\n3\n4\n5\n6\n7\n8\n9\na\nb\nc\nd\ne\nf'
  427. context = salt.utils.stringutils.get_context(template, 15)
  428. expected = '---\n[...]\na\nb\nc\nd\ne\nf\n---'
  429. self.assertEqual(expected, context)
  430. def test_get_context_2_context_lines(self):
  431. template = '1\n2\n3\n4\n5\n6\n7\n8\n9\na\nb\nc\nd\ne\nf'
  432. context = salt.utils.stringutils.get_context(template, 8, num_lines=2)
  433. expected = '---\n[...]\n6\n7\n8\n9\na\n[...]\n---'
  434. self.assertEqual(expected, context)
  435. def test_get_context_with_marker(self):
  436. template = '1\n2\n3\n4\n5\n6\n7\n8\n9\na\nb\nc\nd\ne\nf'
  437. context = salt.utils.stringutils.get_context(template, 8, num_lines=2, marker=' <---')
  438. expected = '---\n[...]\n6\n7\n8 <---\n9\na\n[...]\n---'
  439. self.assertEqual(expected, context)
  440. def test_render_with_syntax_error(self):
  441. template = 'hello\n\n{{ bad\n\nfoo'
  442. expected = r'.*---\nhello\n\n{{ bad\n\nfoo <======================\n---'
  443. self.assertRaisesRegex(
  444. SaltRenderError,
  445. expected,
  446. render_jinja_tmpl,
  447. template,
  448. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt)
  449. )
  450. @skipIf(six.PY3, 'Not applicable to Python 3')
  451. @skipIf(NO_MOCK, NO_MOCK_REASON)
  452. def test_render_with_unicode_syntax_error(self):
  453. with patch.object(builtins, '__salt_system_encoding__', 'utf-8'):
  454. template = 'hello\n\n{{ bad\n\nfoo한'
  455. expected = r'.*---\nhello\n\n{{ bad\n\nfoo\xed\x95\x9c <======================\n---'
  456. self.assertRaisesRegex(
  457. SaltRenderError,
  458. expected,
  459. render_jinja_tmpl,
  460. template,
  461. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt)
  462. )
  463. @skipIf(NO_MOCK, NO_MOCK_REASON)
  464. def test_render_with_utf8_syntax_error(self):
  465. with patch.object(builtins, '__salt_system_encoding__', 'utf-8'):
  466. template = 'hello\n\n{{ bad\n\nfoo한'
  467. expected = salt.utils.stringutils.to_str(
  468. r'.*---\nhello\n\n{{ bad\n\nfoo한 <======================\n---'
  469. )
  470. self.assertRaisesRegex(
  471. SaltRenderError,
  472. expected,
  473. render_jinja_tmpl,
  474. template,
  475. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt)
  476. )
  477. def test_render_with_undefined_variable(self):
  478. template = "hello\n\n{{ foo }}\n\nfoo"
  479. expected = r'Jinja variable \'foo\' is undefined'
  480. self.assertRaisesRegex(
  481. SaltRenderError,
  482. expected,
  483. render_jinja_tmpl,
  484. template,
  485. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt)
  486. )
  487. def test_render_with_undefined_variable_utf8(self):
  488. template = "hello\xed\x95\x9c\n\n{{ foo }}\n\nfoo"
  489. expected = r'Jinja variable \'foo\' is undefined'
  490. self.assertRaisesRegex(
  491. SaltRenderError,
  492. expected,
  493. render_jinja_tmpl,
  494. template,
  495. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt)
  496. )
  497. def test_render_with_undefined_variable_unicode(self):
  498. template = 'hello한\n\n{{ foo }}\n\nfoo'
  499. expected = r'Jinja variable \'foo\' is undefined'
  500. self.assertRaisesRegex(
  501. SaltRenderError,
  502. expected,
  503. render_jinja_tmpl,
  504. template,
  505. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt)
  506. )
  507. class TestJinjaDefaultOptions(TestCase):
  508. def __init__(self, *args, **kws):
  509. TestCase.__init__(self, *args, **kws)
  510. self.local_opts = {
  511. 'cachedir': CACHEDIR,
  512. 'file_client': 'local',
  513. 'file_ignore_regex': None,
  514. 'file_ignore_glob': None,
  515. 'file_roots': {
  516. 'test': [os.path.join(BASE_FILES, 'templates')]
  517. },
  518. 'pillar_roots': {
  519. 'test': [os.path.join(BASE_FILES, 'templates')]
  520. },
  521. 'fileserver_backend': ['roots'],
  522. 'hash_type': 'md5',
  523. 'extension_modules': os.path.join(
  524. os.path.dirname(os.path.abspath(__file__)),
  525. 'extmods'),
  526. 'jinja_env': {
  527. 'line_comment_prefix': '##',
  528. 'line_statement_prefix': '%',
  529. },
  530. }
  531. self.local_salt = {
  532. 'myvar': 'zero',
  533. 'mylist': [0, 1, 2, 3],
  534. }
  535. def test_comment_prefix(self):
  536. template = """
  537. %- set myvar = 'one'
  538. ## ignored comment 1
  539. {{- myvar -}}
  540. {%- set myvar = 'two' %} ## ignored comment 2
  541. {{- myvar }} ## ignored comment 3
  542. %- if myvar == 'two':
  543. %- set myvar = 'three'
  544. %- endif
  545. {{- myvar -}}
  546. """
  547. rendered = render_jinja_tmpl(template,
  548. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  549. self.assertEqual(rendered, 'onetwothree')
  550. def test_statement_prefix(self):
  551. template = """
  552. {%- set mylist = ['1', '2', '3'] %}
  553. %- set mylist = ['one', 'two', 'three']
  554. %- for item in mylist:
  555. {{- item }}
  556. %- endfor
  557. """
  558. rendered = render_jinja_tmpl(template,
  559. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  560. self.assertEqual(rendered, 'onetwothree')
  561. class TestCustomExtensions(TestCase):
  562. def __init__(self, *args, **kws):
  563. super(TestCustomExtensions, self).__init__(*args, **kws)
  564. self.local_opts = {
  565. 'cachedir': CACHEDIR,
  566. 'file_client': 'local',
  567. 'file_ignore_regex': None,
  568. 'file_ignore_glob': None,
  569. 'file_roots': {
  570. 'test': [os.path.join(BASE_FILES, 'templates')]
  571. },
  572. 'pillar_roots': {
  573. 'test': [os.path.join(BASE_FILES, 'templates')]
  574. },
  575. 'fileserver_backend': ['roots'],
  576. 'hash_type': 'md5',
  577. 'extension_modules': os.path.join(
  578. os.path.dirname(os.path.abspath(__file__)),
  579. 'extmods'),
  580. }
  581. self.local_salt = {
  582. # 'dns.A': dnsutil.A,
  583. # 'dns.AAAA': dnsutil.AAAA,
  584. # 'file.exists': filemod.file_exists,
  585. # 'file.basename': filemod.basename,
  586. # 'file.dirname': filemod.dirname
  587. }
  588. def test_regex_escape(self):
  589. dataset = 'foo?:.*/\\bar'
  590. env = Environment(extensions=[SerializerExtension])
  591. env.filters.update(JinjaFilter.salt_jinja_filters)
  592. rendered = env.from_string('{{ dataset|regex_escape }}').render(dataset=dataset)
  593. self.assertEqual(rendered, re.escape(dataset))
  594. def test_unique_string(self):
  595. dataset = 'foo'
  596. unique = set(dataset)
  597. env = Environment(extensions=[SerializerExtension])
  598. env.filters.update(JinjaFilter.salt_jinja_filters)
  599. if six.PY3:
  600. rendered = env.from_string('{{ dataset|unique }}').render(dataset=dataset).strip("'{}").split("', '")
  601. self.assertEqual(sorted(rendered), sorted(list(unique)))
  602. else:
  603. rendered = env.from_string('{{ dataset|unique }}').render(dataset=dataset)
  604. self.assertEqual(rendered, "{0}".format(unique))
  605. def test_unique_tuple(self):
  606. dataset = ('foo', 'foo', 'bar')
  607. unique = set(dataset)
  608. env = Environment(extensions=[SerializerExtension])
  609. env.filters.update(JinjaFilter.salt_jinja_filters)
  610. if six.PY3:
  611. rendered = env.from_string('{{ dataset|unique }}').render(dataset=dataset).strip("'{}").split("', '")
  612. self.assertEqual(sorted(rendered), sorted(list(unique)))
  613. else:
  614. rendered = env.from_string('{{ dataset|unique }}').render(dataset=dataset)
  615. self.assertEqual(rendered, "{0}".format(unique))
  616. def test_unique_list(self):
  617. dataset = ['foo', 'foo', 'bar']
  618. unique = ['foo', 'bar']
  619. env = Environment(extensions=[SerializerExtension])
  620. env.filters.update(JinjaFilter.salt_jinja_filters)
  621. if six.PY3:
  622. rendered = env.from_string('{{ dataset|unique }}').render(dataset=dataset).strip("'[]").split("', '")
  623. self.assertEqual(rendered, unique)
  624. else:
  625. rendered = env.from_string('{{ dataset|unique }}').render(dataset=dataset)
  626. self.assertEqual(rendered, "{0}".format(unique))
  627. def test_serialize_json(self):
  628. dataset = {
  629. "foo": True,
  630. "bar": 42,
  631. "baz": [1, 2, 3],
  632. "qux": 2.0
  633. }
  634. env = Environment(extensions=[SerializerExtension])
  635. rendered = env.from_string('{{ dataset|json }}').render(dataset=dataset)
  636. self.assertEqual(dataset, salt.utils.json.loads(rendered))
  637. def test_serialize_yaml(self):
  638. dataset = {
  639. "foo": True,
  640. "bar": 42,
  641. "baz": [1, 2, 3],
  642. "qux": 2.0,
  643. "spam": OrderedDict([
  644. ('foo', OrderedDict([
  645. ('bar', 'baz'),
  646. ('qux', 42)
  647. ])
  648. )
  649. ])
  650. }
  651. env = Environment(extensions=[SerializerExtension])
  652. rendered = env.from_string('{{ dataset|yaml }}').render(dataset=dataset)
  653. self.assertEqual(dataset, salt.utils.yaml.safe_load(rendered))
  654. def test_serialize_yaml_str(self):
  655. dataset = "str value"
  656. env = Environment(extensions=[SerializerExtension])
  657. rendered = env.from_string('{{ dataset|yaml }}').render(dataset=dataset)
  658. self.assertEqual(dataset, rendered)
  659. def test_serialize_yaml_unicode(self):
  660. dataset = 'str value'
  661. env = Environment(extensions=[SerializerExtension])
  662. rendered = env.from_string('{{ dataset|yaml }}').render(dataset=dataset)
  663. if six.PY3:
  664. self.assertEqual("str value", rendered)
  665. else:
  666. # Due to a bug in the equality handler, this check needs to be split
  667. # up into several different assertions. We need to check that the various
  668. # string segments are present in the rendered value, as well as the
  669. # type of the rendered variable (should be unicode, which is the same as
  670. # six.text_type). This should cover all use cases but also allow the test
  671. # to pass on CentOS 6 running Python 2.7.
  672. self.assertIn('str value', rendered)
  673. self.assertIsInstance(rendered, six.text_type)
  674. def test_serialize_python(self):
  675. dataset = {
  676. "foo": True,
  677. "bar": 42,
  678. "baz": [1, 2, 3],
  679. "qux": 2.0
  680. }
  681. env = Environment(extensions=[SerializerExtension])
  682. rendered = env.from_string('{{ dataset|python }}').render(dataset=dataset)
  683. self.assertEqual(rendered, pprint.pformat(dataset))
  684. def test_load_yaml(self):
  685. env = Environment(extensions=[SerializerExtension])
  686. rendered = env.from_string('{% set document = "{foo: it works}"|load_yaml %}{{ document.foo }}').render()
  687. self.assertEqual(rendered, "it works")
  688. rendered = env.from_string('{% set document = document|load_yaml %}'
  689. '{{ document.foo }}').render(document="{foo: it works}")
  690. self.assertEqual(rendered, "it works")
  691. with self.assertRaises((TypeError, exceptions.TemplateRuntimeError)):
  692. env.from_string('{% set document = document|load_yaml %}'
  693. '{{ document.foo }}').render(document={"foo": "it works"})
  694. def test_load_tag(self):
  695. env = Environment(extensions=[SerializerExtension])
  696. source = '{{ bar }}, ' + \
  697. '{% load_yaml as docu %}{foo: it works, {{ bar }}: baz}{% endload %}' + \
  698. '{{ docu.foo }}'
  699. rendered = env.from_string(source).render(bar="barred")
  700. self.assertEqual(rendered, "barred, it works")
  701. source = '{{ bar }}, {% load_json as docu %}{"foo": "it works", "{{ bar }}": "baz"}{% endload %}' + \
  702. '{{ docu.foo }}'
  703. rendered = env.from_string(source).render(bar="barred")
  704. self.assertEqual(rendered, "barred, it works")
  705. with self.assertRaises(exceptions.TemplateSyntaxError):
  706. env.from_string('{% load_yamle as document %}{foo, bar: it works}{% endload %}').render()
  707. with self.assertRaises(exceptions.TemplateRuntimeError):
  708. env.from_string('{% load_json as document %}{foo, bar: it works}{% endload %}').render()
  709. def test_load_json(self):
  710. env = Environment(extensions=[SerializerExtension])
  711. rendered = env.from_string('{% set document = \'{"foo": "it works"}\'|load_json %}'
  712. '{{ document.foo }}').render()
  713. self.assertEqual(rendered, "it works")
  714. rendered = env.from_string('{% set document = document|load_json %}'
  715. '{{ document.foo }}').render(document='{"foo": "it works"}')
  716. self.assertEqual(rendered, "it works")
  717. # bad quotes
  718. with self.assertRaises(exceptions.TemplateRuntimeError):
  719. env.from_string("{{ document|load_json }}").render(document="{'foo': 'it works'}")
  720. # not a string
  721. with self.assertRaises(exceptions.TemplateRuntimeError):
  722. env.from_string('{{ document|load_json }}').render(document={"foo": "it works"})
  723. def test_load_yaml_template(self):
  724. loader = DictLoader({'foo': '{bar: "my god is blue", foo: [1, 2, 3]}'})
  725. env = Environment(extensions=[SerializerExtension], loader=loader)
  726. rendered = env.from_string('{% import_yaml "foo" as doc %}{{ doc.bar }}').render()
  727. self.assertEqual(rendered, "my god is blue")
  728. with self.assertRaises(exceptions.TemplateNotFound):
  729. env.from_string('{% import_yaml "does not exists" as doc %}').render()
  730. def test_load_json_template(self):
  731. loader = DictLoader({'foo': '{"bar": "my god is blue", "foo": [1, 2, 3]}'})
  732. env = Environment(extensions=[SerializerExtension], loader=loader)
  733. rendered = env.from_string('{% import_json "foo" as doc %}{{ doc.bar }}').render()
  734. self.assertEqual(rendered, "my god is blue")
  735. with self.assertRaises(exceptions.TemplateNotFound):
  736. env.from_string('{% import_json "does not exists" as doc %}').render()
  737. def test_load_text_template(self):
  738. loader = DictLoader({'foo': 'Foo!'})
  739. env = Environment(extensions=[SerializerExtension], loader=loader)
  740. rendered = env.from_string('{% import_text "foo" as doc %}{{ doc }}').render()
  741. self.assertEqual(rendered, "Foo!")
  742. with self.assertRaises(exceptions.TemplateNotFound):
  743. env.from_string('{% import_text "does not exists" as doc %}').render()
  744. def test_catalog(self):
  745. loader = DictLoader({
  746. 'doc1': '{bar: "my god is blue"}',
  747. 'doc2': '{% import_yaml "doc1" as local2 %} never exported',
  748. 'doc3': '{% load_yaml as local3 %}{"foo": "it works"}{% endload %} me neither',
  749. 'main1': '{% from "doc2" import local2 %}{{ local2.bar }}',
  750. 'main2': '{% from "doc3" import local3 %}{{ local3.foo }}',
  751. 'main3': '''
  752. {% import "doc2" as imported2 %}
  753. {% import "doc3" as imported3 %}
  754. {{ imported2.local2.bar }}
  755. ''',
  756. 'main4': '''
  757. {% import "doc2" as imported2 %}
  758. {% import "doc3" as imported3 %}
  759. {{ imported3.local3.foo }}
  760. ''',
  761. 'main5': '''
  762. {% from "doc2" import local2 as imported2 %}
  763. {% from "doc3" import local3 as imported3 %}
  764. {{ imported2.bar }}
  765. ''',
  766. 'main6': '''
  767. {% from "doc2" import local2 as imported2 %}
  768. {% from "doc3" import local3 as imported3 %}
  769. {{ imported3.foo }}
  770. '''
  771. })
  772. env = Environment(extensions=[SerializerExtension], loader=loader)
  773. rendered = env.get_template('main1').render()
  774. self.assertEqual(rendered, "my god is blue")
  775. rendered = env.get_template('main2').render()
  776. self.assertEqual(rendered, "it works")
  777. rendered = env.get_template('main3').render().strip()
  778. self.assertEqual(rendered, "my god is blue")
  779. rendered = env.get_template('main4').render().strip()
  780. self.assertEqual(rendered, "it works")
  781. rendered = env.get_template('main5').render().strip()
  782. self.assertEqual(rendered, "my god is blue")
  783. rendered = env.get_template('main6').render().strip()
  784. self.assertEqual(rendered, "it works")
  785. def test_nested_structures(self):
  786. env = Environment(extensions=[SerializerExtension])
  787. rendered = env.from_string('{{ data }}').render(data="foo")
  788. self.assertEqual(rendered, "foo")
  789. data = OrderedDict([
  790. ('foo', OrderedDict([
  791. ('bar', 'baz'),
  792. ('qux', 42)
  793. ])
  794. )
  795. ])
  796. rendered = env.from_string('{{ data }}').render(data=data)
  797. self.assertEqual(
  798. rendered,
  799. "{u'foo': {u'bar': u'baz', u'qux': 42}}" if six.PY2
  800. else "{'foo': {'bar': 'baz', 'qux': 42}}"
  801. )
  802. rendered = env.from_string('{{ data }}').render(data=[
  803. OrderedDict(
  804. foo='bar',
  805. ),
  806. OrderedDict(
  807. baz=42,
  808. )
  809. ])
  810. self.assertEqual(
  811. rendered,
  812. "[{'foo': u'bar'}, {'baz': 42}]" if six.PY2
  813. else "[{'foo': 'bar'}, {'baz': 42}]"
  814. )
  815. def test_sequence(self):
  816. env = Environment()
  817. env.filters['sequence'] = ensure_sequence_filter
  818. rendered = env.from_string('{{ data | sequence | length }}') \
  819. .render(data='foo')
  820. self.assertEqual(rendered, '1')
  821. rendered = env.from_string('{{ data | sequence | length }}') \
  822. .render(data=['foo', 'bar'])
  823. self.assertEqual(rendered, '2')
  824. rendered = env.from_string('{{ data | sequence | length }}') \
  825. .render(data=('foo', 'bar'))
  826. self.assertEqual(rendered, '2')
  827. rendered = env.from_string('{{ data | sequence | length }}') \
  828. .render(data=set(['foo', 'bar']))
  829. self.assertEqual(rendered, '2')
  830. rendered = env.from_string('{{ data | sequence | length }}') \
  831. .render(data={'foo': 'bar'})
  832. self.assertEqual(rendered, '1')
  833. def test_is_ip(self):
  834. '''
  835. Test the `is_ip` Jinja filter.
  836. '''
  837. rendered = render_jinja_tmpl("{{ '192.168.0.1' | is_ip }}",
  838. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  839. self.assertEqual(rendered, 'True')
  840. rendered = render_jinja_tmpl("{{ 'FE80::' | is_ip }}",
  841. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  842. self.assertEqual(rendered, 'True')
  843. rendered = render_jinja_tmpl("{{ 'random' | is_ip }}",
  844. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  845. self.assertEqual(rendered, 'False')
  846. def test_is_ipv4(self):
  847. '''
  848. Test the `is_ipv4` Jinja filter.
  849. '''
  850. rendered = render_jinja_tmpl("{{ '192.168.0.1' | is_ipv4 }}",
  851. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  852. self.assertEqual(rendered, 'True')
  853. rendered = render_jinja_tmpl("{{ 'FE80::' | is_ipv4 }}",
  854. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  855. self.assertEqual(rendered, 'False')
  856. rendered = render_jinja_tmpl("{{ 'random' | is_ipv4 }}",
  857. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  858. self.assertEqual(rendered, 'False')
  859. def test_is_ipv6(self):
  860. '''
  861. Test the `is_ipv6` Jinja filter.
  862. '''
  863. rendered = render_jinja_tmpl("{{ '192.168.0.1' | is_ipv6 }}",
  864. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  865. self.assertEqual(rendered, 'False')
  866. rendered = render_jinja_tmpl("{{ 'fe80::20d:b9ff:fe01:ea8%eth0' | is_ipv6 }}",
  867. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  868. self.assertEqual(rendered, 'True')
  869. rendered = render_jinja_tmpl("{{ 'FE80::' | is_ipv6 }}",
  870. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  871. self.assertEqual(rendered, 'True')
  872. rendered = render_jinja_tmpl("{{ 'random' | is_ipv6 }}",
  873. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  874. self.assertEqual(rendered, 'False')
  875. def test_ipaddr(self):
  876. '''
  877. Test the `ipaddr` Jinja filter.
  878. '''
  879. rendered = render_jinja_tmpl("{{ '::' | ipaddr }}",
  880. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  881. self.assertEqual(rendered, '::')
  882. rendered = render_jinja_tmpl("{{ '192.168.0.1' | ipaddr }}",
  883. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  884. self.assertEqual(rendered, '192.168.0.1')
  885. # provides a list with valid IP addresses only
  886. rendered = render_jinja_tmpl("{{ ['192.168.0.1', '172.17.17.1', 'foo', 'bar', '::'] | ipaddr | join(', ') }}",
  887. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  888. self.assertEqual(rendered, '192.168.0.1, 172.17.17.1, ::')
  889. # return only multicast addresses
  890. rendered = render_jinja_tmpl("{{ ['224.0.0.1', 'FF01::1', '::'] | ipaddr(options='multicast') | join(', ') }}",
  891. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  892. self.assertEqual(rendered, '224.0.0.1, ff01::1')
  893. def test_ipv4(self):
  894. '''
  895. Test the `ipv4` Jinja filter.
  896. '''
  897. rendered = render_jinja_tmpl("{{ '192.168.0.1' | ipv4 }}",
  898. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  899. self.assertEqual(rendered, '192.168.0.1')
  900. rendered = render_jinja_tmpl("{{ ['192.168.0.1', '172.17.17.1'] | ipv4 | join(', ')}}",
  901. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  902. self.assertEqual(rendered, '192.168.0.1, 172.17.17.1')
  903. rendered = render_jinja_tmpl("{{ 'fe80::' | ipv4 }}",
  904. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  905. self.assertEqual(rendered, 'None')
  906. rendered = render_jinja_tmpl("{{ 'random' | ipv4 }}",
  907. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  908. self.assertEqual(rendered, 'None')
  909. rendered = render_jinja_tmpl("{{ '192.168.0.1' | ipv4(options='lo') }}",
  910. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  911. self.assertEqual(rendered, 'None')
  912. rendered = render_jinja_tmpl("{{ '127.0.0.1' | ipv4(options='lo') }}",
  913. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  914. self.assertEqual(rendered, '127.0.0.1')
  915. def test_ipv6(self):
  916. '''
  917. Test the `ipv6` Jinja filter.
  918. '''
  919. rendered = render_jinja_tmpl("{{ '192.168.0.1' | ipv6 }}",
  920. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  921. self.assertEqual(rendered, 'None')
  922. rendered = render_jinja_tmpl("{{ 'random' | ipv6 }}",
  923. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  924. self.assertEqual(rendered, 'None')
  925. # returns the standard format value
  926. rendered = render_jinja_tmpl("{{ 'FE80:0:0::0' | ipv6 }}",
  927. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  928. self.assertEqual(rendered, 'fe80::')
  929. # fe80:: is link local therefore will be returned
  930. rendered = render_jinja_tmpl("{{ 'fe80::' | ipv6(options='ll') }}",
  931. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  932. self.assertEqual(rendered, 'fe80::')
  933. # fe80:: is not loopback
  934. rendered = render_jinja_tmpl("{{ 'fe80::' | ipv6(options='lo') }}",
  935. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  936. self.assertEqual(rendered, 'None')
  937. # returns only IPv6 addresses in the list
  938. rendered = render_jinja_tmpl("{{ ['fe80::', '192.168.0.1'] | ipv6 | join(', ') }}",
  939. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  940. self.assertEqual(rendered, 'fe80::')
  941. rendered = render_jinja_tmpl("{{ ['fe80::', '::'] | ipv6 | join(', ') }}",
  942. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  943. self.assertEqual(rendered, 'fe80::, ::')
  944. def test_network_hosts(self):
  945. '''
  946. Test the `network_hosts` Jinja filter.
  947. '''
  948. rendered = render_jinja_tmpl("{{ '192.168.0.1/30' | network_hosts | join(', ') }}",
  949. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  950. self.assertEqual(rendered, '192.168.0.1, 192.168.0.2')
  951. def test_network_size(self):
  952. '''
  953. Test the `network_size` Jinja filter.
  954. '''
  955. rendered = render_jinja_tmpl("{{ '192.168.0.1' | network_size }}",
  956. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  957. self.assertEqual(rendered, '1')
  958. rendered = render_jinja_tmpl("{{ '192.168.0.1/8' | network_size }}",
  959. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  960. self.assertEqual(rendered, '16777216')
  961. @flaky
  962. def test_http_query(self):
  963. '''
  964. Test the `http_query` Jinja filter.
  965. '''
  966. for backend in ('requests', 'tornado', 'urllib2'):
  967. rendered = render_jinja_tmpl("{{ 'http://icanhazip.com' | http_query(backend='" + backend + "') }}",
  968. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  969. self.assertIsInstance(rendered, six.text_type, 'Failed with backend: {}'.format(backend))
  970. dict_reply = ast.literal_eval(rendered)
  971. self.assertIsInstance(dict_reply, dict, 'Failed with backend: {}'.format(backend))
  972. self.assertIsInstance(dict_reply['body'], six.string_types, 'Failed with backend: {}'.format(backend))
  973. def test_to_bool(self):
  974. '''
  975. Test the `to_bool` Jinja filter.
  976. '''
  977. rendered = render_jinja_tmpl("{{ 1 | to_bool }}",
  978. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  979. self.assertEqual(rendered, 'True')
  980. rendered = render_jinja_tmpl("{{ 'True' | to_bool }}",
  981. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  982. self.assertEqual(rendered, 'True')
  983. rendered = render_jinja_tmpl("{{ 0 | to_bool }}",
  984. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  985. self.assertEqual(rendered, 'False')
  986. rendered = render_jinja_tmpl("{{ 'Yes' | to_bool }}",
  987. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  988. self.assertEqual(rendered, 'True')
  989. def test_quote(self):
  990. '''
  991. Test the `quote` Jinja filter.
  992. '''
  993. rendered = render_jinja_tmpl("{{ 'random' | quote }}",
  994. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  995. self.assertEqual(rendered, 'random')
  996. def test_regex_search(self):
  997. '''
  998. Test the `regex_search` Jinja filter.
  999. '''
  1000. rendered = render_jinja_tmpl("{{ 'abcdefabcdef' | regex_search('BC(.*)', ignorecase=True) }}",
  1001. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  1002. self.assertEqual(rendered, "('defabcdef',)") # because search looks only at the beginning
  1003. def test_regex_match(self):
  1004. '''
  1005. Test the `regex_match` Jinja filter.
  1006. '''
  1007. rendered = render_jinja_tmpl("{{ 'abcdefabcdef' | regex_match('BC(.*)', ignorecase=True)}}",
  1008. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  1009. self.assertEqual(rendered, "None")
  1010. def test_regex_replace(self):
  1011. '''
  1012. Test the `regex_replace` Jinja filter.
  1013. '''
  1014. rendered = render_jinja_tmpl(r"{{ 'lets replace spaces' | regex_replace('\s+', '__') }}",
  1015. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  1016. self.assertEqual(rendered, 'lets__replace__spaces')
  1017. def test_uuid(self):
  1018. '''
  1019. Test the `uuid` Jinja filter.
  1020. '''
  1021. rendered = render_jinja_tmpl("{{ 'random' | uuid }}",
  1022. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  1023. self.assertEqual(rendered, '3652b285-26ad-588e-a5dc-c2ee65edc804')
  1024. def test_min(self):
  1025. '''
  1026. Test the `min` Jinja filter.
  1027. '''
  1028. rendered = render_jinja_tmpl("{{ [1, 2, 3] | min }}",
  1029. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  1030. self.assertEqual(rendered, '1')
  1031. def test_max(self):
  1032. '''
  1033. Test the `max` Jinja filter.
  1034. '''
  1035. rendered = render_jinja_tmpl("{{ [1, 2, 3] | max }}",
  1036. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  1037. self.assertEqual(rendered, '3')
  1038. def test_avg(self):
  1039. '''
  1040. Test the `avg` Jinja filter.
  1041. '''
  1042. rendered = render_jinja_tmpl("{{ [1, 2, 3] | avg }}",
  1043. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  1044. self.assertEqual(rendered, '2.0')
  1045. def test_union(self):
  1046. '''
  1047. Test the `union` Jinja filter.
  1048. '''
  1049. rendered = render_jinja_tmpl("{{ [1, 2, 3] | union([2, 3, 4]) | join(', ') }}",
  1050. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  1051. self.assertEqual(rendered, '1, 2, 3, 4')
  1052. def test_intersect(self):
  1053. '''
  1054. Test the `intersect` Jinja filter.
  1055. '''
  1056. rendered = render_jinja_tmpl("{{ [1, 2, 3] | intersect([2, 3, 4]) | join(', ') }}",
  1057. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  1058. self.assertEqual(rendered, '2, 3')
  1059. def test_difference(self):
  1060. '''
  1061. Test the `difference` Jinja filter.
  1062. '''
  1063. rendered = render_jinja_tmpl("{{ [1, 2, 3] | difference([2, 3, 4]) | join(', ') }}",
  1064. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  1065. self.assertEqual(rendered, '1')
  1066. def test_symmetric_difference(self):
  1067. '''
  1068. Test the `symmetric_difference` Jinja filter.
  1069. '''
  1070. rendered = render_jinja_tmpl("{{ [1, 2, 3] | symmetric_difference([2, 3, 4]) | join(', ') }}",
  1071. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  1072. self.assertEqual(rendered, '1, 4')
  1073. def test_md5(self):
  1074. '''
  1075. Test the `md5` Jinja filter.
  1076. '''
  1077. rendered = render_jinja_tmpl("{{ 'random' | md5 }}",
  1078. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  1079. self.assertEqual(rendered, '7ddf32e17a6ac5ce04a8ecbf782ca509')
  1080. def test_sha256(self):
  1081. '''
  1082. Test the `sha256` Jinja filter.
  1083. '''
  1084. rendered = render_jinja_tmpl("{{ 'random' | sha256 }}",
  1085. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  1086. self.assertEqual(rendered, 'a441b15fe9a3cf56661190a0b93b9dec7d04127288cc87250967cf3b52894d11')
  1087. def test_sha512(self):
  1088. '''
  1089. Test the `sha512` Jinja filter.
  1090. '''
  1091. rendered = render_jinja_tmpl("{{ 'random' | sha512 }}",
  1092. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  1093. self.assertEqual(rendered, six.text_type(('811a90e1c8e86c7b4c0eef5b2c0bf0ec1b19c4b1b5a242e6455be93787cb473cb7bc'
  1094. '9b0fdeb960d00d5c6881c2094dd63c5c900ce9057255e2a4e271fc25fef1')))
  1095. def test_hmac(self):
  1096. '''
  1097. Test the `hmac` Jinja filter.
  1098. '''
  1099. rendered = render_jinja_tmpl("{{ 'random' | hmac('secret', 'blah') }}",
  1100. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  1101. self.assertEqual(rendered, 'False')
  1102. rendered = render_jinja_tmpl(("{{ 'get salted' | "
  1103. "hmac('shared secret', 'eBWf9bstXg+NiP5AOwppB5HMvZiYMPzEM9W5YMm/AmQ=') }}"),
  1104. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  1105. self.assertEqual(rendered, 'True')
  1106. def test_base64_encode(self):
  1107. '''
  1108. Test the `base64_encode` Jinja filter.
  1109. '''
  1110. rendered = render_jinja_tmpl("{{ 'random' | base64_encode }}",
  1111. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  1112. self.assertEqual(rendered, 'cmFuZG9t')
  1113. def test_base64_decode(self):
  1114. '''
  1115. Test the `base64_decode` Jinja filter.
  1116. '''
  1117. rendered = render_jinja_tmpl("{{ 'cmFuZG9t' | base64_decode }}",
  1118. dict(opts=self.local_opts, saltenv='test', salt=self.local_salt))
  1119. self.assertEqual(rendered, 'random')
  1120. # def test_print(self):
  1121. # env = Environment(extensions=[SerializerExtension])
  1122. # source = '{% import_yaml "toto.foo" as docu %}'
  1123. # name, filename = None, '<filename>'
  1124. # parsed = env._parse(source, name, filename)
  1125. # print parsed
  1126. # print
  1127. # compiled = env._generate(parsed, name, filename)
  1128. # print compiled
  1129. # return
  1130. class TestDotNotationLookup(ModuleCase):
  1131. '''
  1132. Tests to call Salt functions via Jinja with various lookup syntaxes
  1133. '''
  1134. def setUp(self, *args, **kwargs):
  1135. functions = {
  1136. 'mocktest.ping': lambda: True,
  1137. 'mockgrains.get': lambda x: 'jerry',
  1138. }
  1139. minion_opts = salt.config.minion_config(os.path.join(TMP_CONF_DIR, 'minion'))
  1140. render = salt.loader.render(minion_opts, functions)
  1141. self.jinja = render.get('jinja')
  1142. def tearDown(self):
  1143. del self.jinja
  1144. def render(self, tmpl_str, context=None):
  1145. return self.jinja(tmpl_str, context=context or {}, from_str=True).read()
  1146. def test_normlookup(self):
  1147. '''
  1148. Sanity-check the normal dictionary-lookup syntax for our stub function
  1149. '''
  1150. tmpl_str = '''Hello, {{ salt['mocktest.ping']() }}.'''
  1151. with patch.object(SaltCacheLoader, 'file_client', Mock()):
  1152. ret = self.render(tmpl_str)
  1153. self.assertEqual(ret, 'Hello, True.')
  1154. def test_dotlookup(self):
  1155. '''
  1156. Check calling a stub function using awesome dot-notation
  1157. '''
  1158. tmpl_str = '''Hello, {{ salt.mocktest.ping() }}.'''
  1159. with patch.object(SaltCacheLoader, 'file_client', Mock()):
  1160. ret = self.render(tmpl_str)
  1161. self.assertEqual(ret, 'Hello, True.')
  1162. def test_shadowed_dict_method(self):
  1163. '''
  1164. Check calling a stub function with a name that shadows a ``dict``
  1165. method name
  1166. '''
  1167. tmpl_str = '''Hello, {{ salt.mockgrains.get('id') }}.'''
  1168. with patch.object(SaltCacheLoader, 'file_client', Mock()):
  1169. ret = self.render(tmpl_str)
  1170. self.assertEqual(ret, 'Hello, jerry.')