saltstack / salt

Software to automate the management and configuration of any infrastructure or application at scale. Install Salt from the Salt package repositories here:
https://docs.saltproject.io/salt/install-guide/en/latest/
Apache License 2.0
14.22k stars 5.49k forks source link

Template renderers cannot receive results of gpg renderer #54200

Open terminalmage opened 5 years ago

terminalmage commented 5 years ago

The GPG renderer is a bit different from most renderers in that it is designed to operate recursively on objects, returning a copy of the object with all the ciphertext decrypted. This works great for a number of applications outside of SLS rendering, but it means that any of the template renderers (jinja, etc.) fail if they are expected to operate on the results of the gpg renderer. For example:

#!gpg|jinja|yaml
-----BEGIN PGP MESSAGE----

.... (ciphertext here) ...
-----END PGP MESSAGE-----

In this case, the gpg renderer will return a string, but this will cause a traceback when it hits the jinja renderer, because from_str is not set. The relevant block of code is here, taken from the head of the 2018.3 branch (note that this issue is also present in all non-EOL Salt releases).

It is not clear to me whether the correct fix is to modify the renderers to pass from_str=True when the input is not file-like, or change the logic in the block of code I linked. I'm not sure what the reason is for from_str, and what edge case this is fixing, and don't want to make a fix that will break other code in the process. Any feedback is appreciated.

Steps to reproduce

Setup

>>> import salt.loader
>>> import salt.config
>>> opts = salt.config.minion_config('/etc/salt/minion')
opts>>> opts['file_client'] = 'local'
>>> rend = salt.loader.render(opts, {'test.ping': lambda: True})
>>> template_data = "{{ salt['test.ping']() }}"

Simulate passing as a string (as in what happens with GPG renderer)

>>> result = rend['jinja'](template_data)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/testing/salt/renderers/jinja.py", line 70, in render
    **kws)
  File "/testing/salt/utils/templates.py", line 165, in render_tmpl
    six.reraise(*sys.exc_info())
  File "/testing/salt/utils/templates.py", line 151, in render_tmpl
    with codecs.open(tmplsrc, 'r', SLS_ENCODING) as _tmplsrc:
  File "/usr/lib64/python2.7/codecs.py", line 881, in open
    file = __builtin__.open(filename, mode, buffering)
IOError: [Errno 2] No such file or directory: "{{ salt['test.ping']() }}"

Simulating passing data in as a StringIO (works as expected)


>>> from salt.ext.six.moves import StringIO
>>> result = rend['jinja'](StringIO(template_data))
>>> result.read()
u'True'
terminalmage commented 5 years ago

As a temporary workaround, I wrote a custom renderer called filelike.py. This allows me to use a render pipe of #!gpg|filelike|jinja|yaml and ensure that jinja is receiving a StringIO. The source is here for anyone who needs it:

# -*- coding: utf-8 -*-
'''
Renderer that can be used as a shim to ensure file-like output for a subsequent
renderer in the render pipe.
'''
from __future__ import absolute_import, print_function, unicode_literals

# Import Salt libs
from salt.exceptions import SaltRenderError

# Import 3rd-party libs
from salt.ext import six
from salt.ext.six.moves import StringIO  # pylint: disable=import-error

def render(data, saltenv='base', sls='', argline='', **kws):
    if hasattr(data, 'read'):
        return data
    elif isinstance(data, six.string_types):
        return StringIO(data)
    raise SaltRenderError(
        'Input to filelike renderer must be string or file-like'
    )