diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 16:04:21 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 16:04:21 +0000 |
commit | 8a754e0858d922e955e71b253c139e071ecec432 (patch) | |
tree | 527d16e74bfd1840c85efd675fdecad056c54107 /lib/ansible/parsing/yaml | |
parent | Initial commit. (diff) | |
download | ansible-core-8a754e0858d922e955e71b253c139e071ecec432.tar.xz ansible-core-8a754e0858d922e955e71b253c139e071ecec432.zip |
Adding upstream version 2.14.3.upstream/2.14.3upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | lib/ansible/parsing/yaml/__init__.py | 20 | ||||
-rw-r--r-- | lib/ansible/parsing/yaml/constructor.py | 178 | ||||
-rw-r--r-- | lib/ansible/parsing/yaml/dumper.py | 122 | ||||
-rw-r--r-- | lib/ansible/parsing/yaml/loader.py | 45 | ||||
-rw-r--r-- | lib/ansible/parsing/yaml/objects.py | 365 |
5 files changed, 730 insertions, 0 deletions
diff --git a/lib/ansible/parsing/yaml/__init__.py b/lib/ansible/parsing/yaml/__init__.py new file mode 100644 index 0000000..ae8ccff --- /dev/null +++ b/lib/ansible/parsing/yaml/__init__.py @@ -0,0 +1,20 @@ +# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type diff --git a/lib/ansible/parsing/yaml/constructor.py b/lib/ansible/parsing/yaml/constructor.py new file mode 100644 index 0000000..4b79578 --- /dev/null +++ b/lib/ansible/parsing/yaml/constructor.py @@ -0,0 +1,178 @@ +# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from yaml.constructor import SafeConstructor, ConstructorError +from yaml.nodes import MappingNode + +from ansible import constants as C +from ansible.module_utils._text import to_bytes, to_native +from ansible.parsing.yaml.objects import AnsibleMapping, AnsibleSequence, AnsibleUnicode, AnsibleVaultEncryptedUnicode +from ansible.parsing.vault import VaultLib +from ansible.utils.display import Display +from ansible.utils.unsafe_proxy import wrap_var + +display = Display() + + +class AnsibleConstructor(SafeConstructor): + def __init__(self, file_name=None, vault_secrets=None): + self._ansible_file_name = file_name + super(AnsibleConstructor, self).__init__() + self._vaults = {} + self.vault_secrets = vault_secrets or [] + self._vaults['default'] = VaultLib(secrets=self.vault_secrets) + + def construct_yaml_map(self, node): + data = AnsibleMapping() + yield data + value = self.construct_mapping(node) + data.update(value) + data.ansible_pos = self._node_position_info(node) + + def construct_mapping(self, node, deep=False): + # Most of this is from yaml.constructor.SafeConstructor. We replicate + # it here so that we can warn users when they have duplicate dict keys + # (pyyaml silently allows overwriting keys) + if not isinstance(node, MappingNode): + raise ConstructorError(None, None, + "expected a mapping node, but found %s" % node.id, + node.start_mark) + self.flatten_mapping(node) + mapping = AnsibleMapping() + + # Add our extra information to the returned value + mapping.ansible_pos = self._node_position_info(node) + + for key_node, value_node in node.value: + key = self.construct_object(key_node, deep=deep) + try: + hash(key) + except TypeError as exc: + raise ConstructorError("while constructing a mapping", node.start_mark, + "found unacceptable key (%s)" % exc, key_node.start_mark) + + if key in mapping: + msg = (u'While constructing a mapping from {1}, line {2}, column {3}, found a duplicate dict key ({0}).' + u' Using last defined value only.'.format(key, *mapping.ansible_pos)) + if C.DUPLICATE_YAML_DICT_KEY == 'warn': + display.warning(msg) + elif C.DUPLICATE_YAML_DICT_KEY == 'error': + raise ConstructorError(context=None, context_mark=None, + problem=to_native(msg), + problem_mark=node.start_mark, + note=None) + else: + # when 'ignore' + display.debug(msg) + + value = self.construct_object(value_node, deep=deep) + mapping[key] = value + + return mapping + + def construct_yaml_str(self, node): + # Override the default string handling function + # to always return unicode objects + value = self.construct_scalar(node) + ret = AnsibleUnicode(value) + + ret.ansible_pos = self._node_position_info(node) + + return ret + + def construct_vault_encrypted_unicode(self, node): + value = self.construct_scalar(node) + b_ciphertext_data = to_bytes(value) + # could pass in a key id here to choose the vault to associate with + # TODO/FIXME: plugin vault selector + vault = self._vaults['default'] + if vault.secrets is None: + raise ConstructorError(context=None, context_mark=None, + problem="found !vault but no vault password provided", + problem_mark=node.start_mark, + note=None) + ret = AnsibleVaultEncryptedUnicode(b_ciphertext_data) + ret.vault = vault + ret.ansible_pos = self._node_position_info(node) + return ret + + def construct_yaml_seq(self, node): + data = AnsibleSequence() + yield data + data.extend(self.construct_sequence(node)) + data.ansible_pos = self._node_position_info(node) + + def construct_yaml_unsafe(self, node): + try: + constructor = getattr(node, 'id', 'object') + if constructor is not None: + constructor = getattr(self, 'construct_%s' % constructor) + except AttributeError: + constructor = self.construct_object + + value = constructor(node) + + return wrap_var(value) + + def _node_position_info(self, node): + # the line number where the previous token has ended (plus empty lines) + # Add one so that the first line is line 1 rather than line 0 + column = node.start_mark.column + 1 + line = node.start_mark.line + 1 + + # in some cases, we may have pre-read the data and then + # passed it to the load() call for YAML, in which case we + # want to override the default datasource (which would be + # '<string>') to the actual filename we read in + datasource = self._ansible_file_name or node.start_mark.name + + return (datasource, line, column) + + +AnsibleConstructor.add_constructor( + u'tag:yaml.org,2002:map', + AnsibleConstructor.construct_yaml_map) + +AnsibleConstructor.add_constructor( + u'tag:yaml.org,2002:python/dict', + AnsibleConstructor.construct_yaml_map) + +AnsibleConstructor.add_constructor( + u'tag:yaml.org,2002:str', + AnsibleConstructor.construct_yaml_str) + +AnsibleConstructor.add_constructor( + u'tag:yaml.org,2002:python/unicode', + AnsibleConstructor.construct_yaml_str) + +AnsibleConstructor.add_constructor( + u'tag:yaml.org,2002:seq', + AnsibleConstructor.construct_yaml_seq) + +AnsibleConstructor.add_constructor( + u'!unsafe', + AnsibleConstructor.construct_yaml_unsafe) + +AnsibleConstructor.add_constructor( + u'!vault', + AnsibleConstructor.construct_vault_encrypted_unicode) + +AnsibleConstructor.add_constructor(u'!vault-encrypted', AnsibleConstructor.construct_vault_encrypted_unicode) diff --git a/lib/ansible/parsing/yaml/dumper.py b/lib/ansible/parsing/yaml/dumper.py new file mode 100644 index 0000000..8701bb8 --- /dev/null +++ b/lib/ansible/parsing/yaml/dumper.py @@ -0,0 +1,122 @@ +# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +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.template import AnsibleUndefined +from ansible.vars.hostvars import HostVars, HostVarsVars +from ansible.vars.manager import VarsWithSources + + +class AnsibleDumper(SafeDumper): + ''' + A simple stub class that allows us to add representers + for our overridden object types. + ''' + + +def represent_hostvars(self, data): + return self.represent_dict(dict(data)) + + +# Note: only want to represent the encrypted data +def represent_vault_encrypted_unicode(self, data): + return self.represent_scalar(u'!vault', data._ciphertext.decode(), style='|') + + +def represent_unicode(self, data): + return yaml.representer.SafeRepresenter.represent_str(self, text_type(data)) + + +def represent_binary(self, data): + return yaml.representer.SafeRepresenter.represent_binary(self, binary_type(data)) + + +def represent_undefined(self, data): + # Here bool will ensure _fail_with_undefined_error happens + # if the value is Undefined. + # This happens because Jinja sets __bool__ on StrictUndefined + return bool(data) + + +AnsibleDumper.add_representer( + AnsibleUnicode, + represent_unicode, +) + +AnsibleDumper.add_representer( + AnsibleUnsafeText, + represent_unicode, +) + +AnsibleDumper.add_representer( + AnsibleUnsafeBytes, + represent_binary, +) + +AnsibleDumper.add_representer( + HostVars, + represent_hostvars, +) + +AnsibleDumper.add_representer( + HostVarsVars, + represent_hostvars, +) + +AnsibleDumper.add_representer( + VarsWithSources, + represent_hostvars, +) + +AnsibleDumper.add_representer( + AnsibleSequence, + yaml.representer.SafeRepresenter.represent_list, +) + +AnsibleDumper.add_representer( + AnsibleMapping, + yaml.representer.SafeRepresenter.represent_dict, +) + +AnsibleDumper.add_representer( + AnsibleVaultEncryptedUnicode, + represent_vault_encrypted_unicode, +) + +AnsibleDumper.add_representer( + AnsibleUndefined, + represent_undefined, +) + +AnsibleDumper.add_representer( + NativeJinjaUnsafeText, + represent_unicode, +) + +AnsibleDumper.add_representer( + NativeJinjaText, + represent_unicode, +) diff --git a/lib/ansible/parsing/yaml/loader.py b/lib/ansible/parsing/yaml/loader.py new file mode 100644 index 0000000..15bde79 --- /dev/null +++ b/lib/ansible/parsing/yaml/loader.py @@ -0,0 +1,45 @@ +# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from yaml.resolver import Resolver + +from ansible.parsing.yaml.constructor import AnsibleConstructor +from ansible.module_utils.common.yaml import HAS_LIBYAML, Parser + +if HAS_LIBYAML: + class AnsibleLoader(Parser, AnsibleConstructor, Resolver): # type: ignore[misc] # pylint: disable=inconsistent-mro + def __init__(self, stream, file_name=None, vault_secrets=None): + Parser.__init__(self, stream) + AnsibleConstructor.__init__(self, file_name=file_name, vault_secrets=vault_secrets) + Resolver.__init__(self) +else: + from yaml.composer import Composer + from yaml.reader import Reader + from yaml.scanner import Scanner + + class AnsibleLoader(Reader, Scanner, Parser, Composer, AnsibleConstructor, Resolver): # type: ignore[misc,no-redef] # pylint: disable=inconsistent-mro + def __init__(self, stream, file_name=None, vault_secrets=None): + Reader.__init__(self, stream) + Scanner.__init__(self) + Parser.__init__(self) + Composer.__init__(self) + AnsibleConstructor.__init__(self, file_name=file_name, vault_secrets=vault_secrets) + Resolver.__init__(self) diff --git a/lib/ansible/parsing/yaml/objects.py b/lib/ansible/parsing/yaml/objects.py new file mode 100644 index 0000000..a2e2a66 --- /dev/null +++ b/lib/ansible/parsing/yaml/objects.py @@ -0,0 +1,365 @@ +# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import string +import sys as _sys + +from collections.abc import Sequence + +import sys +import yaml + +from ansible.module_utils.six import text_type +from ansible.module_utils._text import to_bytes, to_text, to_native + + +class AnsibleBaseYAMLObject(object): + ''' + the base class used to sub-class python built-in objects + so that we can add attributes to them during yaml parsing + + ''' + _data_source = None + _line_number = 0 + _column_number = 0 + + def _get_ansible_position(self): + return (self._data_source, self._line_number, self._column_number) + + def _set_ansible_position(self, obj): + try: + (src, line, col) = obj + except (TypeError, ValueError): + raise AssertionError( + 'ansible_pos can only be set with a tuple/list ' + 'of three values: source, line number, column number' + ) + self._data_source = src + self._line_number = line + self._column_number = col + + ansible_pos = property(_get_ansible_position, _set_ansible_position) + + +class AnsibleMapping(AnsibleBaseYAMLObject, dict): + ''' sub class for dictionaries ''' + pass + + +class AnsibleUnicode(AnsibleBaseYAMLObject, text_type): + ''' sub class for unicode objects ''' + pass + + +class AnsibleSequence(AnsibleBaseYAMLObject, list): + ''' sub class for lists ''' + pass + + +class AnsibleVaultEncryptedUnicode(Sequence, AnsibleBaseYAMLObject): + '''Unicode like object that is not evaluated (decrypted) until it needs to be''' + __UNSAFE__ = True + __ENCRYPTED__ = True + yaml_tag = u'!vault' + + @classmethod + def from_plaintext(cls, seq, vault, secret): + if not vault: + raise vault.AnsibleVaultError('Error creating AnsibleVaultEncryptedUnicode, invalid vault (%s) provided' % vault) + + ciphertext = vault.encrypt(seq, secret) + avu = cls(ciphertext) + avu.vault = vault + return avu + + def __init__(self, ciphertext): + '''A AnsibleUnicode with a Vault attribute that can decrypt it. + + ciphertext is a byte string (str on PY2, bytestring on PY3). + + The .data attribute is a property that returns the decrypted plaintext + of the ciphertext as a PY2 unicode or PY3 string object. + ''' + super(AnsibleVaultEncryptedUnicode, self).__init__() + + # after construction, calling code has to set the .vault attribute to a vaultlib object + self.vault = None + self._ciphertext = to_bytes(ciphertext) + + @property + def data(self): + if not self.vault: + return to_text(self._ciphertext) + return to_text(self.vault.decrypt(self._ciphertext, obj=self)) + + @data.setter + def data(self, value): + self._ciphertext = to_bytes(value) + + def is_encrypted(self): + return self.vault and self.vault.is_encrypted(self._ciphertext) + + def __eq__(self, other): + if self.vault: + return other == self.data + return False + + def __ne__(self, other): + if self.vault: + return other != self.data + return True + + def __reversed__(self): + # This gets inerhited from ``collections.Sequence`` which returns a generator + # make this act more like the string implementation + return to_text(self[::-1], errors='surrogate_or_strict') + + def __str__(self): + return to_native(self.data, errors='surrogate_or_strict') + + def __unicode__(self): + return to_text(self.data, errors='surrogate_or_strict') + + def encode(self, encoding=None, errors=None): + return to_bytes(self.data, encoding=encoding, errors=errors) + + # Methods below are a copy from ``collections.UserString`` + # Some are copied as is, where others are modified to not + # auto wrap with ``self.__class__`` + def __repr__(self): + return repr(self.data) + + def __int__(self, base=10): + return int(self.data, base=base) + + def __float__(self): + return float(self.data) + + def __complex__(self): + return complex(self.data) + + def __hash__(self): + return hash(self.data) + + # This breaks vault, do not define it, we cannot satisfy this + # def __getnewargs__(self): + # return (self.data[:],) + + def __lt__(self, string): + if isinstance(string, AnsibleVaultEncryptedUnicode): + return self.data < string.data + return self.data < string + + def __le__(self, string): + if isinstance(string, AnsibleVaultEncryptedUnicode): + return self.data <= string.data + return self.data <= string + + def __gt__(self, string): + if isinstance(string, AnsibleVaultEncryptedUnicode): + return self.data > string.data + return self.data > string + + def __ge__(self, string): + if isinstance(string, AnsibleVaultEncryptedUnicode): + return self.data >= string.data + return self.data >= string + + def __contains__(self, char): + if isinstance(char, AnsibleVaultEncryptedUnicode): + char = char.data + return char in self.data + + def __len__(self): + return len(self.data) + + def __getitem__(self, index): + return self.data[index] + + def __getslice__(self, start, end): + start = max(start, 0) + end = max(end, 0) + return self.data[start:end] + + def __add__(self, other): + if isinstance(other, AnsibleVaultEncryptedUnicode): + return self.data + other.data + elif isinstance(other, text_type): + return self.data + other + return self.data + to_text(other) + + def __radd__(self, other): + if isinstance(other, text_type): + return other + self.data + return to_text(other) + self.data + + def __mul__(self, n): + return self.data * n + + __rmul__ = __mul__ + + def __mod__(self, args): + return self.data % args + + def __rmod__(self, template): + return to_text(template) % self + + # the following methods are defined in alphabetical order: + def capitalize(self): + return self.data.capitalize() + + def casefold(self): + return self.data.casefold() + + def center(self, width, *args): + return self.data.center(width, *args) + + def count(self, sub, start=0, end=_sys.maxsize): + if isinstance(sub, AnsibleVaultEncryptedUnicode): + sub = sub.data + return self.data.count(sub, start, end) + + def endswith(self, suffix, start=0, end=_sys.maxsize): + return self.data.endswith(suffix, start, end) + + def expandtabs(self, tabsize=8): + return self.data.expandtabs(tabsize) + + def find(self, sub, start=0, end=_sys.maxsize): + if isinstance(sub, AnsibleVaultEncryptedUnicode): + sub = sub.data + return self.data.find(sub, start, end) + + def format(self, *args, **kwds): + return self.data.format(*args, **kwds) + + def format_map(self, mapping): + return self.data.format_map(mapping) + + def index(self, sub, start=0, end=_sys.maxsize): + return self.data.index(sub, start, end) + + def isalpha(self): + return self.data.isalpha() + + def isalnum(self): + return self.data.isalnum() + + def isascii(self): + return self.data.isascii() + + def isdecimal(self): + return self.data.isdecimal() + + def isdigit(self): + return self.data.isdigit() + + def isidentifier(self): + return self.data.isidentifier() + + def islower(self): + return self.data.islower() + + def isnumeric(self): + return self.data.isnumeric() + + def isprintable(self): + return self.data.isprintable() + + def isspace(self): + return self.data.isspace() + + def istitle(self): + return self.data.istitle() + + def isupper(self): + return self.data.isupper() + + def join(self, seq): + return self.data.join(seq) + + def ljust(self, width, *args): + return self.data.ljust(width, *args) + + def lower(self): + return self.data.lower() + + def lstrip(self, chars=None): + return self.data.lstrip(chars) + + maketrans = str.maketrans + + def partition(self, sep): + return self.data.partition(sep) + + def replace(self, old, new, maxsplit=-1): + if isinstance(old, AnsibleVaultEncryptedUnicode): + old = old.data + if isinstance(new, AnsibleVaultEncryptedUnicode): + new = new.data + return self.data.replace(old, new, maxsplit) + + def rfind(self, sub, start=0, end=_sys.maxsize): + if isinstance(sub, AnsibleVaultEncryptedUnicode): + sub = sub.data + return self.data.rfind(sub, start, end) + + def rindex(self, sub, start=0, end=_sys.maxsize): + return self.data.rindex(sub, start, end) + + def rjust(self, width, *args): + return self.data.rjust(width, *args) + + def rpartition(self, sep): + return self.data.rpartition(sep) + + def rstrip(self, chars=None): + return self.data.rstrip(chars) + + def split(self, sep=None, maxsplit=-1): + return self.data.split(sep, maxsplit) + + def rsplit(self, sep=None, maxsplit=-1): + return self.data.rsplit(sep, maxsplit) + + def splitlines(self, keepends=False): + return self.data.splitlines(keepends) + + def startswith(self, prefix, start=0, end=_sys.maxsize): + return self.data.startswith(prefix, start, end) + + def strip(self, chars=None): + return self.data.strip(chars) + + def swapcase(self): + return self.data.swapcase() + + def title(self): + return self.data.title() + + def translate(self, *args): + return self.data.translate(*args) + + def upper(self): + return self.data.upper() + + def zfill(self, width): + return self.data.zfill(width) |