summaryrefslogtreecommitdiff
path: root/lib/ansible
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ansible')
-rwxr-xr-xlib/ansible/cli/pull.py7
-rw-r--r--lib/ansible/module_utils/ansible_release.py2
-rw-r--r--lib/ansible/module_utils/common/json.py4
-rw-r--r--lib/ansible/modules/pip.py2
-rw-r--r--lib/ansible/parsing/yaml/dumper.py6
-rw-r--r--lib/ansible/playbook/conditional.py9
-rw-r--r--lib/ansible/playbook/task.py24
-rw-r--r--lib/ansible/plugins/action/assert.py23
-rw-r--r--lib/ansible/plugins/callback/__init__.py4
-rw-r--r--lib/ansible/plugins/filter/core.py5
-rw-r--r--lib/ansible/plugins/lookup/first_found.py15
-rw-r--r--lib/ansible/release.py2
-rw-r--r--lib/ansible/template/__init__.py15
-rw-r--r--lib/ansible/utils/unsafe_proxy.py265
14 files changed, 354 insertions, 29 deletions
diff --git a/lib/ansible/cli/pull.py b/lib/ansible/cli/pull.py
index dc8f055b..47084989 100755
--- a/lib/ansible/cli/pull.py
+++ b/lib/ansible/cli/pull.py
@@ -29,6 +29,7 @@ from ansible.plugins.loader import module_loader
from ansible.utils.cmd_functions import run_cmd
from ansible.utils.display import Display
+
display = Display()
@@ -102,8 +103,8 @@ class PullCLI(CLI):
'This is a useful way to disperse git requests')
self.parser.add_argument('-f', '--force', dest='force', default=False, action='store_true',
help='run the playbook even if the repository could not be updated')
- self.parser.add_argument('-d', '--directory', dest='dest', default=None,
- help='absolute path of repository checkout directory (relative paths are not supported)')
+ self.parser.add_argument('-d', '--directory', dest='dest', default=None, type=opt_help.unfrack_path(),
+ help='path to the directory to which Ansible will checkout the repository.')
self.parser.add_argument('-U', '--url', dest='url', default=None, help='URL of the playbook repository')
self.parser.add_argument('--full', dest='fullclone', action='store_true', help='Do a full clone, instead of a shallow one.')
self.parser.add_argument('-C', '--checkout', dest='checkout',
@@ -134,7 +135,6 @@ class PullCLI(CLI):
hostname = socket.getfqdn()
# use a hostname dependent directory, in case of $HOME on nfs
options.dest = os.path.join(C.ANSIBLE_HOME, 'pull', hostname)
- options.dest = os.path.expandvars(os.path.expanduser(options.dest))
if os.path.exists(options.dest) and not os.path.isdir(options.dest):
raise AnsibleOptionsError("%s is not a valid or accessible directory." % options.dest)
@@ -307,6 +307,7 @@ class PullCLI(CLI):
if context.CLIARGS['purge']:
os.chdir('/')
try:
+ display.debug("removing: %s" % context.CLIARGS['dest'])
shutil.rmtree(context.CLIARGS['dest'])
except Exception as e:
display.error(u"Failed to remove %s: %s" % (context.CLIARGS['dest'], to_text(e)))
diff --git a/lib/ansible/module_utils/ansible_release.py b/lib/ansible/module_utils/ansible_release.py
index 6f0f794f..5fc1bde1 100644
--- a/lib/ansible/module_utils/ansible_release.py
+++ b/lib/ansible/module_utils/ansible_release.py
@@ -19,6 +19,6 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-__version__ = '2.14.11'
+__version__ = '2.14.13'
__author__ = 'Ansible, Inc.'
__codename__ = "C'mon Everybody"
diff --git a/lib/ansible/module_utils/common/json.py b/lib/ansible/module_utils/common/json.py
index 727083ca..c4333fc1 100644
--- a/lib/ansible/module_utils/common/json.py
+++ b/lib/ansible/module_utils/common/json.py
@@ -30,7 +30,7 @@ def _preprocess_unsafe_encode(value):
Used in ``AnsibleJSONEncoder.iterencode``
"""
if _is_unsafe(value):
- value = {'__ansible_unsafe': to_text(value, errors='surrogate_or_strict', nonstring='strict')}
+ value = {'__ansible_unsafe': to_text(value._strip_unsafe(), errors='surrogate_or_strict', nonstring='strict')}
elif is_sequence(value):
value = [_preprocess_unsafe_encode(v) for v in value]
elif isinstance(value, Mapping):
@@ -63,7 +63,7 @@ class AnsibleJSONEncoder(json.JSONEncoder):
value = {'__ansible_vault': to_text(o._ciphertext, errors='surrogate_or_strict', nonstring='strict')}
elif getattr(o, '__UNSAFE__', False):
# unsafe object, this will never be triggered, see ``AnsibleJSONEncoder.iterencode``
- value = {'__ansible_unsafe': to_text(o, errors='surrogate_or_strict', nonstring='strict')}
+ value = {'__ansible_unsafe': to_text(o._strip_unsafe(), errors='surrogate_or_strict', nonstring='strict')}
elif isinstance(o, Mapping):
# hostvars and other objects
value = dict(o)
diff --git a/lib/ansible/modules/pip.py b/lib/ansible/modules/pip.py
index a9930ccd..95a5d0d3 100644
--- a/lib/ansible/modules/pip.py
+++ b/lib/ansible/modules/pip.py
@@ -121,6 +121,8 @@ attributes:
platform:
platforms: posix
notes:
+ - Python installations marked externally-managed (as defined by PEP668) cannot be updated by pip versions >= 23.0.1 without the use of
+ a virtual environment or setting the environment variable ``PIP_BREAK_SYSTEM_PACKAGES=1``.
- The virtualenv (U(http://www.virtualenv.org/)) must be
installed on the remote host if the virtualenv parameter is specified and
the virtualenv needs to be created.
diff --git a/lib/ansible/parsing/yaml/dumper.py b/lib/ansible/parsing/yaml/dumper.py
index 8701bb81..bf2c0843 100644
--- a/lib/ansible/parsing/yaml/dumper.py
+++ b/lib/ansible/parsing/yaml/dumper.py
@@ -24,7 +24,7 @@ import yaml
from ansible.module_utils.six import text_type, binary_type
from ansible.module_utils.common.yaml import SafeDumper
from ansible.parsing.yaml.objects import AnsibleUnicode, AnsibleSequence, AnsibleMapping, AnsibleVaultEncryptedUnicode
-from ansible.utils.unsafe_proxy import AnsibleUnsafeText, AnsibleUnsafeBytes, NativeJinjaUnsafeText, NativeJinjaText
+from ansible.utils.unsafe_proxy import AnsibleUnsafeText, AnsibleUnsafeBytes, NativeJinjaUnsafeText, NativeJinjaText, _is_unsafe
from ansible.template import AnsibleUndefined
from ansible.vars.hostvars import HostVars, HostVarsVars
from ansible.vars.manager import VarsWithSources
@@ -47,10 +47,14 @@ def represent_vault_encrypted_unicode(self, data):
def represent_unicode(self, data):
+ if _is_unsafe(data):
+ data = data._strip_unsafe()
return yaml.representer.SafeRepresenter.represent_str(self, text_type(data))
def represent_binary(self, data):
+ if _is_unsafe(data):
+ data = data._strip_unsafe()
return yaml.representer.SafeRepresenter.represent_binary(self, binary_type(data))
diff --git a/lib/ansible/playbook/conditional.py b/lib/ansible/playbook/conditional.py
index fe07358c..d994f8f4 100644
--- a/lib/ansible/playbook/conditional.py
+++ b/lib/ansible/playbook/conditional.py
@@ -26,7 +26,7 @@ from jinja2.compiler import generate
from jinja2.exceptions import UndefinedError
from ansible import constants as C
-from ansible.errors import AnsibleError, AnsibleUndefinedVariable
+from ansible.errors import AnsibleError, AnsibleUndefinedVariable, AnsibleTemplateError
from ansible.module_utils.six import text_type
from ansible.module_utils._text import to_native, to_text
from ansible.playbook.attribute import FieldAttribute
@@ -138,9 +138,10 @@ class Conditional:
if not isinstance(conditional, text_type) or conditional == "":
return conditional
- # update the lookups flag, as the string returned above may now be unsafe
- # and we don't want future templating calls to do unsafe things
- disable_lookups |= hasattr(conditional, '__UNSAFE__')
+ # If the result of the first-pass template render (to resolve inline templates) is marked unsafe,
+ # explicitly fail since the next templating operation would never evaluate
+ if hasattr(conditional, '__UNSAFE__'):
+ raise AnsibleTemplateError('Conditional is marked as unsafe, and cannot be evaluated.')
# First, we do some low-level jinja2 parsing involving the AST format of the
# statement to ensure we don't do anything unsafe (using the disable_lookup flag above)
diff --git a/lib/ansible/playbook/task.py b/lib/ansible/playbook/task.py
index ba35fcf0..a1a1162b 100644
--- a/lib/ansible/playbook/task.py
+++ b/lib/ansible/playbook/task.py
@@ -290,6 +290,30 @@ class Task(Base, Conditional, Taggable, CollectionSearch):
super(Task, self).post_validate(templar)
+ def _post_validate_args(self, attr, value, templar):
+ # smuggle an untemplated copy of the task args for actions that need more control over the templating of their
+ # input (eg, debug's var/msg, assert's "that" conditional expressions)
+ self.untemplated_args = value
+
+ # now recursively template the args dict
+ args = templar.template(value)
+
+ # FIXME: could we just nuke this entirely and/or wrap it up in ModuleArgsParser or something?
+ if '_variable_params' in args:
+ variable_params = args.pop('_variable_params')
+ if isinstance(variable_params, dict):
+ if C.INJECT_FACTS_AS_VARS:
+ display.warning("Using a variable for a task's 'args' is unsafe in some situations "
+ "(see https://docs.ansible.com/ansible/devel/reference_appendices/faq.html#argsplat-unsafe)")
+ variable_params.update(args)
+ args = variable_params
+ else:
+ # if we didn't get a dict, it means there's garbage remaining after k=v parsing, just give up
+ # see https://github.com/ansible/ansible/issues/79862
+ raise AnsibleError(f"invalid or malformed argument: '{variable_params}'")
+
+ return args
+
def _post_validate_loop(self, attr, value, templar):
'''
Override post validation for the loop field, which is templated
diff --git a/lib/ansible/plugins/action/assert.py b/lib/ansible/plugins/action/assert.py
index 7721a6b4..e8ab6a9a 100644
--- a/lib/ansible/plugins/action/assert.py
+++ b/lib/ansible/plugins/action/assert.py
@@ -63,8 +63,29 @@ class ActionModule(ActionBase):
quiet = boolean(self._task.args.get('quiet', False), strict=False)
+ # directly access 'that' via untemplated args from the task so we can intelligently trust embedded
+ # templates and preserve the original inputs/locations for better messaging on assert failures and
+ # errors.
+ # FIXME: even in devel, things like `that: item` don't always work properly (truthy string value
+ # is not really an embedded expression)
+ # we could fix that by doing direct var lookups on the inputs
+ # FIXME: some form of this code should probably be shared between debug, assert, and
+ # Task.post_validate, since they
+ # have a lot of overlapping needs
+ try:
+ thats = self._task.untemplated_args['that']
+ except KeyError:
+ # in the case of "we got our entire args dict from a template", we can just consult the
+ # post-templated dict (the damage has likely already been done for embedded templates anyway)
+ thats = self._task.args['that']
+
+ # FIXME: this is a case where we only want to resolve indirections, NOT recurse containers
+ # (and even then, the leaf-most expression being wrapped is at least suboptimal
+ # (since its expression will be "eaten").
+ if isinstance(thats, str):
+ thats = self._templar.template(thats)
+
# make sure the 'that' items are a list
- thats = self._task.args['that']
if not isinstance(thats, list):
thats = [thats]
diff --git a/lib/ansible/plugins/callback/__init__.py b/lib/ansible/plugins/callback/__init__.py
index d4fc347d..7646d293 100644
--- a/lib/ansible/plugins/callback/__init__.py
+++ b/lib/ansible/plugins/callback/__init__.py
@@ -38,7 +38,7 @@ from ansible.parsing.yaml.objects import AnsibleUnicode
from ansible.plugins import AnsiblePlugin
from ansible.utils.color import stringc
from ansible.utils.display import Display
-from ansible.utils.unsafe_proxy import AnsibleUnsafeText, NativeJinjaUnsafeText
+from ansible.utils.unsafe_proxy import AnsibleUnsafeText, NativeJinjaUnsafeText, _is_unsafe
from ansible.vars.clean import strip_internal_keys, module_response_deepcopy
import yaml
@@ -113,6 +113,8 @@ def _munge_data_for_lossy_yaml(scalar):
def _pretty_represent_str(self, data):
"""Uses block style for multi-line strings"""
+ if _is_unsafe(data):
+ data = data._strip_unsafe()
data = text_type(data)
if _should_use_block(data):
style = '|'
diff --git a/lib/ansible/plugins/filter/core.py b/lib/ansible/plugins/filter/core.py
index 52a2cd10..b7e2c11e 100644
--- a/lib/ansible/plugins/filter/core.py
+++ b/lib/ansible/plugins/filter/core.py
@@ -37,6 +37,7 @@ from ansible.utils.display import Display
from ansible.utils.encrypt import passlib_or_crypt
from ansible.utils.hashing import md5s, checksum_s
from ansible.utils.unicode import unicode_wrap
+from ansible.utils.unsafe_proxy import _is_unsafe
from ansible.utils.vars import merge_hash
display = Display()
@@ -215,6 +216,8 @@ def from_yaml(data):
# The ``text_type`` call here strips any custom
# string wrapper class, so that CSafeLoader can
# read the data
+ if _is_unsafe(data):
+ data = data._strip_unsafe()
return yaml_load(text_type(to_text(data, errors='surrogate_or_strict')))
return data
@@ -224,6 +227,8 @@ def from_yaml_all(data):
# The ``text_type`` call here strips any custom
# string wrapper class, so that CSafeLoader can
# read the data
+ if _is_unsafe(data):
+ data = data._strip_unsafe()
return yaml_load_all(text_type(to_text(data, errors='surrogate_or_strict')))
return data
diff --git a/lib/ansible/plugins/lookup/first_found.py b/lib/ansible/plugins/lookup/first_found.py
index 5b94b103..a882db01 100644
--- a/lib/ansible/plugins/lookup/first_found.py
+++ b/lib/ansible/plugins/lookup/first_found.py
@@ -136,7 +136,6 @@ RETURN = """
elements: path
"""
import os
-import re
from collections.abc import Mapping, Sequence
@@ -147,10 +146,22 @@ from ansible.module_utils.six import string_types
from ansible.plugins.lookup import LookupBase
+def _splitter(value, chars):
+ chars = set(chars)
+ v = ''
+ for c in value:
+ if c in chars:
+ yield v
+ v = ''
+ continue
+ v += c
+ yield v
+
+
def _split_on(terms, spliters=','):
termlist = []
if isinstance(terms, string_types):
- termlist = re.split(r'[%s]' % ''.join(map(re.escape, spliters)), terms)
+ termlist = list(_splitter(terms, spliters))
else:
# added since options will already listify
for t in terms:
diff --git a/lib/ansible/release.py b/lib/ansible/release.py
index 6f0f794f..5fc1bde1 100644
--- a/lib/ansible/release.py
+++ b/lib/ansible/release.py
@@ -19,6 +19,6 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-__version__ = '2.14.11'
+__version__ = '2.14.13'
__author__ = 'Ansible, Inc.'
__codename__ = "C'mon Everybody"
diff --git a/lib/ansible/template/__init__.py b/lib/ansible/template/__init__.py
index baa85ed7..c45cfe35 100644
--- a/lib/ansible/template/__init__.py
+++ b/lib/ansible/template/__init__.py
@@ -31,7 +31,7 @@ from contextlib import contextmanager
from numbers import Number
from traceback import format_exc
-from jinja2.exceptions import TemplateSyntaxError, UndefinedError
+from jinja2.exceptions import TemplateSyntaxError, UndefinedError, SecurityError
from jinja2.loaders import FileSystemLoader
from jinja2.nativetypes import NativeEnvironment
from jinja2.runtime import Context, StrictUndefined
@@ -55,7 +55,7 @@ from ansible.template.vars import AnsibleJ2Vars
from ansible.utils.display import Display
from ansible.utils.listify import listify_lookup_plugin_terms
from ansible.utils.native_jinja import NativeJinjaText
-from ansible.utils.unsafe_proxy import wrap_var
+from ansible.utils.unsafe_proxy import wrap_var, AnsibleUnsafeText, AnsibleUnsafeBytes, NativeJinjaUnsafeText
display = Display()
@@ -332,10 +332,21 @@ class AnsibleContext(Context):
flag is checked post-templating, and (when set) will result in the
final templated result being wrapped in AnsibleUnsafe.
'''
+ _disallowed_callables = frozenset({
+ AnsibleUnsafeText._strip_unsafe.__qualname__,
+ AnsibleUnsafeBytes._strip_unsafe.__qualname__,
+ NativeJinjaUnsafeText._strip_unsafe.__qualname__,
+ })
+
def __init__(self, *args, **kwargs):
super(AnsibleContext, self).__init__(*args, **kwargs)
self.unsafe = False
+ def call(self, obj, *args, **kwargs):
+ if getattr(obj, '__qualname__', None) in self._disallowed_callables or obj in self._disallowed_callables:
+ raise SecurityError(f"{obj!r} is not safely callable")
+ return super().call(obj, *args, **kwargs)
+
def _is_unsafe(self, val):
'''
Our helper function, which will also recursively check dict and
diff --git a/lib/ansible/utils/unsafe_proxy.py b/lib/ansible/utils/unsafe_proxy.py
index d78ebf6e..683f6e27 100644
--- a/lib/ansible/utils/unsafe_proxy.py
+++ b/lib/ansible/utils/unsafe_proxy.py
@@ -57,7 +57,6 @@ from collections.abc import Mapping, Set
from ansible.module_utils._text import to_bytes, to_text
from ansible.module_utils.common.collections import is_sequence
-from ansible.module_utils.six import string_types, binary_type, text_type
from ansible.utils.native_jinja import NativeJinjaText
@@ -68,16 +67,256 @@ class AnsibleUnsafe(object):
__UNSAFE__ = True
-class AnsibleUnsafeBytes(binary_type, AnsibleUnsafe):
- def decode(self, *args, **kwargs):
- """Wrapper method to ensure type conversions maintain unsafe context"""
- return AnsibleUnsafeText(super(AnsibleUnsafeBytes, self).decode(*args, **kwargs))
+class AnsibleUnsafeBytes(bytes, AnsibleUnsafe):
+ def _strip_unsafe(self):
+ return super().__bytes__()
+ def __reduce__(self, /):
+ return (self.__class__, (self._strip_unsafe(),))
-class AnsibleUnsafeText(text_type, AnsibleUnsafe):
- def encode(self, *args, **kwargs):
- """Wrapper method to ensure type conversions maintain unsafe context"""
- return AnsibleUnsafeBytes(super(AnsibleUnsafeText, self).encode(*args, **kwargs))
+ def __str__(self, /): # pylint: disable=invalid-str-returned
+ return self.decode()
+
+ def __bytes__(self, /): # pylint: disable=invalid-bytes-returned
+ return self
+
+ def __repr__(self, /): # pylint: disable=invalid-repr-returned
+ return AnsibleUnsafeText(super().__repr__())
+
+ def __format__(self, format_spec, /): # pylint: disable=invalid-format-returned
+ return AnsibleUnsafeText(super().__format__(format_spec))
+
+ def __getitem__(self, key, /):
+ if isinstance(key, int):
+ return super().__getitem__(key)
+ return self.__class__(super().__getitem__(key))
+
+ def __reversed__(self, /):
+ return self[::-1]
+
+ def __add__(self, value, /):
+ return self.__class__(super().__add__(value))
+
+ def __radd__(self, value, /):
+ return self.__class__(value.__add__(self))
+
+ def __mul__(self, value, /):
+ return self.__class__(super().__mul__(value))
+
+ __rmul__ = __mul__
+
+ def __mod__(self, value, /):
+ return self.__class__(super().__mod__(value))
+
+ def __rmod__(self, value, /):
+ return self.__class__(super().__rmod__(value))
+
+ def capitalize(self, /):
+ return self.__class__(super().capitalize())
+
+ def center(self, width, fillchar=b' ', /):
+ return self.__class__(super().center(width, fillchar))
+
+ def decode(self, /, encoding='utf-8', errors='strict'):
+ return AnsibleUnsafeText(super().decode(encoding=encoding, errors=errors))
+
+ def removeprefix(self, prefix, /):
+ return self.__class__(super().removeprefix(prefix))
+
+ def removesuffix(self, suffix, /):
+ return self.__class__(super().removesuffix(suffix))
+
+ def expandtabs(self, /, tabsize=8):
+ return self.__class__(super().expandtabs(tabsize))
+
+ def join(self, iterable_of_bytes, /):
+ return self.__class__(super().join(iterable_of_bytes))
+
+ def ljust(self, width, fillchar=b' ', /):
+ return self.__class__(super().ljust(width, fillchar))
+
+ def lower(self, /):
+ return self.__class__(super().lower())
+
+ def lstrip(self, chars=None, /):
+ return self.__class__(super().lstrip(chars))
+
+ def partition(self, sep, /):
+ cls = self.__class__
+ return tuple(cls(e) for e in super().partition(sep))
+
+ def replace(self, old, new, count=-1, /):
+ return self.__class__(super().replace(old, new, count))
+
+ def rjust(self, width, fillchar=b' ', /):
+ return self.__class__(super().rjust(width, fillchar))
+
+ def rpartition(self, sep, /):
+ cls = self.__class__
+ return tuple(cls(e) for e in super().rpartition(sep))
+
+ def rstrip(self, chars=None, /):
+ return self.__class__(super().rstrip(chars))
+
+ def split(self, /, sep=None, maxsplit=-1):
+ cls = self.__class__
+ return [cls(e) for e in super().split(sep=sep, maxsplit=maxsplit)]
+
+ def rsplit(self, /, sep=None, maxsplit=-1):
+ cls = self.__class__
+ return [cls(e) for e in super().rsplit(sep=sep, maxsplit=maxsplit)]
+
+ def splitlines(self, /, keepends=False):
+ cls = self.__class__
+ return [cls(e) for e in super().splitlines(keepends=keepends)]
+
+ def strip(self, chars=None, /):
+ return self.__class__(super().strip(chars))
+
+ def swapcase(self, /):
+ return self.__class__(super().swapcase())
+
+ def title(self, /):
+ return self.__class__(super().title())
+
+ def translate(self, table, /, delete=b''):
+ return self.__class__(super().translate(table, delete=delete))
+
+ def upper(self, /):
+ return self.__class__(super().upper())
+
+ def zfill(self, width, /):
+ return self.__class__(super().zfill(width))
+
+
+class AnsibleUnsafeText(str, AnsibleUnsafe):
+ def _strip_unsafe(self, /):
+ return super().__str__()
+
+ def __reduce__(self, /):
+ return (self.__class__, (self._strip_unsafe(),))
+
+ def __str__(self, /): # pylint: disable=invalid-str-returned
+ return self
+
+ def __repr__(self, /): # pylint: disable=invalid-repr-returned
+ return self.__class__(super().__repr__())
+
+ def __format__(self, format_spec, /): # pylint: disable=invalid-format-returned
+ return self.__class__(super().__format__(format_spec))
+
+ def __getitem__(self, key, /):
+ return self.__class__(super().__getitem__(key))
+
+ def __iter__(self, /):
+ cls = self.__class__
+ return (cls(c) for c in super().__iter__())
+
+ def __reversed__(self, /):
+ return self[::-1]
+
+ def __add__(self, value, /):
+ return self.__class__(super().__add__(value))
+
+ def __radd__(self, value, /):
+ return self.__class__(value.__add__(self))
+
+ def __mul__(self, value, /):
+ return self.__class__(super().__mul__(value))
+
+ __rmul__ = __mul__
+
+ def __mod__(self, value, /):
+ return self.__class__(super().__mod__(value))
+
+ def __rmod__(self, value, /):
+ return self.__class__(super().__rmod__(value))
+
+ def capitalize(self, /):
+ return self.__class__(super().capitalize())
+
+ def casefold(self, /):
+ return self.__class__(super().casefold())
+
+ def center(self, width, fillchar=' ', /):
+ return self.__class__(super().center(width, fillchar))
+
+ def encode(self, /, encoding='utf-8', errors='strict'):
+ return AnsibleUnsafeBytes(super().encode(encoding=encoding, errors=errors))
+
+ def removeprefix(self, prefix, /):
+ return self.__class__(super().removeprefix(prefix))
+
+ def removesuffix(self, suffix, /):
+ return self.__class__(super().removesuffix(suffix))
+
+ def expandtabs(self, /, tabsize=8):
+ return self.__class__(super().expandtabs(tabsize))
+
+ def format(self, /, *args, **kwargs):
+ return self.__class__(super().format(*args, **kwargs))
+
+ def format_map(self, mapping, /):
+ return self.__class__(super().format_map(mapping))
+
+ def join(self, iterable, /):
+ return self.__class__(super().join(iterable))
+
+ def ljust(self, width, fillchar=' ', /):
+ return self.__class__(super().ljust(width, fillchar))
+
+ def lower(self, /):
+ return self.__class__(super().lower())
+
+ def lstrip(self, chars=None, /):
+ return self.__class__(super().lstrip(chars))
+
+ def partition(self, sep, /):
+ cls = self.__class__
+ return tuple(cls(e) for e in super().partition(sep))
+
+ def replace(self, old, new, count=-1, /):
+ return self.__class__(super().replace(old, new, count))
+
+ def rjust(self, width, fillchar=' ', /):
+ return self.__class__(super().rjust(width, fillchar))
+
+ def rpartition(self, sep, /):
+ cls = self.__class__
+ return tuple(cls(e) for e in super().rpartition(sep))
+
+ def rstrip(self, chars=None, /):
+ return self.__class__(super().rstrip(chars))
+
+ def split(self, /, sep=None, maxsplit=-1):
+ cls = self.__class__
+ return [cls(e) for e in super().split(sep=sep, maxsplit=maxsplit)]
+
+ def rsplit(self, /, sep=None, maxsplit=-1):
+ cls = self.__class__
+ return [cls(e) for e in super().rsplit(sep=sep, maxsplit=maxsplit)]
+
+ def splitlines(self, /, keepends=False):
+ cls = self.__class__
+ return [cls(e) for e in super().splitlines(keepends=keepends)]
+
+ def strip(self, chars=None, /):
+ return self.__class__(super().strip(chars))
+
+ def swapcase(self, /):
+ return self.__class__(super().swapcase())
+
+ def title(self, /):
+ return self.__class__(super().title())
+
+ def translate(self, table, /):
+ return self.__class__(super().translate(table))
+
+ def upper(self, /):
+ return self.__class__(super().upper())
+
+ def zfill(self, width, /):
+ return self.__class__(super().zfill(width))
class NativeJinjaUnsafeText(NativeJinjaText, AnsibleUnsafeText):
@@ -112,9 +351,9 @@ def wrap_var(v):
v = _wrap_sequence(v)
elif isinstance(v, NativeJinjaText):
v = NativeJinjaUnsafeText(v)
- elif isinstance(v, binary_type):
+ elif isinstance(v, bytes):
v = AnsibleUnsafeBytes(v)
- elif isinstance(v, text_type):
+ elif isinstance(v, str):
v = AnsibleUnsafeText(v)
return v
@@ -126,3 +365,7 @@ def to_unsafe_bytes(*args, **kwargs):
def to_unsafe_text(*args, **kwargs):
return wrap_var(to_text(*args, **kwargs))
+
+
+def _is_unsafe(obj):
+ return getattr(obj, '__UNSAFE__', False) is True