diff options
Diffstat (limited to 'lib/ansible/plugins/filter')
73 files changed, 3360 insertions, 0 deletions
diff --git a/lib/ansible/plugins/filter/__init__.py b/lib/ansible/plugins/filter/__init__.py new file mode 100644 index 0000000..5ae10da --- /dev/null +++ b/lib/ansible/plugins/filter/__init__.py @@ -0,0 +1,14 @@ +# (c) Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible import constants as C +from ansible.plugins import AnsibleJinja2Plugin + + +class AnsibleJinja2Filter(AnsibleJinja2Plugin): + + def _no_options(self, *args, **kwargs): + raise NotImplementedError("Jinaj2 filter plugins do not support option functions, they use direct arguments instead.") diff --git a/lib/ansible/plugins/filter/b64decode.yml b/lib/ansible/plugins/filter/b64decode.yml new file mode 100644 index 0000000..30565fa --- /dev/null +++ b/lib/ansible/plugins/filter/b64decode.yml @@ -0,0 +1,29 @@ +DOCUMENTATION: + name: b64decode + author: ansible core team + version_added: 'historical' + short_description: Decode a base64 string + description: + - Base64 decoding function. + - The return value is a string. + - Trying to store a binary blob in a string most likely corrupts the binary. To base64 decode a binary blob, + use the ``base64`` command and pipe the encoded data through standard input. + For example, in the ansible.builtin.shell`` module, ``cmd="base64 --decode > myfile.bin" stdin="{{ encoded }}"``. + positional: _input + options: + _input: + description: A base64 string to decode. + type: string + required: true + +EXAMPLES: | + # b64 decode a string + lola: "{{ 'bG9sYQ==' | b64decode }}" + + # b64 decode the content of 'b64stuff' variable + stuff: "{{ b64stuff | b64encode }}" + +RETURN: + _value: + description: The contents of the base64 encoded string. + type: string diff --git a/lib/ansible/plugins/filter/b64encode.yml b/lib/ansible/plugins/filter/b64encode.yml new file mode 100644 index 0000000..14676e5 --- /dev/null +++ b/lib/ansible/plugins/filter/b64encode.yml @@ -0,0 +1,25 @@ +DOCUMENTATION: + name: b64encode + author: ansible core team + version_added: 'historical' + short_description: Encode a string as base64 + description: + - Base64 encoding function. + positional: _input + options: + _input: + description: A string to encode. + type: string + required: true + +EXAMPLES: | + # b64 encode a string + b64lola: "{{ 'lola'|b64encode }}" + + # b64 encode the content of 'stuff' variable + b64stuff: "{{ stuff|b64encode }}" + +RETURN: + _value: + description: A base64 encoded string. + type: string diff --git a/lib/ansible/plugins/filter/basename.yml b/lib/ansible/plugins/filter/basename.yml new file mode 100644 index 0000000..4e868df --- /dev/null +++ b/lib/ansible/plugins/filter/basename.yml @@ -0,0 +1,24 @@ +DOCUMENTATION: + name: basename + author: ansible core team + version_added: "historical" + short_description: get a path's base name + description: + - Returns the last name component of a path, what is left in the string that is not 'dirname'. + options: + _input: + description: A path. + type: path + required: true + seealso: + - plugin_type: filter + plugin: ansible.builtin.dirname +EXAMPLES: | + + # To get the last name of a file path, like 'foo.txt' out of '/etc/asdf/foo.txt' + {{ mypath | basename }} + +RETURN: + _value: + description: The base name from the path provided. + type: str diff --git a/lib/ansible/plugins/filter/bool.yml b/lib/ansible/plugins/filter/bool.yml new file mode 100644 index 0000000..86ba353 --- /dev/null +++ b/lib/ansible/plugins/filter/bool.yml @@ -0,0 +1,28 @@ +DOCUMENTATION: + name: bool + version_added: "historical" + short_description: cast into a boolean + description: + - Attempt to cast the input into a boolean (C(True) or C(False)) value. + positional: _input + options: + _input: + description: Data to cast. + type: raw + required: true + +EXAMPLES: | + + # simply encrypt my key in a vault + vars: + isbool: "{{ (a == b)|bool }} " + otherbool: "{{ anothervar|bool }} " + + # in a task + ... + when: some_string_value | bool + +RETURN: + _value: + description: The boolean resulting of casting the input expression into a C(True) or C(False) value. + type: bool diff --git a/lib/ansible/plugins/filter/checksum.yml b/lib/ansible/plugins/filter/checksum.yml new file mode 100644 index 0000000..2f8eadd --- /dev/null +++ b/lib/ansible/plugins/filter/checksum.yml @@ -0,0 +1,21 @@ +DOCUMENTATION: + name: checksum + version_added: "1.9" + short_description: checksum of input data + description: + - Returns a checksum (L(SHA-1, https://en.wikipedia.org/wiki/SHA-1)) hash of the input data. + positional: _input + options: + _input: + description: Data to checksum. + type: raw + required: true + +EXAMPLES: | + # csum => "109f4b3c50d7b0df729d299bc6f8e9ef9066971f" + csum: "{{ 'test2' | checksum }}" + +RETURN: + _value: + description: The checksum (SHA-1) of the input. + type: string diff --git a/lib/ansible/plugins/filter/combinations.yml b/lib/ansible/plugins/filter/combinations.yml new file mode 100644 index 0000000..a46e51e --- /dev/null +++ b/lib/ansible/plugins/filter/combinations.yml @@ -0,0 +1,26 @@ +DOCUMENTATION: + name: combinations + version_added: "historical" + short_description: combinations from the elements of a list + description: + - Create a list of combinations of sets from the elements of a list. + positional: _input, set_size + options: + _input: + description: Elements to combine. + type: list + required: true + set_size: + description: The size of the set for each combination. + type: int + required: true +EXAMPLES: | + + # combos_of_two => [ [ 1, 2 ], [ 1, 3 ], [ 1, 4 ], [ 1, 5 ], [ 2, 3 ], [ 2, 4 ], [ 2, 5 ], [ 3, 4 ], [ 3, 5 ], [ 4, 5 ] ] + combos_of_two: "{{ [1,2,3,4,5] | combinations(2) }}" + + +RETURN: + _value: + description: List of combination sets resulting from the supplied elements and set size. + type: list diff --git a/lib/ansible/plugins/filter/combine.yml b/lib/ansible/plugins/filter/combine.yml new file mode 100644 index 0000000..86788f3 --- /dev/null +++ b/lib/ansible/plugins/filter/combine.yml @@ -0,0 +1,44 @@ +DOCUMENTATION: + name: combine + version_added: "2.0" + short_description: combine two dictionaries + description: + - Create a dictionary (hash/associative array) as a result of merging existing dictionaries. + positional: _input, _dicts + options: + _input: + description: First dictionary to combine. + type: dict + required: true + _dicts: # TODO: this is really an *args so not list, but list ref + description: The list of dictionaries to combine. + type: list + elements: dictionary + required: true + recursive: + description: If C(True), merge elements recursively. + type: bool + default: false + list_merge: + description: Behavior when encountering list elements. + type: str + default: replace + choices: + replace: overwrite older entries with newer ones + keep: discard newer entries + append: append newer entries to the older ones + prepend: insert newer entries in front of the older ones + append_rp: append newer entries to the older ones, overwrite duplicates + prepend_rp: insert newer entries in front of the older ones, discard duplicates + +EXAMPLES: | + + # ab => {'a':1, 'b':3, 'c': 4} + ab: {{ {'a':1, 'b':2} | combine({'b':3, 'c':4}) }} + + many: "{{ dict1 | combine(dict2, dict3, dict4) }}" + +RETURN: + _value: + description: Resulting merge of supplied dictionaries. + type: dict diff --git a/lib/ansible/plugins/filter/comment.yml b/lib/ansible/plugins/filter/comment.yml new file mode 100644 index 0000000..95a4efb --- /dev/null +++ b/lib/ansible/plugins/filter/comment.yml @@ -0,0 +1,60 @@ +DOCUMENTATION: + name: comment + version_added: 'historical' + short_description: comment out a string + description: + - Use programming language conventions to turn the input string into an embeddable comment. + positional: _input, style + options: + _input: + description: String to comment. + type: string + required: true + style: + description: Comment style to use. + type: string + default: plain + choices: ['plain', 'decoration', 'erlang', 'c', 'cblock', 'xml'] + decoration: + description: Indicator for comment or intermediate comment depending on the style. + type: string + begining: + description: Indicator of the start of a comment block, only available for styles that support multiline comments. + type: string + end: + description: Indicator the end of a comment block, only available for styles that support multiline comments. + type: string + newline: + description: Indicator of comment end of line, only available for styles that support multiline comments. + type: string + default: '\n' + prefix: + description: Token to start each line inside a comment block, only available for styles that support multiline comments. + type: string + prefix_count: + description: Number of times to add a prefix at the start of a line, when a prefix exists and is usable. + type: int + default: 1 + postfix: + description: Indicator of the end of each line inside a comment block, only available for styles that support multiline comments. + type: string + protfix_count: + description: Number of times to add a postfix at the end of a line, when a prefix exists and is usable. + type: int + default: 1 + +EXAMPLES: | + + # commented => # + # # Plain style (default) + # # + commented: "{{ 'Plain style (default)' | comment }}" + + # not going to show that here ... + verycustom: "{{ "Custom style" | comment('plain', prefix='#######\n#', postfix='#\n#######\n ###\n #') }}" + + +RETURN: + _value: + description: The 'commented out' string. + type: string diff --git a/lib/ansible/plugins/filter/core.py b/lib/ansible/plugins/filter/core.py new file mode 100644 index 0000000..52a2cd1 --- /dev/null +++ b/lib/ansible/plugins/filter/core.py @@ -0,0 +1,658 @@ +# (c) 2012, Jeroen Hoekx <jeroen@hoekx.be> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import base64 +import glob +import hashlib +import json +import ntpath +import os.path +import re +import shlex +import sys +import time +import uuid +import yaml +import datetime + +from collections.abc import Mapping +from functools import partial +from random import Random, SystemRandom, shuffle + +from jinja2.filters import pass_environment + +from ansible.errors import AnsibleError, AnsibleFilterError, AnsibleFilterTypeError +from ansible.module_utils.six import string_types, integer_types, reraise, text_type +from ansible.module_utils._text import to_bytes, to_native, to_text +from ansible.module_utils.common.collections import is_sequence +from ansible.module_utils.common.yaml import yaml_load, yaml_load_all +from ansible.parsing.ajson import AnsibleJSONEncoder +from ansible.parsing.yaml.dumper import AnsibleDumper +from ansible.template import recursive_check_defined +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.vars import merge_hash + +display = Display() + +UUID_NAMESPACE_ANSIBLE = uuid.UUID('361E6D51-FAEC-444A-9079-341386DA8E2E') + + +def to_yaml(a, *args, **kw): + '''Make verbose, human readable yaml''' + default_flow_style = kw.pop('default_flow_style', None) + try: + transformed = yaml.dump(a, Dumper=AnsibleDumper, allow_unicode=True, default_flow_style=default_flow_style, **kw) + except Exception as e: + raise AnsibleFilterError("to_yaml - %s" % to_native(e), orig_exc=e) + return to_text(transformed) + + +def to_nice_yaml(a, indent=4, *args, **kw): + '''Make verbose, human readable yaml''' + try: + transformed = yaml.dump(a, Dumper=AnsibleDumper, indent=indent, allow_unicode=True, default_flow_style=False, **kw) + except Exception as e: + raise AnsibleFilterError("to_nice_yaml - %s" % to_native(e), orig_exc=e) + return to_text(transformed) + + +def to_json(a, *args, **kw): + ''' Convert the value to JSON ''' + + # defaults for filters + if 'vault_to_text' not in kw: + kw['vault_to_text'] = True + if 'preprocess_unsafe' not in kw: + kw['preprocess_unsafe'] = False + + return json.dumps(a, cls=AnsibleJSONEncoder, *args, **kw) + + +def to_nice_json(a, indent=4, sort_keys=True, *args, **kw): + '''Make verbose, human readable JSON''' + return to_json(a, indent=indent, sort_keys=sort_keys, separators=(',', ': '), *args, **kw) + + +def to_bool(a): + ''' return a bool for the arg ''' + if a is None or isinstance(a, bool): + return a + if isinstance(a, string_types): + a = a.lower() + if a in ('yes', 'on', '1', 'true', 1): + return True + return False + + +def to_datetime(string, format="%Y-%m-%d %H:%M:%S"): + return datetime.datetime.strptime(string, format) + + +def strftime(string_format, second=None, utc=False): + ''' return a date string using string. See https://docs.python.org/3/library/time.html#time.strftime for format ''' + if utc: + timefn = time.gmtime + else: + timefn = time.localtime + if second is not None: + try: + second = float(second) + except Exception: + raise AnsibleFilterError('Invalid value for epoch value (%s)' % second) + return time.strftime(string_format, timefn(second)) + + +def quote(a): + ''' return its argument quoted for shell usage ''' + if a is None: + a = u'' + return shlex.quote(to_text(a)) + + +def fileglob(pathname): + ''' return list of matched regular files for glob ''' + return [g for g in glob.glob(pathname) if os.path.isfile(g)] + + +def regex_replace(value='', pattern='', replacement='', ignorecase=False, multiline=False): + ''' Perform a `re.sub` returning a string ''' + + value = to_text(value, errors='surrogate_or_strict', nonstring='simplerepr') + + flags = 0 + if ignorecase: + flags |= re.I + if multiline: + flags |= re.M + _re = re.compile(pattern, flags=flags) + return _re.sub(replacement, value) + + +def regex_findall(value, regex, multiline=False, ignorecase=False): + ''' Perform re.findall and return the list of matches ''' + + value = to_text(value, errors='surrogate_or_strict', nonstring='simplerepr') + + flags = 0 + if ignorecase: + flags |= re.I + if multiline: + flags |= re.M + return re.findall(regex, value, flags) + + +def regex_search(value, regex, *args, **kwargs): + ''' Perform re.search and return the list of matches or a backref ''' + + value = to_text(value, errors='surrogate_or_strict', nonstring='simplerepr') + + groups = list() + for arg in args: + if arg.startswith('\\g'): + match = re.match(r'\\g<(\S+)>', arg).group(1) + groups.append(match) + elif arg.startswith('\\'): + match = int(re.match(r'\\(\d+)', arg).group(1)) + groups.append(match) + else: + raise AnsibleFilterError('Unknown argument') + + flags = 0 + if kwargs.get('ignorecase'): + flags |= re.I + if kwargs.get('multiline'): + flags |= re.M + + match = re.search(regex, value, flags) + if match: + if not groups: + return match.group() + else: + items = list() + for item in groups: + items.append(match.group(item)) + return items + + +def ternary(value, true_val, false_val, none_val=None): + ''' value ? true_val : false_val ''' + if value is None and none_val is not None: + return none_val + elif bool(value): + return true_val + else: + return false_val + + +def regex_escape(string, re_type='python'): + string = to_text(string, errors='surrogate_or_strict', nonstring='simplerepr') + '''Escape all regular expressions special characters from STRING.''' + if re_type == 'python': + return re.escape(string) + elif re_type == 'posix_basic': + # list of BRE special chars: + # https://en.wikibooks.org/wiki/Regular_Expressions/POSIX_Basic_Regular_Expressions + return regex_replace(string, r'([].[^$*\\])', r'\\\1') + # TODO: implement posix_extended + # It's similar to, but different from python regex, which is similar to, + # but different from PCRE. It's possible that re.escape would work here. + # https://remram44.github.io/regex-cheatsheet/regex.html#programs + elif re_type == 'posix_extended': + raise AnsibleFilterError('Regex type (%s) not yet implemented' % re_type) + else: + raise AnsibleFilterError('Invalid regex type (%s)' % re_type) + + +def from_yaml(data): + if isinstance(data, string_types): + # The ``text_type`` call here strips any custom + # string wrapper class, so that CSafeLoader can + # read the data + return yaml_load(text_type(to_text(data, errors='surrogate_or_strict'))) + return data + + +def from_yaml_all(data): + if isinstance(data, string_types): + # The ``text_type`` call here strips any custom + # string wrapper class, so that CSafeLoader can + # read the data + return yaml_load_all(text_type(to_text(data, errors='surrogate_or_strict'))) + return data + + +@pass_environment +def rand(environment, end, start=None, step=None, seed=None): + if seed is None: + r = SystemRandom() + else: + r = Random(seed) + if isinstance(end, integer_types): + if not start: + start = 0 + if not step: + step = 1 + return r.randrange(start, end, step) + elif hasattr(end, '__iter__'): + if start or step: + raise AnsibleFilterError('start and step can only be used with integer values') + return r.choice(end) + else: + raise AnsibleFilterError('random can only be used on sequences and integers') + + +def randomize_list(mylist, seed=None): + try: + mylist = list(mylist) + if seed: + r = Random(seed) + r.shuffle(mylist) + else: + shuffle(mylist) + except Exception: + pass + return mylist + + +def get_hash(data, hashtype='sha1'): + try: + h = hashlib.new(hashtype) + except Exception as e: + # hash is not supported? + raise AnsibleFilterError(e) + + h.update(to_bytes(data, errors='surrogate_or_strict')) + return h.hexdigest() + + +def get_encrypted_password(password, hashtype='sha512', salt=None, salt_size=None, rounds=None, ident=None): + passlib_mapping = { + 'md5': 'md5_crypt', + 'blowfish': 'bcrypt', + 'sha256': 'sha256_crypt', + 'sha512': 'sha512_crypt', + } + + hashtype = passlib_mapping.get(hashtype, hashtype) + try: + return passlib_or_crypt(password, hashtype, salt=salt, salt_size=salt_size, rounds=rounds, ident=ident) + except AnsibleError as e: + reraise(AnsibleFilterError, AnsibleFilterError(to_native(e), orig_exc=e), sys.exc_info()[2]) + + +def to_uuid(string, namespace=UUID_NAMESPACE_ANSIBLE): + uuid_namespace = namespace + if not isinstance(uuid_namespace, uuid.UUID): + try: + uuid_namespace = uuid.UUID(namespace) + except (AttributeError, ValueError) as e: + raise AnsibleFilterError("Invalid value '%s' for 'namespace': %s" % (to_native(namespace), to_native(e))) + # uuid.uuid5() requires bytes on Python 2 and bytes or text or Python 3 + return to_text(uuid.uuid5(uuid_namespace, to_native(string, errors='surrogate_or_strict'))) + + +def mandatory(a, msg=None): + from jinja2.runtime import Undefined + + ''' Make a variable mandatory ''' + if isinstance(a, Undefined): + if a._undefined_name is not None: + name = "'%s' " % to_text(a._undefined_name) + else: + name = '' + + if msg is not None: + raise AnsibleFilterError(to_native(msg)) + else: + raise AnsibleFilterError("Mandatory variable %s not defined." % name) + + return a + + +def combine(*terms, **kwargs): + recursive = kwargs.pop('recursive', False) + list_merge = kwargs.pop('list_merge', 'replace') + if kwargs: + raise AnsibleFilterError("'recursive' and 'list_merge' are the only valid keyword arguments") + + # allow the user to do `[dict1, dict2, ...] | combine` + dictionaries = flatten(terms, levels=1) + + # recursively check that every elements are defined (for jinja2) + recursive_check_defined(dictionaries) + + if not dictionaries: + return {} + + if len(dictionaries) == 1: + return dictionaries[0] + + # merge all the dicts so that the dict at the end of the array have precedence + # over the dict at the beginning. + # we merge the dicts from the highest to the lowest priority because there is + # a huge probability that the lowest priority dict will be the biggest in size + # (as the low prio dict will hold the "default" values and the others will be "patches") + # and merge_hash create a copy of it's first argument. + # so high/right -> low/left is more efficient than low/left -> high/right + high_to_low_prio_dict_iterator = reversed(dictionaries) + result = next(high_to_low_prio_dict_iterator) + for dictionary in high_to_low_prio_dict_iterator: + result = merge_hash(dictionary, result, recursive, list_merge) + + return result + + +def comment(text, style='plain', **kw): + # Predefined comment types + comment_styles = { + 'plain': { + 'decoration': '# ' + }, + 'erlang': { + 'decoration': '% ' + }, + 'c': { + 'decoration': '// ' + }, + 'cblock': { + 'beginning': '/*', + 'decoration': ' * ', + 'end': ' */' + }, + 'xml': { + 'beginning': '<!--', + 'decoration': ' - ', + 'end': '-->' + } + } + + # Pointer to the right comment type + style_params = comment_styles[style] + + if 'decoration' in kw: + prepostfix = kw['decoration'] + else: + prepostfix = style_params['decoration'] + + # Default params + p = { + 'newline': '\n', + 'beginning': '', + 'prefix': (prepostfix).rstrip(), + 'prefix_count': 1, + 'decoration': '', + 'postfix': (prepostfix).rstrip(), + 'postfix_count': 1, + 'end': '' + } + + # Update default params + p.update(style_params) + p.update(kw) + + # Compose substrings for the final string + str_beginning = '' + if p['beginning']: + str_beginning = "%s%s" % (p['beginning'], p['newline']) + str_prefix = '' + if p['prefix']: + if p['prefix'] != p['newline']: + str_prefix = str( + "%s%s" % (p['prefix'], p['newline'])) * int(p['prefix_count']) + else: + str_prefix = str( + "%s" % (p['newline'])) * int(p['prefix_count']) + str_text = ("%s%s" % ( + p['decoration'], + # Prepend each line of the text with the decorator + text.replace( + p['newline'], "%s%s" % (p['newline'], p['decoration'])))).replace( + # Remove trailing spaces when only decorator is on the line + "%s%s" % (p['decoration'], p['newline']), + "%s%s" % (p['decoration'].rstrip(), p['newline'])) + str_postfix = p['newline'].join( + [''] + [p['postfix'] for x in range(p['postfix_count'])]) + str_end = '' + if p['end']: + str_end = "%s%s" % (p['newline'], p['end']) + + # Return the final string + return "%s%s%s%s%s" % ( + str_beginning, + str_prefix, + str_text, + str_postfix, + str_end) + + +@pass_environment +def extract(environment, item, container, morekeys=None): + if morekeys is None: + keys = [item] + elif isinstance(morekeys, list): + keys = [item] + morekeys + else: + keys = [item, morekeys] + + value = container + for key in keys: + value = environment.getitem(value, key) + + return value + + +def b64encode(string, encoding='utf-8'): + return to_text(base64.b64encode(to_bytes(string, encoding=encoding, errors='surrogate_or_strict'))) + + +def b64decode(string, encoding='utf-8'): + return to_text(base64.b64decode(to_bytes(string, errors='surrogate_or_strict')), encoding=encoding) + + +def flatten(mylist, levels=None, skip_nulls=True): + + ret = [] + for element in mylist: + if skip_nulls and element in (None, 'None', 'null'): + # ignore null items + continue + elif is_sequence(element): + if levels is None: + ret.extend(flatten(element, skip_nulls=skip_nulls)) + elif levels >= 1: + # decrement as we go down the stack + ret.extend(flatten(element, levels=(int(levels) - 1), skip_nulls=skip_nulls)) + else: + ret.append(element) + else: + ret.append(element) + + return ret + + +def subelements(obj, subelements, skip_missing=False): + '''Accepts a dict or list of dicts, and a dotted accessor and produces a product + of the element and the results of the dotted accessor + + >>> obj = [{"name": "alice", "groups": ["wheel"], "authorized": ["/tmp/alice/onekey.pub"]}] + >>> subelements(obj, 'groups') + [({'name': 'alice', 'groups': ['wheel'], 'authorized': ['/tmp/alice/onekey.pub']}, 'wheel')] + + ''' + if isinstance(obj, dict): + element_list = list(obj.values()) + elif isinstance(obj, list): + element_list = obj[:] + else: + raise AnsibleFilterError('obj must be a list of dicts or a nested dict') + + if isinstance(subelements, list): + subelement_list = subelements[:] + elif isinstance(subelements, string_types): + subelement_list = subelements.split('.') + else: + raise AnsibleFilterTypeError('subelements must be a list or a string') + + results = [] + + for element in element_list: + values = element + for subelement in subelement_list: + try: + values = values[subelement] + except KeyError: + if skip_missing: + values = [] + break + raise AnsibleFilterError("could not find %r key in iterated item %r" % (subelement, values)) + except TypeError: + raise AnsibleFilterTypeError("the key %s should point to a dictionary, got '%s'" % (subelement, values)) + if not isinstance(values, list): + raise AnsibleFilterTypeError("the key %r should point to a list, got %r" % (subelement, values)) + + for value in values: + results.append((element, value)) + + return results + + +def dict_to_list_of_dict_key_value_elements(mydict, key_name='key', value_name='value'): + ''' takes a dictionary and transforms it into a list of dictionaries, + with each having a 'key' and 'value' keys that correspond to the keys and values of the original ''' + + if not isinstance(mydict, Mapping): + raise AnsibleFilterTypeError("dict2items requires a dictionary, got %s instead." % type(mydict)) + + ret = [] + for key in mydict: + ret.append({key_name: key, value_name: mydict[key]}) + return ret + + +def list_of_dict_key_value_elements_to_dict(mylist, key_name='key', value_name='value'): + ''' takes a list of dicts with each having a 'key' and 'value' keys, and transforms the list into a dictionary, + effectively as the reverse of dict2items ''' + + if not is_sequence(mylist): + raise AnsibleFilterTypeError("items2dict requires a list, got %s instead." % type(mylist)) + + try: + return dict((item[key_name], item[value_name]) for item in mylist) + except KeyError: + raise AnsibleFilterTypeError( + "items2dict requires each dictionary in the list to contain the keys '%s' and '%s', got %s instead." + % (key_name, value_name, mylist) + ) + except TypeError: + raise AnsibleFilterTypeError("items2dict requires a list of dictionaries, got %s instead." % mylist) + + +def path_join(paths): + ''' takes a sequence or a string, and return a concatenation + of the different members ''' + if isinstance(paths, string_types): + return os.path.join(paths) + elif is_sequence(paths): + return os.path.join(*paths) + else: + raise AnsibleFilterTypeError("|path_join expects string or sequence, got %s instead." % type(paths)) + + +class FilterModule(object): + ''' Ansible core jinja2 filters ''' + + def filters(self): + return { + # base 64 + 'b64decode': b64decode, + 'b64encode': b64encode, + + # uuid + 'to_uuid': to_uuid, + + # json + 'to_json': to_json, + 'to_nice_json': to_nice_json, + 'from_json': json.loads, + + # yaml + 'to_yaml': to_yaml, + 'to_nice_yaml': to_nice_yaml, + 'from_yaml': from_yaml, + 'from_yaml_all': from_yaml_all, + + # path + 'basename': partial(unicode_wrap, os.path.basename), + 'dirname': partial(unicode_wrap, os.path.dirname), + 'expanduser': partial(unicode_wrap, os.path.expanduser), + 'expandvars': partial(unicode_wrap, os.path.expandvars), + 'path_join': path_join, + 'realpath': partial(unicode_wrap, os.path.realpath), + 'relpath': partial(unicode_wrap, os.path.relpath), + 'splitext': partial(unicode_wrap, os.path.splitext), + 'win_basename': partial(unicode_wrap, ntpath.basename), + 'win_dirname': partial(unicode_wrap, ntpath.dirname), + 'win_splitdrive': partial(unicode_wrap, ntpath.splitdrive), + + # file glob + 'fileglob': fileglob, + + # types + 'bool': to_bool, + 'to_datetime': to_datetime, + + # date formatting + 'strftime': strftime, + + # quote string for shell usage + 'quote': quote, + + # hash filters + # md5 hex digest of string + 'md5': md5s, + # sha1 hex digest of string + 'sha1': checksum_s, + # checksum of string as used by ansible for checksumming files + 'checksum': checksum_s, + # generic hashing + 'password_hash': get_encrypted_password, + 'hash': get_hash, + + # regex + 'regex_replace': regex_replace, + 'regex_escape': regex_escape, + 'regex_search': regex_search, + 'regex_findall': regex_findall, + + # ? : ; + 'ternary': ternary, + + # random stuff + 'random': rand, + 'shuffle': randomize_list, + + # undefined + 'mandatory': mandatory, + + # comment-style decoration + 'comment': comment, + + # debug + 'type_debug': lambda o: o.__class__.__name__, + + # Data structures + 'combine': combine, + 'extract': extract, + 'flatten': flatten, + 'dict2items': dict_to_list_of_dict_key_value_elements, + 'items2dict': list_of_dict_key_value_elements_to_dict, + 'subelements': subelements, + 'split': partial(unicode_wrap, text_type.split), + } diff --git a/lib/ansible/plugins/filter/dict2items.yml b/lib/ansible/plugins/filter/dict2items.yml new file mode 100644 index 0000000..aa51826 --- /dev/null +++ b/lib/ansible/plugins/filter/dict2items.yml @@ -0,0 +1,45 @@ +DOCUMENTATION: + name: dict2items + author: Ansible core team + version_added: "2.6" + short_description: Convert a dictionary into an itemized list of dictionaries + positional: _input, key_name, value_name + description: + - Takes a dictionary and transforms it into a list of dictionaries, with each having a + C(key) and C(value) keys that correspond to the keys and values of the original. + options: + _input: + description: + - The dictionary to transform + type: dict + required: true + key_name: + description: The name of the property on the item representing the dictionary's keys. + type: str + default: key + version_added: "2.8" + value_name: + description: The name of the property on the item representing the dictionary's values. + type: str + default: value + version_added: "2.8" + seealso: + - plugin_type: filter + plugin: ansible.builtin.items2dict + +EXAMPLES: | + + # items => [ { "key": "a", "value": 1 }, { "key": "b", "value": 2 } ] + items: "{{ {'a': 1, 'b': 2}| dict2items}}" + + vars: + files: + users: /etc/passwd + groups: /etc/group + files_dicts: "{{ files | dict2items(key_name='file', value_name='path') }}" + +RETURN: + _value: + description: A list of dictionaries. + type: list + elements: dict diff --git a/lib/ansible/plugins/filter/difference.yml b/lib/ansible/plugins/filter/difference.yml new file mode 100644 index 0000000..decc811 --- /dev/null +++ b/lib/ansible/plugins/filter/difference.yml @@ -0,0 +1,35 @@ +DOCUMENTATION: + name: difference + author: Brian Coca (@bcoca) + version_added: "1.4" + short_description: the difference of one list from another + description: + - Provide a unique list of all the elements of the first list that do not appear in the second one. + options: + _input: + description: A list. + type: list + required: true + _second_list: + description: A list. + type: list + required: true + seealso: + - plugin_type: filter + plugin: ansible.builtin.intersect + - plugin_type: filter + plugin: ansible.builtin.symmetric_difference + - plugin_type: filter + plugin: ansible.builtin.union + - plugin_type: filter + plugin: ansible.builtin.unique +EXAMPLES: | + # return the elements of list1 not in list2 + # list1: [1, 2, 5, 1, 3, 4, 10] + # list2: [1, 2, 3, 4, 5, 11, 99] + {{ list1 | difference(list2) }} + # => [10] +RETURN: + _value: + description: A unique list of the elements from the first list that do not appear on the second. + type: list diff --git a/lib/ansible/plugins/filter/dirname.yml b/lib/ansible/plugins/filter/dirname.yml new file mode 100644 index 0000000..52f7d5d --- /dev/null +++ b/lib/ansible/plugins/filter/dirname.yml @@ -0,0 +1,24 @@ +DOCUMENTATION: + name: dirname + author: ansible core team + version_added: "historical" + short_description: get a path's directory name + description: + - Returns the 'head' component of a path, basically everything that is not the 'basename'. + options: + _input: + description: A path. + type: path + required: true + seealso: + - plugin: ansible.builtin.basename + plugin_type: filter +EXAMPLES: | + + # To get the dir name of a file path, like '/etc/asdf' out of '/etc/asdf/foo.txt' + {{ mypath | dirname }} + +RETURN: + _value: + description: The directory portion of the original path. + type: path diff --git a/lib/ansible/plugins/filter/encryption.py b/lib/ansible/plugins/filter/encryption.py new file mode 100644 index 0000000..b6f4961 --- /dev/null +++ b/lib/ansible/plugins/filter/encryption.py @@ -0,0 +1,82 @@ +# Copyright: (c) 2021, Ansible Project + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from jinja2.runtime import Undefined +from jinja2.exceptions import UndefinedError + +from ansible.errors import AnsibleFilterError, AnsibleFilterTypeError +from ansible.module_utils._text import to_native, to_bytes +from ansible.module_utils.six import string_types, binary_type +from ansible.parsing.yaml.objects import AnsibleVaultEncryptedUnicode +from ansible.parsing.vault import is_encrypted, VaultSecret, VaultLib +from ansible.utils.display import Display + +display = Display() + + +def do_vault(data, secret, salt=None, vaultid='filter_default', wrap_object=False): + + if not isinstance(secret, (string_types, binary_type, Undefined)): + raise AnsibleFilterTypeError("Secret passed is required to be a string, instead we got: %s" % type(secret)) + + if not isinstance(data, (string_types, binary_type, Undefined)): + raise AnsibleFilterTypeError("Can only vault strings, instead we got: %s" % type(data)) + + vault = '' + vs = VaultSecret(to_bytes(secret)) + vl = VaultLib() + try: + vault = vl.encrypt(to_bytes(data), vs, vaultid, salt) + except UndefinedError: + raise + except Exception as e: + raise AnsibleFilterError("Unable to encrypt: %s" % to_native(e), orig_exc=e) + + if wrap_object: + vault = AnsibleVaultEncryptedUnicode(vault) + else: + vault = to_native(vault) + + return vault + + +def do_unvault(vault, secret, vaultid='filter_default'): + + if not isinstance(secret, (string_types, binary_type, Undefined)): + raise AnsibleFilterTypeError("Secret passed is required to be as string, instead we got: %s" % type(secret)) + + if not isinstance(vault, (string_types, binary_type, AnsibleVaultEncryptedUnicode, Undefined)): + raise AnsibleFilterTypeError("Vault should be in the form of a string, instead we got: %s" % type(vault)) + + data = '' + vs = VaultSecret(to_bytes(secret)) + vl = VaultLib([(vaultid, vs)]) + if isinstance(vault, AnsibleVaultEncryptedUnicode): + vault.vault = vl + data = vault.data + elif is_encrypted(vault): + try: + data = vl.decrypt(vault) + except UndefinedError: + raise + except Exception as e: + raise AnsibleFilterError("Unable to decrypt: %s" % to_native(e), orig_exc=e) + else: + data = vault + + return to_native(data) + + +class FilterModule(object): + ''' Ansible vault jinja2 filters ''' + + def filters(self): + filters = { + 'vault': do_vault, + 'unvault': do_unvault, + } + + return filters diff --git a/lib/ansible/plugins/filter/expanduser.yml b/lib/ansible/plugins/filter/expanduser.yml new file mode 100644 index 0000000..2aff468 --- /dev/null +++ b/lib/ansible/plugins/filter/expanduser.yml @@ -0,0 +1,21 @@ +DOCUMENTATION: + name: basename + author: ansible core team + version_added: "1.5" + short_description: Returns a path with C(~) translation. + description: + - Translates C(~) in a path to the proper user's home directory. + options: + _input: + description: A string that contains a path. + type: path + required: true +EXAMPLES: | + + # To get '/home/myuser/stuff.txt' from '~/stuff.txt'. + {{ mypath | expanduser }} + +RETURN: + _value: + description: The translated path. + type: path diff --git a/lib/ansible/plugins/filter/expandvars.yml b/lib/ansible/plugins/filter/expandvars.yml new file mode 100644 index 0000000..02c201e --- /dev/null +++ b/lib/ansible/plugins/filter/expandvars.yml @@ -0,0 +1,21 @@ +DOCUMENTATION: + name: expandvars + author: ansible core team + version_added: "1.5" + short_description: expand environment variables + description: + - Will do a shell-like substitution of environment variables on the provided input. + options: + _input: + description: A string that contains environment variables. + type: str + required: true +EXAMPLES: | + + # To get '/home/myuser/stuff.txt' from '$HOME/stuff.txt' + {{ mypath | expandvars }} + +RETURN: + _value: + description: The string with translated environment variable values. + type: str diff --git a/lib/ansible/plugins/filter/extract.yml b/lib/ansible/plugins/filter/extract.yml new file mode 100644 index 0000000..2b4989d --- /dev/null +++ b/lib/ansible/plugins/filter/extract.yml @@ -0,0 +1,39 @@ +DOCUMENTATION: + name: extract + version_added: "2.1" + short_description: extract a value based on an index or key + description: + - Extract a value from a list or dictionary based on an index/key. + - User must ensure that index or key used matches the type of container. + - Equivalent of using C(list[index]) and C(dictionary[key]) but useful as a filter to combine with C(map). + positional: _input, container, morekeys + options: + _input: + description: Index or key to extract. + type: raw + required: true + contianer: + description: Dictionary or list from which to extract a value. + type: raw + required: true + morekeys: + description: Indicies or keys to extract from the initial result (subkeys/subindices). + type: list + elements: dictionary + required: true + +EXAMPLES: | + + # extracted => 'b', same as ['a', 'b', 'c'][1] + extracted: "{{ 1 | extract(['a', 'b', 'c']) }}" + + # extracted_key => '2', same as {'a': 1, 'b': 2, 'c': 3}['b'] + extracted_key: "{{ 'b' | extract({'a': 1, 'b': 2, 'c': 3}) }}" + + # extracted_key_r => '2', same as [{'a': 1, 'b': 2, 'c': 3}, {'x': 9, 'y': 10}][0]['b'] + extracted_key_r: "{{ 0 | extract([{'a': 1, 'b': 2, 'c': 3}, {'x': 9, 'y': 10}], morekeys='b') }}" + +RETURN: + _value: + description: Resulting merge of supplied dictionaries. + type: dict diff --git a/lib/ansible/plugins/filter/fileglob.yml b/lib/ansible/plugins/filter/fileglob.yml new file mode 100644 index 0000000..69e8a9b --- /dev/null +++ b/lib/ansible/plugins/filter/fileglob.yml @@ -0,0 +1,22 @@ +DOCUMENTATION: + name: fileglob + short_description: explode a path glob to matching files + description: + - Return a list of files that matches the supplied path glob pattern. + - Filters run on the controller, so the files are matched from the controller's file system. + positional: _input + options: + _input: + description: Path glob pattern. + type: string + required: true + +EXAMPLES: | + # found = ['/etc/hosts', '/etc/hasts'] + found: "{{ '/etc/h?sts' | fileglob }}" + +RETURN: + _value: + description: List of files matched. + type: list + elements: string diff --git a/lib/ansible/plugins/filter/flatten.yml b/lib/ansible/plugins/filter/flatten.yml new file mode 100644 index 0000000..b909c3d --- /dev/null +++ b/lib/ansible/plugins/filter/flatten.yml @@ -0,0 +1,32 @@ +DOCUMENTATION: + name: flatten + version_added: "2.5" + short_description: flatten lists within a list + description: + - For a given list, take any elements that are lists and insert their elements into the parent list directly. + positional: _input, levels, skip_nulls + options: + _input: + description: First dictionary to combine. + type: dict + required: true + levels: + description: Number of recursive list depths to flatten. + type: int + skip_nulls: + description: Skip C(null)/C(None) elements when inserting into the top list. + type: bool + default: true + +EXAMPLES: | + + # [1,2,3,4,5,6] + flat: "{{ [1 , 2, [3, [4, 5]], 6] | flatten }}" + + # [1,2,3,[4,5],6] + flatone: "{{ [1, 2, [3, [4, 5]], 6] | flatten(1) }}" + +RETURN: + _value: + description: The flattened list. + type: list diff --git a/lib/ansible/plugins/filter/from_json.yml b/lib/ansible/plugins/filter/from_json.yml new file mode 100644 index 0000000..4edc2bd --- /dev/null +++ b/lib/ansible/plugins/filter/from_json.yml @@ -0,0 +1,25 @@ +DOCUMENTATION: + name: from_json + version_added: 'historical' + short_description: Convert JSON string into variable structure + description: + - Converts a JSON string representation into an equivalent structured Ansible variable. + - Ansible automatically converts JSON strings into variable structures in most contexts, use this plugin in contexts where automatic conversion does not happen. + notes: + - This filter functions as a wrapper to the Python C(json.loads) function. + options: + _input: + description: A JSON string. + type: string + required: true +EXAMPLES: | + # variable from string variable containing a JSON document + {{ docker_config | from_json }} + + # variable from string JSON document + {{ '{"a": true, "b": 54, "c": [1,2,3]}' | from_json }} + +RETURN: + _value: + description: The variable resulting from deserialization of the JSON document. + type: raw diff --git a/lib/ansible/plugins/filter/from_yaml.yml b/lib/ansible/plugins/filter/from_yaml.yml new file mode 100644 index 0000000..e9b1599 --- /dev/null +++ b/lib/ansible/plugins/filter/from_yaml.yml @@ -0,0 +1,25 @@ +DOCUMENTATION: + name: from_yaml + version_added: 'historical' + short_description: Convert YAML string into variable structure + description: + - Converts a YAML string representation into an equivalent structured Ansible variable. + - Ansible automatically converts YAML strings into variable structures in most contexts, use this plugin in contexts where automatic conversion does not happen. + notes: + - This filter functions as a wrapper to the L(Python pyyaml library, https://pypi.org/project/PyYAML/)'s C(yaml.safe_load) function. + options: + _input: + description: A YAML string. + type: string + required: true +EXAMPLES: | + # variable from string variable containing a YAML document + {{ github_workflow | from_yaml}} + + # variable from string JSON document + {{ '{"a": true, "b": 54, "c": [1,2,3]}' | from_yaml }} + +RETURN: + _value: + description: The variable resulting from deserializing the YAML document. + type: raw diff --git a/lib/ansible/plugins/filter/from_yaml_all.yml b/lib/ansible/plugins/filter/from_yaml_all.yml new file mode 100644 index 0000000..b179f1c --- /dev/null +++ b/lib/ansible/plugins/filter/from_yaml_all.yml @@ -0,0 +1,28 @@ +DOCUMENTATION: + name: from_yaml_all + version_added: 'historical' + short_description: Convert a series of YAML documents into a variable structure + description: + - Converts a YAML documents in a string representation into an equivalent structured Ansible variable. + - Ansible internally auto-converts YAML strings into variable structures in most contexts, but by default does not handle 'multi document' YAML files or strings. + - If multiple YAML documents are not supplied, this is the equivalend of using C(from_yaml). + notes: + - This filter functions as a wrapper to the Python C(yaml.safe_load_all) function, part of the L(pyyaml Python library, https://pypi.org/project/PyYAML/). + - Possible conflicts in variable names from the mulitple documents are resolved directly by the pyyaml library. + options: + _input: + description: A YAML string. + type: string + required: true + +EXAMPLES: | + # variable from string variable containing YAML documents + {{ multidoc_yaml_string | from_yaml_all }} + + # variable from multidocument YAML string + {{ '---\n{"a": true, "b": 54, "c": [1,2,3]}\n...\n---{"x": 1}\n...\n' | from_yaml_all}} + +RETURN: + _value: + description: The variable resulting from deserializing the YAML documents. + type: raw diff --git a/lib/ansible/plugins/filter/hash.yml b/lib/ansible/plugins/filter/hash.yml new file mode 100644 index 0000000..0f5f315 --- /dev/null +++ b/lib/ansible/plugins/filter/hash.yml @@ -0,0 +1,28 @@ +DOCUMENTATION: + name: checksum + version_added: "1.9" + short_description: hash of input data + description: + - Returns a configurable hash of the input data. Uses L(SHA-1, https://en.wikipedia.org/wiki/SHA-1) by default. + positional: _input + options: + _input: + description: Data to checksum. + type: raw + required: true + hashtype: + description: + - Type of algorithm to produce the hash. + - The list of available choices depends on the installed Python's hashlib. + type: string + default: sha1 +EXAMPLES: | + # sha1_hash => "109f4b3c50d7b0df729d299bc6f8e9ef9066971f" + sha1_hash: {{ 'test2' | hash('sha1') }} + # md5 => "5a105e8b9d40e1329780d62ea2265d8a" + md5: {{ 'test2' | hash('md5') }} + +RETURN: + _value: + description: The checksum of the input, as configured in I(hashtype). + type: string diff --git a/lib/ansible/plugins/filter/human_readable.yml b/lib/ansible/plugins/filter/human_readable.yml new file mode 100644 index 0000000..e3028ac --- /dev/null +++ b/lib/ansible/plugins/filter/human_readable.yml @@ -0,0 +1,35 @@ +DOCUMENTATION: + name: human_redable + version_added: "historical" + short_description: Make bytes/bits human readable + description: + - Convert byte or bit figures to more human readable formats. + positional: _input, isbits, unit + options: + _input: + description: Number of bytes, or bits. Depends on I(isbits). + type: int + required: true + isbits: + description: Whether the input is bits, instead of bytes. + type: bool + default: false + unit: + description: Unit to force output into. If none specified the largest unit arrived at will be used. + type: str + choices: [ 'Y', 'Z', 'E', 'P', 'T', 'G', 'M', 'K', 'B'] +EXAMPLES: | + + # size => "1.15 GB" + size: "{{ 1232345345 | human_readable }}" + + # size => "1.15 Gb" + size_bits: "{{ 1232345345 | human_readable(true) }}" + + # size => "1175.26 MB" + size_MB: "{{ 1232345345 | human_readable(unit='M') }}" + +RETURN: + _value: + description: Human readable byte or bit size. + type: str diff --git a/lib/ansible/plugins/filter/human_to_bytes.yml b/lib/ansible/plugins/filter/human_to_bytes.yml new file mode 100644 index 0000000..f03deed --- /dev/null +++ b/lib/ansible/plugins/filter/human_to_bytes.yml @@ -0,0 +1,34 @@ +DOCUMENTATION: + name: human_to_bytes + version_added: "historical" + short_description: Get bytes from string + description: + - Convert a human readable byte or bit string into a number bytes. + positional: _input, default_unit, isbits + options: + _input: + description: Human readable description of a number of bytes. + type: int + required: true + default_unit: + description: Unit to assume when input does not specify it. + type: str + choices: ['Y', 'Z', 'E', 'P', 'T', 'G', 'M', 'K', 'B'] + isbits: + description: If C(True), force to interpret only bit input; if C(False), force bytes. Otherwise use the notation to guess. + type: bool +EXAMPLES: | + + # size => 1234803098 + size: '{{ "1.15 GB" | human_to_bytes }}' + + # size => 1234803098 + size: '{{ "1.15" | human_to_bytes(deafult_unit="G") }}' + + # this is an error, wants bits, got bytes + ERROR: '{{ "1.15 GB" | human_to_bytes(isbits=true) }}' + +RETURN: + _value: + description: Integer representing the bytes from the input. + type: int diff --git a/lib/ansible/plugins/filter/intersect.yml b/lib/ansible/plugins/filter/intersect.yml new file mode 100644 index 0000000..d811eca --- /dev/null +++ b/lib/ansible/plugins/filter/intersect.yml @@ -0,0 +1,35 @@ +DOCUMENTATION: + name: intersect + author: Brian Coca (@bcoca) + version_added: "1.4" + short_description: intersection of lists + description: + - Provide a list with the common elements from other lists. + options: + _input: + description: A list. + type: list + required: true + _second_list: + description: A list. + type: list + required: true + seealso: + - plugin_type: filter + plugin: ansible.builtin.difference + - plugin_type: filter + plugin: ansible.builtin.symmetric_difference + - plugin_type: filter + plugin: ansible.builtin.unique + - plugin_type: filter + plugin: ansible.builtin.union +EXAMPLES: | + # return only the common elements of list1 and list2 + # list1: [1, 2, 5, 3, 4, 10] + # list2: [1, 2, 3, 4, 5, 11, 99] + {{ list1 | intersect(list2) }} + # => [1, 2, 5, 3, 4] +RETURN: + _value: + description: A list with unique elements common to both lists, also known as a set. + type: list diff --git a/lib/ansible/plugins/filter/items2dict.yml b/lib/ansible/plugins/filter/items2dict.yml new file mode 100644 index 0000000..1352c67 --- /dev/null +++ b/lib/ansible/plugins/filter/items2dict.yml @@ -0,0 +1,48 @@ +DOCUMENTATION: + name: items2dict + author: Ansible core team + version_added: "2.7" + short_description: Consolidate a list of itemized dictionaries into a dictionary + positional: _input, key_name, value_name + description: + - Takes a list of dicts with each having a C(key) and C(value) keys, and transforms the list into a dictionary, + effectively as the reverse of R(dict2items,ansible_collections.ansible.builtin.dict2items_filter). + options: + _input: + description: + - A list of dictionaries. + - Every dictionary must have keys C(key) and C(value). + type: list + elements: dict + required: true + key_name: + description: The name of the key in the element dictionaries that holds the key to use at destination. + type: str + default: key + value_name: + description: The name of the key in the element dictionaries that holds the value to use at destination. + type: str + default: value + seealso: + - plugin_type: filter + plugin: ansible.builtin.dict2items + +EXAMPLES: | + # mydict => { "hi": "bye", "ciao": "ciao" } + mydict: {{ [{'key': 'hi', 'value': 'bye'}, {'key': 'ciao', 'value': 'ciao'} ]| items2dict}} + + # The output is a dictionary with two key/value pairs: + # Application: payment + # Environment: dev + vars: + tags: + - key: Application + value: payment + - key: Environment + value: dev + consolidated: "{{ tags | items2dict }}" + +RETURN: + _value: + description: Dictionary with the consolidated key/values. + type: dict diff --git a/lib/ansible/plugins/filter/log.yml b/lib/ansible/plugins/filter/log.yml new file mode 100644 index 0000000..c7bb704 --- /dev/null +++ b/lib/ansible/plugins/filter/log.yml @@ -0,0 +1,33 @@ +DOCUMENTATION: + name: log + version_added: "1.9" + short_description: log of (math operation) + description: + - Math operation that returns the L(logarithm, https://en.wikipedia.org/wiki/Logarithm) to base N of the input number. + - By default, computes the L(natural logarithm, https://en.wikipedia.org/wiki/Natural_logarithm). + notes: + - This is a passthrough to Python's C(math.log). + positional: _input, base + options: + _input: + description: Number to operate on. + type: float + required: true + base: + description: Which base to use. Defaults to L(Euler's number, https://en.wikipedia.org/wiki/Euler%27s_number). + type: float + default: 2.718281828459045 + +EXAMPLES: | + + # 1.2920296742201791 + eightlogfive: "{{ 8 | log(5) }}" + + # 0.9030899869919435 + eightlog10: "{{ 8 | log() }}" + + +RETURN: + _value: + description: Resulting number. + type: float diff --git a/lib/ansible/plugins/filter/mandatory.yml b/lib/ansible/plugins/filter/mandatory.yml new file mode 100644 index 0000000..5addf15 --- /dev/null +++ b/lib/ansible/plugins/filter/mandatory.yml @@ -0,0 +1,21 @@ +DOCUMENTATION: + name: mandatory + version_added: "historical" + short_description: make a variable's existance mandatory + description: + - Depending on context undefined variables can be ignored or skipped, this ensures they force an error. + positional: _input + options: + _input: + description: Mandatory expression. + type: raw + required: true +EXAMPLES: | + + # results in a Filter Error + {{ notdefined | mandatory }} + +RETURN: + _value: + description: The input if defined, otherwise an error. + type: raw diff --git a/lib/ansible/plugins/filter/mathstuff.py b/lib/ansible/plugins/filter/mathstuff.py new file mode 100644 index 0000000..d4b6af7 --- /dev/null +++ b/lib/ansible/plugins/filter/mathstuff.py @@ -0,0 +1,252 @@ +# Copyright 2014, Brian Coca <bcoca@ansible.com> +# Copyright 2017, Ken Celenza <ken@networktocode.com> +# Copyright 2017, Jason Edelman <jason@networktocode.com> +# Copyright 2017, Ansible Project +# +# 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 itertools +import math + +from collections.abc import Hashable, Mapping, Iterable + +from jinja2.filters import pass_environment + +from ansible.errors import AnsibleFilterError, AnsibleFilterTypeError +from ansible.module_utils.common.text import formatters +from ansible.module_utils.six import binary_type, text_type +from ansible.module_utils._text import to_native, to_text +from ansible.utils.display import Display + +try: + from jinja2.filters import do_unique + HAS_UNIQUE = True +except ImportError: + HAS_UNIQUE = False + + +display = Display() + + +@pass_environment +# Use case_sensitive=None as a sentinel value, so we raise an error only when +# explicitly set and cannot be handle (by Jinja2 w/o 'unique' or fallback version) +def unique(environment, a, case_sensitive=None, attribute=None): + + def _do_fail(e): + if case_sensitive is False or attribute: + raise AnsibleFilterError("Jinja2's unique filter failed and we cannot fall back to Ansible's version " + "as it does not support the parameters supplied", orig_exc=e) + + error = e = None + try: + if HAS_UNIQUE: + c = list(do_unique(environment, a, case_sensitive=bool(case_sensitive), attribute=attribute)) + except TypeError as e: + error = e + _do_fail(e) + except Exception as e: + error = e + _do_fail(e) + display.warning('Falling back to Ansible unique filter as Jinja2 one failed: %s' % to_text(e)) + + if not HAS_UNIQUE or error: + + # handle Jinja2 specific attributes when using Ansible's version + if case_sensitive is False or attribute: + raise AnsibleFilterError("Ansible's unique filter does not support case_sensitive=False nor attribute parameters, " + "you need a newer version of Jinja2 that provides their version of the filter.") + + c = [] + for x in a: + if x not in c: + c.append(x) + + return c + + +@pass_environment +def intersect(environment, a, b): + if isinstance(a, Hashable) and isinstance(b, Hashable): + c = set(a) & set(b) + else: + c = unique(environment, [x for x in a if x in b], True) + return c + + +@pass_environment +def difference(environment, a, b): + if isinstance(a, Hashable) and isinstance(b, Hashable): + c = set(a) - set(b) + else: + c = unique(environment, [x for x in a if x not in b], True) + return c + + +@pass_environment +def symmetric_difference(environment, a, b): + if isinstance(a, Hashable) and isinstance(b, Hashable): + c = set(a) ^ set(b) + else: + isect = intersect(environment, a, b) + c = [x for x in union(environment, a, b) if x not in isect] + return c + + +@pass_environment +def union(environment, a, b): + if isinstance(a, Hashable) and isinstance(b, Hashable): + c = set(a) | set(b) + else: + c = unique(environment, a + b, True) + return c + + +def logarithm(x, base=math.e): + try: + if base == 10: + return math.log10(x) + else: + return math.log(x, base) + except TypeError as e: + raise AnsibleFilterTypeError('log() can only be used on numbers: %s' % to_native(e)) + + +def power(x, y): + try: + return math.pow(x, y) + except TypeError as e: + raise AnsibleFilterTypeError('pow() can only be used on numbers: %s' % to_native(e)) + + +def inversepower(x, base=2): + try: + if base == 2: + return math.sqrt(x) + else: + return math.pow(x, 1.0 / float(base)) + except (ValueError, TypeError) as e: + raise AnsibleFilterTypeError('root() can only be used on numbers: %s' % to_native(e)) + + +def human_readable(size, isbits=False, unit=None): + ''' Return a human readable string ''' + try: + return formatters.bytes_to_human(size, isbits, unit) + except TypeError as e: + raise AnsibleFilterTypeError("human_readable() failed on bad input: %s" % to_native(e)) + except Exception: + raise AnsibleFilterError("human_readable() can't interpret following string: %s" % size) + + +def human_to_bytes(size, default_unit=None, isbits=False): + ''' Return bytes count from a human readable string ''' + try: + return formatters.human_to_bytes(size, default_unit, isbits) + except TypeError as e: + raise AnsibleFilterTypeError("human_to_bytes() failed on bad input: %s" % to_native(e)) + except Exception: + raise AnsibleFilterError("human_to_bytes() can't interpret following string: %s" % size) + + +def rekey_on_member(data, key, duplicates='error'): + """ + Rekey a dict of dicts on another member + + May also create a dict from a list of dicts. + + duplicates can be one of ``error`` or ``overwrite`` to specify whether to error out if the key + value would be duplicated or to overwrite previous entries if that's the case. + """ + if duplicates not in ('error', 'overwrite'): + raise AnsibleFilterError("duplicates parameter to rekey_on_member has unknown value: {0}".format(duplicates)) + + new_obj = {} + + # Ensure the positional args are defined - raise jinja2.exceptions.UndefinedError if not + bool(data) and bool(key) + + if isinstance(data, Mapping): + iterate_over = data.values() + elif isinstance(data, Iterable) and not isinstance(data, (text_type, binary_type)): + iterate_over = data + else: + raise AnsibleFilterTypeError("Type is not a valid list, set, or dict") + + for item in iterate_over: + if not isinstance(item, Mapping): + raise AnsibleFilterTypeError("List item is not a valid dict") + + try: + key_elem = item[key] + except KeyError: + raise AnsibleFilterError("Key {0} was not found".format(key)) + except TypeError as e: + raise AnsibleFilterTypeError(to_native(e)) + except Exception as e: + raise AnsibleFilterError(to_native(e)) + + # Note: if new_obj[key_elem] exists it will always be a non-empty dict (it will at + # minimum contain {key: key_elem} + if new_obj.get(key_elem, None): + if duplicates == 'error': + raise AnsibleFilterError("Key {0} is not unique, cannot correctly turn into dict".format(key_elem)) + elif duplicates == 'overwrite': + new_obj[key_elem] = item + else: + new_obj[key_elem] = item + + return new_obj + + +class FilterModule(object): + ''' Ansible math jinja2 filters ''' + + def filters(self): + filters = { + # exponents and logarithms + 'log': logarithm, + 'pow': power, + 'root': inversepower, + + # set theory + 'unique': unique, + 'intersect': intersect, + 'difference': difference, + 'symmetric_difference': symmetric_difference, + 'union': union, + + # combinatorial + 'product': itertools.product, + 'permutations': itertools.permutations, + 'combinations': itertools.combinations, + + # computer theory + 'human_readable': human_readable, + 'human_to_bytes': human_to_bytes, + 'rekey_on_member': rekey_on_member, + + # zip + 'zip': zip, + 'zip_longest': itertools.zip_longest, + + } + + return filters diff --git a/lib/ansible/plugins/filter/md5.yml b/lib/ansible/plugins/filter/md5.yml new file mode 100644 index 0000000..c97870d --- /dev/null +++ b/lib/ansible/plugins/filter/md5.yml @@ -0,0 +1,24 @@ +DOCUMENTATION: + name: md5 + version_added: "historical" + short_description: MD5 hash of input data + description: + - Returns an L(MD5 hash, https://en.wikipedia.org/wiki/MD5) of the input data + positional: _input + notes: + - This requires the MD5 algorithm to be available on the system, security contexts like FIPS might prevent this. + - MD5 has long been deemed insecure and is not recommended for security related uses. + options: + _input: + description: data to hash + type: raw + required: true + +EXAMPLES: | + # md5hash => "ae2b1fca515949e5d54fb22b8ed95575" + md5hash: "{{ 'testing' | md5 }}" + +RETURN: + _value: + description: The MD5 hash of the input. + type: string diff --git a/lib/ansible/plugins/filter/password_hash.yml b/lib/ansible/plugins/filter/password_hash.yml new file mode 100644 index 0000000..d12efb4 --- /dev/null +++ b/lib/ansible/plugins/filter/password_hash.yml @@ -0,0 +1,37 @@ +DOCUMENTATION: + name: password_hash + version_added: "historical" + short_description: convert input password into password_hash + description: + - Returns a password_hash of a secret. + positional: _input + notes: + - Algorithms available might be restricted by the system. + options: + _input: + description: Secret to hash. + type: string + required: true + hashtype: + description: Hashing algorithm to use. + type: string + default: sha512 + choices: [ md5, blowfish, sha256, sha512 ] + salt: + description: Secret string that is used for the hashing, if none is provided a random one can be generated. + type: int + rounds: + description: Number of encryption rounds, default varies by algorithm used. + type: int + ident: + description: Algorithm identifier. + type: string + +EXAMPLES: | + # pwdhash => "$6$/bQCntzQ7VrgVcFa$VaMkmevkY1dqrx8neaenUDlVU.6L/.ojRbrnI4ID.yBHU6XON1cB422scCiXfUL5wRucMdLgJU0Fn38uoeBni/" + pwdhash: "{{ 'testing' | password_hash }}" + +RETURN: + _value: + description: The resulting password hash. + type: string diff --git a/lib/ansible/plugins/filter/path_join.yml b/lib/ansible/plugins/filter/path_join.yml new file mode 100644 index 0000000..d50deaa --- /dev/null +++ b/lib/ansible/plugins/filter/path_join.yml @@ -0,0 +1,30 @@ +DOCUMENTATION: + name: path_join + author: Anthony Bourguignon (@Toniob) + version_added: "2.10" + short_description: Join one or more path components + positional: _input + description: + - Returns a path obtained by joining one or more path components. + options: + _input: + description: A path, or a list of paths. + type: list + elements: str + required: true + +EXAMPLES: | + + # If path == 'foo/bar' and file == 'baz.txt', the result is '/etc/foo/bar/subdir/baz.txt' + {{ ('/etc', path, 'subdir', file) | path_join }} + + # equivalent to '/etc/subdir/{{filename}}' + wheremyfile: "{{ ['/etc', 'subdir', filename] | path_join }}" + + # trustme => '/etc/apt/trusted.d/mykey.gpgp' + trustme: "{{ ['/etc', 'apt', 'trusted.d', 'mykey.gpg'] | path_join }}" + +RETURN: + _value: + description: The concatenated path. + type: str diff --git a/lib/ansible/plugins/filter/permutations.yml b/lib/ansible/plugins/filter/permutations.yml new file mode 100644 index 0000000..6e0202b --- /dev/null +++ b/lib/ansible/plugins/filter/permutations.yml @@ -0,0 +1,26 @@ +DOCUMENTATION: + name: permutations + version_added: "historical" + short_description: permutations from the elements of a list + description: + - Create a list of the permutations of lists from the elements of a list. + - Unlike combinations, in permutations order is significant. + positional: _input, list_size + options: + _input: + description: Elements to base the permutations on. + type: list + required: true + list_size: + description: The size of the list for each permutation. + type: int + required: true + +EXAMPLES: | + # ptrs_of_two => [ [ 1, 2 ], [ 1, 3 ], [ 1, 4 ], [ 1, 5 ], [ 2, 1 ], [ 2, 3 ], [ 2, 4 ], [ 2, 5 ], [ 3, 1 ], [ 3, 2 ], [ 3, 4 ], [ 3, 5 ], [ 4, 1 ], [ 4, 2 ], [ 4, 3 ], [ 4, 5 ], [ 5, 1 ], [ 5, 2 ], [ 5, 3 ], [ 5, 4 ] ] + prts_of_two: "{{ [1,2,3,4,5] | permutations(2) }}" + +RETURN: + _value: + description: List of permutations lists resulting from the supplied elements and list size. + type: list diff --git a/lib/ansible/plugins/filter/pow.yml b/lib/ansible/plugins/filter/pow.yml new file mode 100644 index 0000000..da2fa42 --- /dev/null +++ b/lib/ansible/plugins/filter/pow.yml @@ -0,0 +1,34 @@ +DOCUMENTATION: + name: pow + version_added: "1.9" + short_description: power of (math operation) + description: + - Math operation that returns the Nth power of inputed number, C(X ^ N). + notes: + - This is a passthrough to Python's C(math.pow). + positional: _input, _power + options: + _input: + description: The base. + type: float + required: true + _power: + description: Which power (exponent) to use. + type: float + required: true + +EXAMPLES: | + + # => 32768 + eight_power_five: "{{ 8 | pow(5) }}" + + # 4 + square_of_2: "{{ 2 | pow(2) }}" + + # me ^ 3 + cube_me: "{{ me | pow(3) }}" + +RETURN: + _value: + description: Resulting number. + type: float diff --git a/lib/ansible/plugins/filter/product.yml b/lib/ansible/plugins/filter/product.yml new file mode 100644 index 0000000..5035522 --- /dev/null +++ b/lib/ansible/plugins/filter/product.yml @@ -0,0 +1,42 @@ +DOCUMENTATION: + name: product + version_added: "historical" + short_description: cartesian product of lists + description: + - Combines two lists into one with each element being the product of the elements of the input lists. + - Creates 'nested loops'. Looping over C(listA) and C(listB) is the same as looping over C(listA | product(listB)). + notes: + - This is a passthrough to Python's C(itertools.product) + positional: _input, _additional_lists, repeat + options: + _input: + description: First list. + type: list + required: true + _additional_lists: #TODO: *args, N possible additional lists + description: Additional list for the product. + type: list + required: false + repeat: + description: Number of times to repeat the product against itself. + default: 1 + type: int +EXAMPLES: | + + # product => [ [ 1, "a" ], [ 1, "b" ], [ 1, "c" ], [ 2, "a" ], [ 2, "b" ], [ 2, "c" ], [ 3, "a" ], [ 3, "b" ], [ 3, "c" ], [ 4, "a" ], [ 4, "b" ], [ 4, "c" ], [ 5, "a" ], [ 5, "b" ], [ 5, "c" ] ] + product: "{{ [1,2,3,4,5] | product(['a', 'b', 'c']) }}" + + # repeat_original => [ [ 1, 1 ], [ 1, 2 ], [ 2, 1 ], [ 2, 2 ] ] + repeat_original: "{{ [1,2] | product(repeat=2) }}" + + # repeat_product => [ [ 1, "a", 1, "a" ], [ 1, "a", 1, "b" ], [ 1, "a", 2, "a" ], [ 1, "a", 2, "b" ], [ 1, "b", 1, "a" ], [ 1, "b", 1, "b" ], [ 1, "b", 2, "a" ], [ 1, "b", 2, "b" ], [ 2, "a", 1, "a" ], [ 2, "a", 1, "b" ], [ 2, "a", 2, "a" ], [ 2, "a", 2, "b" ], [ 2, "b", 1, "a" ], [ 2, "b", 1, "b" ], [ 2, "b", 2, "a" ], [ 2, "b", 2, "b" ] ] + repeat_product: "{{ [1,2] | product(['a', 'b'], repeat=2) }}" + + # domains => [ 'example.com', 'ansible.com', 'redhat.com' ] + domains: "{{ [ 'example', 'ansible', 'redhat'] | product(['com']) | map('join', '.') }}" + +RETURN: + _value: + description: List of lists of combined elements from the input lists. + type: list + elements: list diff --git a/lib/ansible/plugins/filter/quote.yml b/lib/ansible/plugins/filter/quote.yml new file mode 100644 index 0000000..2d621ed --- /dev/null +++ b/lib/ansible/plugins/filter/quote.yml @@ -0,0 +1,23 @@ +DOCUMENTATION: + name: quote + version_added: "2.10" + short_description: shell quoting + description: + - Quote a string to safely use as in a POSIX shell. + notes: + - This is a passthrough to Python's C(shlex.quote). + positional: _input + options: + _input: + description: String to quote. + type: str + required: true + +EXAMPLES: | + - name: Run a shell command + shell: echo {{ string_value | quote }} + +RETURN: + _value: + description: Quoted string. + type: str diff --git a/lib/ansible/plugins/filter/random.yml b/lib/ansible/plugins/filter/random.yml new file mode 100644 index 0000000..b72dbb2 --- /dev/null +++ b/lib/ansible/plugins/filter/random.yml @@ -0,0 +1,35 @@ +DOCUMENTATION: + name: random + version_added: "2.6" + short_description: random number or list item + description: + - Use the input to either select a random element of a list or generate a random number. + positional: _input, start, step, seed + options: + _input: + description: A number or list/sequence, if it is a number it is the top bound for random number generation, if it is a sequence or list, the source of the random element selected. + type: raw + required: true + start: + description: Bottom bound for the random number/element generated. + type: int + step: + description: Subsets the defined range by only using this value to select the increments of it between start and end. + type: int + default: 1 + seed: + description: If specified use a pseudo random selection instead (repeatable). + type: str + +EXAMPLES: | + + # can be any item from the list + random_item: "{{ ['a','b','c'] | random }}" + + # cron line, select random minute repeatable for each host + "{{ 60 | random(seed=inventory_hostname) }} * * * * root /script/from/cron" + +RETURN: + _value: + description: Random number or list element. + type: raw diff --git a/lib/ansible/plugins/filter/realpath.yml b/lib/ansible/plugins/filter/realpath.yml new file mode 100644 index 0000000..12687b6 --- /dev/null +++ b/lib/ansible/plugins/filter/realpath.yml @@ -0,0 +1,21 @@ +DOCUMENTATION: + name: realpath + author: darkone23 (@darkone23) + version_added: "1.8" + short_description: Turn path into real path + description: + - Resolves/follows symliknks to return the 'real path' from a given path. + - Filters alwasy run on controller so this path is resolved using the controller's filesystem. + options: + _input: + description: A path. + type: path + required: true +EXAMPLES: | + + realpath: {{ '/path/to/synlink' | realpath }} + +RETURN: + _value: + description: The canonical path. + type: path diff --git a/lib/ansible/plugins/filter/regex_escape.yml b/lib/ansible/plugins/filter/regex_escape.yml new file mode 100644 index 0000000..7819909 --- /dev/null +++ b/lib/ansible/plugins/filter/regex_escape.yml @@ -0,0 +1,29 @@ +DOCUMENTATION: + name: regex_escape + version_added: "2.8" + short_description: escape regex chars + description: + - Escape special characters in a string for use in a regular expression. + positional: _input, re_type + notes: + - posix_extended is not implemented yet + options: + _input: + description: String to escape. + type: str + required: true + re_type: + description: Which type of escaping to use. + type: str + default: python + choices: [python, posix_basic] + +EXAMPLES: | + + # safe_for_regex => '\^f\.\*o\(\.\*\)\$' + safe_for_regex: "{{ '^f.*o(.*)$' | regex_escape() }}" + +RETURN: + _value: + description: Escaped string. + type: str diff --git a/lib/ansible/plugins/filter/regex_findall.yml b/lib/ansible/plugins/filter/regex_findall.yml new file mode 100644 index 0000000..707d6fa --- /dev/null +++ b/lib/ansible/plugins/filter/regex_findall.yml @@ -0,0 +1,37 @@ +DOCUMENTATION: + name: regex_findall + version_added: "2.0" + short_description: extract all regex matches from string + description: + - Search in a string or extract all the parts of a string matching a regular expression. + positional: _input, _regex + options: + _input: + description: String to match against. + type: str + required: true + _regex: + description: Regular expression string that defines the match. + type: str + multiline: + description: Search across line endings if C(True), do not if otherwise. + type: bool + default: no + ignorecase: + description: Force the search to be case insensitive if C(True), case sensitive otherwise. + type: bool + default: no + +EXAMPLES: | + + # all_pirates => ['CAR', 'tar', 'bar'] + all_pirates: "{{ 'CAR\ntar\nfoo\nbar\n' | regex_findall('^.ar$', multiline=True, ignorecase=True) }}" + + # get_ips => ['8.8.8.8', '8.8.4.4'] + get_ips: "{{ 'Some DNS servers are 8.8.8.8 and 8.8.4.4' | regex_findall('\\b(?:[0-9]{1,3}\\.){3}[0-9]{1,3}\\b') }}" + +RETURN: + _value: + description: List of matched strings. + type: list + elements: str diff --git a/lib/ansible/plugins/filter/regex_replace.yml b/lib/ansible/plugins/filter/regex_replace.yml new file mode 100644 index 0000000..0277b56 --- /dev/null +++ b/lib/ansible/plugins/filter/regex_replace.yml @@ -0,0 +1,46 @@ +DOCUMENTATION: + name: regex_replace + version_added: "2.0" + short_description: replace a string via regex + description: + - Replace a substring defined by a regular expression with another defined by another regular expression based on the first match. + notes: + - Maps to Python's C(re.replace). + positional: _input, _regex_match, _regex_replace + options: + _input: + description: String to match against. + type: str + required: true + _regex_match: + description: Regular expression string that defines the match. + type: int + required: true + _regex_replace: + description: Regular expression string that defines the replacement. + type: int + required: true + multiline: + description: Search across line endings if C(True), do not if otherwise. + type: bool + default: no + ignorecase: + description: Force the search to be case insensitive if C(True), case sensitive otherwise. + type: bool + default: no + +EXAMPLES: | + + # whatami => 'able' + whatami: "{{ 'ansible' | regex_replace('^a.*i(.*)$', 'a\\1') }}" + + # commalocal => 'localhost, 80' + commalocal: "{{ 'localhost:80' | regex_replace('^(?P<host>.+):(?P<port>\\d+)$', '\\g<host>, \\g<port>') }}" + + # piratecomment => '#CAR\n#tar\nfoo\n#bar\n' + piratecomment: "{{ 'CAR\ntar\nfoo\nbar\n' | regex_replace('^(.ar)$', '#\\1', multiline=True, ignorecase=True) }}" + +RETURN: + _value: + description: String with substitution (or original if no match). + type: str diff --git a/lib/ansible/plugins/filter/regex_search.yml b/lib/ansible/plugins/filter/regex_search.yml new file mode 100644 index 0000000..c61efb7 --- /dev/null +++ b/lib/ansible/plugins/filter/regex_search.yml @@ -0,0 +1,38 @@ +DOCUMENTATION: + name: regex_search + version_added: "2.0" + short_description: extract regex match from string + description: + - Search in a string to extract the part that matches the regular expression. + notes: + - Maps to Python's C(re.search). + positional: _input, _regex + options: + _input: + description: String to match against. + type: str + required: true + _regex: + description: Regular expression string that defines the match. + type: str + multiline: + description: Search across line endings if C(True), do not if otherwise. + type: bool + default: no + ignorecase: + description: Force the search to be case insensitive if C(True), case sensitive otherwise. + type: bool + default: no + +EXAMPLES: | + + # db => 'database42' + db: "{{ 'server1/database42' | regex_search('database[0-9]+') }}" + + # drinkat => 'BAR' + drinkat: "{{ 'foo\nBAR' | regex_search('^bar', multiline=True, ignorecase=True) }}" + +RETURN: + _value: + description: Matched string or empty string if no match. + type: str diff --git a/lib/ansible/plugins/filter/rekey_on_member.yml b/lib/ansible/plugins/filter/rekey_on_member.yml new file mode 100644 index 0000000..d7470ab --- /dev/null +++ b/lib/ansible/plugins/filter/rekey_on_member.yml @@ -0,0 +1,30 @@ +DOCUMENTATION: + name: rekey_on_member + version_added: "2.13" + short_description: Rekey a list of dicts into a dict using a member + positional: _input, '_key', duplicates + description: Iterate over several iterables in parallel, producing tuples with an item from each one. + options: + _input: + description: Original dictionary. + type: dict + required: yes + _key: + description: The key to rekey. + type: str + required: yes + duplicates: + description: How to handle duplicates. + type: str + default: error + choices: [overwrite, error] + +EXAMPLES: | + + # mydict => {'eigrp': {'state': 'enabled', 'proto': 'eigrp'}, 'ospf': {'state': 'enabled', 'proto': 'ospf'}} + mydict: '{{ [{"proto": "eigrp", "state": "enabled"}, {"proto": "ospf", "state": "enabled"}] | rekey_on_member("proto") }}' + +RETURN: + _value: + description: The resulting dictionary. + type: dict diff --git a/lib/ansible/plugins/filter/relpath.yml b/lib/ansible/plugins/filter/relpath.yml new file mode 100644 index 0000000..47611c7 --- /dev/null +++ b/lib/ansible/plugins/filter/relpath.yml @@ -0,0 +1,28 @@ +DOCUMENTATION: + name: relpath + author: Jakub Jirutka (@jirutka) + version_added: "1.7" + short_description: Make a path relative + positional: _input, start + description: + - Converts the given path to a relative path from the I(start), + or relative to the directory given in I(start). + options: + _input: + description: A path. + type: str + required: true + start: + description: The directory the path should be relative to. If not supplied the current working directory will be used. + type: str + +EXAMPLES: | + + # foobar => ../test/me.txt + testing: "{{ '/tmp/test/me.txt' | relpath('/tmp/other/') }}" + otherrelpath: "{{ mypath | relpath(mydir) }}" + +RETURN: + _value: + description: The relative path. + type: str diff --git a/lib/ansible/plugins/filter/root.yml b/lib/ansible/plugins/filter/root.yml new file mode 100644 index 0000000..4f52590 --- /dev/null +++ b/lib/ansible/plugins/filter/root.yml @@ -0,0 +1,32 @@ +DOCUMENTATION: + name: root + version_added: "1.9" + short_description: root of (math operation) + description: + - Math operation that returns the Nth root of inputed number C(X ^^ N). + positional: _input, base + options: + _input: + description: Number to operate on. + type: float + required: true + base: + description: Which root to take. + type: float + default: 2 + +EXAMPLES: | + + # => 8 + fiveroot: "{{ 32768 | root (5) }}" + + # 2 + sqrt_of_2: "{{ 4 | root }}" + + # me ^^ 3 + cuberoot_me: "{{ me | root(3) }}" + +RETURN: + _value: + description: Resulting number. + type: float diff --git a/lib/ansible/plugins/filter/sha1.yml b/lib/ansible/plugins/filter/sha1.yml new file mode 100644 index 0000000..f80803b --- /dev/null +++ b/lib/ansible/plugins/filter/sha1.yml @@ -0,0 +1,24 @@ +DOCUMENTATION: + name: sha1 + version_added: "historical" + short_description: SHA-1 hash of input data + description: + - Returns a L(SHA-1 hash, https://en.wikipedia.org/wiki/SHA-1) of the input data. + positional: _input + notes: + - This requires the SHA-1 algorithm to be available on the system, security contexts like FIPS might prevent this. + - SHA-1 has been deemed insecure and is not recommended for security related uses. + options: + _input: + description: Data to hash. + type: raw + required: true + +EXAMPLES: | + # sha1hash => "dc724af18fbdd4e59189f5fe768a5f8311527050" + sha1hash: "{{ 'testing' | sha1 }}" + +RETURN: + _value: + description: The SHA-1 hash of the input. + type: string diff --git a/lib/ansible/plugins/filter/shuffle.yml b/lib/ansible/plugins/filter/shuffle.yml new file mode 100644 index 0000000..a7c3e7e --- /dev/null +++ b/lib/ansible/plugins/filter/shuffle.yml @@ -0,0 +1,27 @@ +DOCUMENTATION: + name: shuffle + version_added: "2.6" + short_description: randomize a list + description: + - Take the elements of the input list and return in a random order. + positional: _input + options: + _input: + description: A number or list to randomize. + type: list + elements: any + required: true + seed: + description: If specified use a pseudo random selection instead (repeatable). + type: str + +EXAMPLES: | + + randomized_list: "{{ ['a','b','c'] | shuffle}}" + per_host_repeatable: "{{ ['a','b','c'] | shuffle(seed=inventory_hostname) }}" + +RETURN: + _value: + description: Random number or list element. + type: list + elements: any diff --git a/lib/ansible/plugins/filter/split.yml b/lib/ansible/plugins/filter/split.yml new file mode 100644 index 0000000..3e7b59e --- /dev/null +++ b/lib/ansible/plugins/filter/split.yml @@ -0,0 +1,32 @@ +DOCUMENTATION: + name: split + version_added: "historical" + short_description: split a string into a list + description: + - Using Python's text object method C(split) we turn strings into lists via a 'spliting character'. + notes: + - This is a passthrough to Python's C(str.split). + positional: _input, _split_string + options: + _input: + description: A string to split. + type: str + required: true + _split_string: + description: A string on which to split the original. + type: str + default: ' ' + +EXAMPLES: | + + # listjojo => [ "jojo", "is", "a" ] + listjojo: "{{ 'jojo is a' | split }}" + + # listjojocomma => [ "jojo is", "a" ] + listjojocomma: "{{ 'jojo is, a' | split(',' }}" + +RETURN: + _value: + description: List of substrings split from the original. + type: list + elements: str diff --git a/lib/ansible/plugins/filter/splitext.yml b/lib/ansible/plugins/filter/splitext.yml new file mode 100644 index 0000000..ea9cbce --- /dev/null +++ b/lib/ansible/plugins/filter/splitext.yml @@ -0,0 +1,30 @@ +DOCUMENTATION: + name: splitext + author: Matt Martz (@sivel) + version_added: "2.0" + short_description: split a path into root and file extension + positional: _input + description: + - Returns a list of two, with the elements consisting of filename root and extension. + options: + _input: + description: A path. + type: str + required: true + +EXAMPLES: | + + # gobble => [ '/etc/make', 'conf' ] + gobble: "{{ '/etc/make.conf' | splitext }}" + + # file_n_ext => [ 'ansible', 'cfg' ] + file_n_ext: "{{ 'ansible.cfg' | splitext }}" + + # hoax => ['/etc/hoasdf', ''] + hoax: '{{ "/etc//hoasdf/"|splitext }}' + +RETURN: + _value: + description: A list consisting of root of the path and the extension. + type: list + elements: str diff --git a/lib/ansible/plugins/filter/strftime.yml b/lib/ansible/plugins/filter/strftime.yml new file mode 100644 index 0000000..6cb8874 --- /dev/null +++ b/lib/ansible/plugins/filter/strftime.yml @@ -0,0 +1,45 @@ +DOCUMENTATION: + name: strftime + version_added: "2.4" + short_description: date formating + description: + - Using Python's C(strftime) function, take a data formating string and a date/time to create a formated date. + notes: + - This is a passthrough to Python's C(stftime). + positional: _input, second, utc + options: + _input: + description: + - A formating string following C(stftime) conventions. + - See L(the Python documentation, https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior) for a reference. + type: str + required: true + second: + description: Datetime in seconds from C(epoch) to format, if not supplied C(gmttime/localtime) will be used. + type: int + utc: + description: Whether time supplied is in UTC. + type: bool + default: false + +EXAMPLES: | + # Display year-month-day + {{ '%Y-%m-%d' | strftime }} + # => "2021-03-19" + + # Display hour:min:sec + {{ '%H:%M:%S' | strftime }} + # => "21:51:04" + + # Use ansible_date_time.epoch fact + {{ '%Y-%m-%d %H:%M:%S' | strftime(ansible_date_time.epoch) }} + # => "2021-03-19 21:54:09" + + # Use arbitrary epoch value + {{ '%Y-%m-%d' | strftime(0) }} # => 1970-01-01 + {{ '%Y-%m-%d' | strftime(1441357287) }} # => 2015-09-04 + +RETURN: + _value: + description: A formatted date/time string. + type: str diff --git a/lib/ansible/plugins/filter/subelements.yml b/lib/ansible/plugins/filter/subelements.yml new file mode 100644 index 0000000..a2d1a94 --- /dev/null +++ b/lib/ansible/plugins/filter/subelements.yml @@ -0,0 +1,38 @@ +DOCUMENTATION: + name: subelements + version_added: "2.7" + short_description: retuns a product of a list and it's elements + positional: _input, _subelement, skip_missing + description: + - This produces a product of an object and the subelement values of that object, similar to the subelements lookup. This lets you specify individual subelements to use in a template I(_input). + options: + _input: + description: Original list. + type: list + elements: any + required: yes + _subelement: + description: Label of property to extract from original list items. + type: str + required: yes + skip_missing: + description: If C(True), ignore missing subelements, otherwise missing subelements generate an error. + type: bool + default: no + +EXAMPLES: | + # data + users: + - groups: [1,2,3] + name: lola + - name: fernando + groups: [2,3,4] + + # user_w_groups =>[ { "groups": [ 1, 2, 3 ], "name": "lola" }, 1 ], [ { "groups": [ 1, 2, 3 ], "name": "lola" }, 2 ], [ { "groups": [ 1, 2, 3 ], "name": "lola" }, 3 ], [ { "groups": [ 2, 3, 4 ], "name": "fernando" }, 2 ], [ { "groups": [ 2, 3, 4 ], "name": "fernando" }, 3 ], [ { "groups": [ 2, 3, 4 ], "name": "fernando" }, 4 ] ] + users_w_groups: {{ users | subelements('groups', skip_missing=True) }} + +RETURN: + _value: + description: List made of original list and product of the subelement list. + type: list + elements: any diff --git a/lib/ansible/plugins/filter/symmetric_difference.yml b/lib/ansible/plugins/filter/symmetric_difference.yml new file mode 100644 index 0000000..de4f3c6 --- /dev/null +++ b/lib/ansible/plugins/filter/symmetric_difference.yml @@ -0,0 +1,35 @@ +DOCUMENTATION: + name: symmetric_difference + author: Brian Coca (@bcoca) + version_added: "1.4" + short_description: different items from two lists + description: + - Provide a unique list of all the elements unique to each list. + options: + _input: + description: A list. + type: list + required: true + _second_list: + description: A list. + type: list + required: true + seealso: + - plugin_type: filter + plugin: ansible.builtin.difference + - plugin_type: filter + plugin: ansible.builtin.intersect + - plugin_type: filter + plugin: ansible.builtin.union + - plugin_type: filter + plugin: ansible.builtin.unique +EXAMPLES: | + # return the elements of list1 not in list2 and the elements in list2 not in list1 + # list1: [1, 2, 5, 1, 3, 4, 10] + # list2: [1, 2, 3, 4, 5, 11, 99] + {{ list1 | symmetric_difference(list2) }} + # => [10, 11, 99] +RETURN: + _value: + description: A unique list of the elements from two lists that are unique to each one. + type: list diff --git a/lib/ansible/plugins/filter/ternary.yml b/lib/ansible/plugins/filter/ternary.yml new file mode 100644 index 0000000..50ff767 --- /dev/null +++ b/lib/ansible/plugins/filter/ternary.yml @@ -0,0 +1,44 @@ +DOCUMENTATION: + name: ternary + author: Brian Coca (@bcoca) + version_added: '1.9' + short_description: Ternary operation filter + description: + - Return the first value if the input is C(True), the second if C(False). + positional: true_val, false_val + options: + _input: + description: A boolean expression, must evaluate to C(True) or C(False). + type: bool + required: true + true_val: + description: Value to return if the input is C(True). + type: any + required: true + false_val: + description: Value to return if the input is C(False). + type: any + none_val: + description: Value to return if the input is C(None). If not set, C(None) will be treated as C(False). + type: any + version_added: '2.8' + notes: + - Vars as values are evaluated even when not returned. This is due to them being evaluated before being passed into the filter. + +EXAMPLES: | + # set first 10 volumes rw, rest as dp + volume_mode: "{{ (item|int < 11)|ternary('rw', 'dp') }}" + + # choose correct vpc subnet id, note that vars as values are evaluated even if not returned + vpc_subnet_id: "{{ (ec2_subnet_type == 'public') | ternary(ec2_vpc_public_subnet_id, ec2_vpc_private_subnet_id) }}" + + - name: service-foo, use systemd module unless upstart is present, then use old service module + service: + state: restarted + enabled: yes + use: "{{ (ansible_service_mgr == 'upstart') | ternary('service', 'systemd') }}" + +RETURN: + _value: + description: The value indicated by the input. + type: any diff --git a/lib/ansible/plugins/filter/to_datetime.yml b/lib/ansible/plugins/filter/to_datetime.yml new file mode 100644 index 0000000..dbd476a --- /dev/null +++ b/lib/ansible/plugins/filter/to_datetime.yml @@ -0,0 +1,35 @@ +DOCUMENTATION: + name: to_datetime + version_added: "2.4" + short_description: Get C(datetime) from string + description: + - Using the input string attempt to create a matching Python C(datetime) object. + notes: + - For a full list of format codes for working with Python date format strings, see + L(the Python documentation, https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior). + positional: _input + options: + _input: + description: A string containing date time information. + type: str + required: true + format: + description: C(strformat) formatted string that describes the expected format of the input string. + type: str + +EXAMPLES: | + + # Get total amount of seconds between two dates. Default date format is %Y-%m-%d %H:%M:%S but you can pass your own format + secsdiff: '{{ (("2016-08-14 20:00:12" | to_datetime) - ("2015-12-25" | to_datetime("%Y-%m-%d"))).total_seconds() }}' + + # Get remaining seconds after delta has been calculated. NOTE: This does NOT convert years, days, hours, and so on to seconds. For that, use total_seconds() + {{ (("2016-08-14 20:00:12" | to_datetime) - ("2016-08-14 18:00:00" | to_datetime)).seconds }} + # This expression evaluates to "12" and not "132". Delta is 2 hours, 12 seconds + + # get amount of days between two dates. This returns only number of days and discards remaining hours, minutes, and seconds + {{ (("2016-08-14 20:00:12" | to_datetime) - ("2015-12-25" | to_datetime('%Y-%m-%d'))).days }} + +RETURN: + _value: + description: C(datetime) object from the represented value. + type: raw diff --git a/lib/ansible/plugins/filter/to_json.yml b/lib/ansible/plugins/filter/to_json.yml new file mode 100644 index 0000000..6f32d7c --- /dev/null +++ b/lib/ansible/plugins/filter/to_json.yml @@ -0,0 +1,69 @@ +DOCUMENTATION: + name: to_json + author: core team + version_added: 'historical' + short_description: Convert variable to JSON string + description: + - Converts an Ansible variable into a JSON string representation. + - This filter functions as a wrapper to the Python C(json.dumps) function. + - Ansible internally auto-converts JSON strings into variable structures so this plugin is used to force it into a JSON string. + options: + _input: + description: A variable or expression that returns a data structure. + type: raw + required: true + vault_to_text: + description: Toggle to either unvault a vault or create the JSON version of a vaulted object. + type: bool + default: True + version_added: '2.9' + preprocess_unsafe: + description: Toggle to represent unsafe values directly in JSON or create a unsafe object in JSON. + type: bool + default: True + version_added: '2.9' + allow_nan: + description: When C(False), strict adherence to float value limits of the JSON specifications, so C(nan), C(inf) and C(-inf) values will produce errors. + When C(True), JavaScript equivalents will be used (C(NaN), C(Infinity), C(-Infinity)). + default: True + type: bool + check_circular: + description: Controls the usage of the internal circular reference detection, if off can result in overflow errors. + default: True + type: bool + ensure_ascii: + description: Escapes all non ASCII characters. + default: True + type: bool + indent: + description: Number of spaces to indent Python structures, mainly used for display to humans. + default: 0 + type: integer + separators: + description: The C(item) and C(key) separator to be used in the serialized output, + default may change depending on I(indent) and Python version. + default: "(', ', ': ')" + type: tuple + skipkeys: + description: If C(True), keys that are not basic Python types will be skipped. + default: False + type: bool + sort_keys: + description: Affects sorting of dictionary keys. + default: False + type: bool + notes: + - Both I(vault_to_text) and I(preprocess_unsafe) defaulted to C(False) between Ansible 2.9 and 2.12. + - 'These parameters to C(json.dumps) will be ignored, as they are overriden internally: I(cls), I(default)' + +EXAMPLES: | + # dump variable in a template to create a JSON document + {{ docker_config|to_json }} + + # same as above but 'prettier' (equivalent to to_nice_json filter) + {{ docker_config|to_json(indent=4, sort_keys=True) }} + +RETURN: + _value: + description: The JSON serialized string representing the variable structure inputted. + type: string diff --git a/lib/ansible/plugins/filter/to_nice_json.yml b/lib/ansible/plugins/filter/to_nice_json.yml new file mode 100644 index 0000000..bedc18b --- /dev/null +++ b/lib/ansible/plugins/filter/to_nice_json.yml @@ -0,0 +1,54 @@ +DOCUMENTATION: + name: to_nice_json + author: core team + version_added: 'historical' + short_description: Convert variable to 'nicely formatted' JSON string + description: + - Converts an Ansible variable into a 'nicely formatted' JSON string representation + - This filter functions as a wrapper to the Python C(json.dumps) function. + - Ansible automatically converts JSON strings into variable structures so this plugin is used to forcibly retain a JSON string. + options: + _input: + description: A variable or expression that returns a data structure. + type: raw + required: true + vault_to_text: + description: Toggle to either unvault a vault or create the JSON version of a vaulted object. + type: bool + default: True + version_added: '2.9' + preprocess_unsafe: + description: Toggle to represent unsafe values directly in JSON or create a unsafe object in JSON. + type: bool + default: True + version_added: '2.9' + allow_nan: + description: When C(False), strict adherence to float value limits of the JSON specification, so C(nan), C(inf) and C(-inf) values will produce errors. + When C(True), JavaScript equivalents will be used (C(NaN), C(Infinity), C(-Infinity)). + default: True + type: bool + check_circular: + description: Controls the usage of the internal circular reference detection, if off can result in overflow errors. + default: True + type: bool + ensure_ascii: + description: Escapes all non ASCII characters. + default: True + type: bool + skipkeys: + description: If C(True), keys that are not basic Python types will be skipped. + default: False + type: bool + notes: + - Both I(vault_to_text) and I(preprocess_unsafe) defaulted to C(False) between Ansible 2.9 and 2.12. + - 'These parameters to C(json.dumps) will be ignored, they are overriden for internal use: I(cls), I(default), I(indent), I(separators), I(sort_keys).' + +EXAMPLES: | + # dump variable in a template to create a nicely formatted JSON document + {{ docker_config|to_nice_json }} + + +RETURN: + _value: + description: The 'nicely formatted' JSON serialized string representing the variable structure inputted. + type: string diff --git a/lib/ansible/plugins/filter/to_nice_yaml.yml b/lib/ansible/plugins/filter/to_nice_yaml.yml new file mode 100644 index 0000000..4677a86 --- /dev/null +++ b/lib/ansible/plugins/filter/to_nice_yaml.yml @@ -0,0 +1,39 @@ +DOCUMENTATION: + name: to_yaml + author: core team + version_added: 'historical' + short_description: Convert variable to YAML string + description: + - Converts an Ansible variable into a YAML string representation. + - This filter functions as a wrapper to the L(Python PyYAML library, https://pypi.org/project/PyYAML/)'s C(yaml.dump) function. + - Ansible internally auto-converts YAML strings into variable structures so this plugin is used to force it into a YAML string. + positional: _input + options: + _input: + description: A variable or expression that returns a data structure. + type: raw + required: true + indent: + description: Number of spaces to indent Python structures, mainly used for display to humans. + type: integer + sort_keys: + description: Affects sorting of dictionary keys. + default: True + type: bool + #allow_unicode: + # description: + # type: bool + # default: true + #default_style=None, canonical=None, width=None, line_break=None, encoding=None, explicit_start=None, explicit_end=None, version=None, tags=None + notes: + - More options may be available, see L(PyYAML documentation, https://pyyaml.org/wiki/PyYAMLDocumentation) for details. + - 'These parameters to C(yaml.dump) will be ignored, as they are overriden internally: I(default_flow_style)' + +EXAMPLES: | + # dump variable in a template to create a YAML document + {{ github_workflow | to_nice_yaml }} + +RETURN: + _value: + description: The YAML serialized string representing the variable structure inputted. + type: string diff --git a/lib/ansible/plugins/filter/to_uuid.yml b/lib/ansible/plugins/filter/to_uuid.yml new file mode 100644 index 0000000..266bf05 --- /dev/null +++ b/lib/ansible/plugins/filter/to_uuid.yml @@ -0,0 +1,30 @@ +DOCUMENTATION: + name: to_uuid + version_added: "2.9" + short_description: namespaced UUID generator + description: + - Use to generate namespeced Universal Unique ID. + positional: _input, namespace + options: + _input: + description: String to use as base fo the UUID. + type: str + required: true + namespace: + description: UUID namespace to use. + type: str + default: 361E6D51-FAEC-444A-9079-341386DA8E2E + +EXAMPLES: | + + # To create a namespaced UUIDv5 + uuid: "{{ string | to_uuid(namespace='11111111-2222-3333-4444-555555555555') }}" + + + # To create a namespaced UUIDv5 using the default Ansible namespace '361E6D51-FAEC-444A-9079-341386DA8E2E' + uuid: "{{ string | to_uuid }}" + +RETURN: + _value: + description: Generated UUID. + type: string diff --git a/lib/ansible/plugins/filter/to_yaml.yml b/lib/ansible/plugins/filter/to_yaml.yml new file mode 100644 index 0000000..2e7be60 --- /dev/null +++ b/lib/ansible/plugins/filter/to_yaml.yml @@ -0,0 +1,52 @@ +DOCUMENTATION: + name: to_yaml + author: core team + version_added: 'historical' + short_description: Convert variable to YAML string + description: + - Converts an Ansible variable into a YAML string representation. + - This filter functions as a wrapper to the L(Python PyYAML library, https://pypi.org/project/PyYAML/)'s C(yaml.dump) function. + - Ansible automatically converts YAML strings into variable structures so this plugin is used to forcibly retain a YAML string. + positional: _input + options: + _input: + description: A variable or expression that returns a data structure. + type: raw + required: true + indent: + description: Number of spaces to indent Python structures, mainly used for display to humans. + type: integer + sort_keys: + description: Affects sorting of dictionary keys. + default: True + type: bool + notes: + - More options may be available, see L(PyYAML documentation, https://pyyaml.org/wiki/PyYAMLDocumentation) for details. + + # TODO: find docs for these + #allow_unicode: + # description: + # type: bool + # default: true + #default_flow_style + #default_style + #canonical=None, + #width=None, + #line_break=None, + #encoding=None, + #explicit_start=None, + #explicit_end=None, + #version=None, + #tags=None + +EXAMPLES: | + # dump variable in a template to create a YAML document + {{ github_workflow |to_yaml}} + + # same as above but 'prettier' (equivalent to to_nice_yaml filter) + {{ docker_config|to_json(indent=4) }} + +RETURN: + _value: + description: The YAML serialized string representing the variable structure inputted. + type: string diff --git a/lib/ansible/plugins/filter/type_debug.yml b/lib/ansible/plugins/filter/type_debug.yml new file mode 100644 index 0000000..73f7946 --- /dev/null +++ b/lib/ansible/plugins/filter/type_debug.yml @@ -0,0 +1,20 @@ +DOCUMENTATION: + name: type_debug + author: Adrian Likins (@alikins) + version_added: "2.3" + short_description: show input data type + description: + - Returns the equivalent of Python's C(type) function. + options: + _input: + description: Variable or expression of which you want to determine type. + type: any + required: true +EXAMPLES: | + # get type of 'myvar' + {{ myvar | type_debug }} + +RETURN: + _value: + description: The Python 'type' of the I(_input) provided. + type: string diff --git a/lib/ansible/plugins/filter/union.yml b/lib/ansible/plugins/filter/union.yml new file mode 100644 index 0000000..d737900 --- /dev/null +++ b/lib/ansible/plugins/filter/union.yml @@ -0,0 +1,35 @@ +DOCUMENTATION: + name: union + author: Brian Coca (@bcoca) + version_added: "1.4" + short_description: union of lists + description: + - Provide a unique list of all the elements of two lists. + options: + _input: + description: A list. + type: list + required: true + _second_list: + description: A list. + type: list + required: true + seealso: + - plugin_type: filter + plugin: ansible.builtin.difference + - plugin_type: filter + plugin: ansible.builtin.intersect + - plugin_type: filter + plugin: ansible.builtin.symmetric_difference + - plugin_type: filter + plugin: ansible.builtin.unique +EXAMPLES: | + # return the unique elements of list1 added to list2 + # list1: [1, 2, 5, 1, 3, 4, 10] + # list2: [1, 2, 3, 4, 5, 11, 99] + {{ list1 | union(list2) }} + # => [1, 2, 5, 1, 3, 4, 10, 11, 99] +RETURN: + _value: + description: A unique list of all the elements from both lists. + type: list diff --git a/lib/ansible/plugins/filter/unique.yml b/lib/ansible/plugins/filter/unique.yml new file mode 100644 index 0000000..c627816 --- /dev/null +++ b/lib/ansible/plugins/filter/unique.yml @@ -0,0 +1,30 @@ +DOCUMENTATION: + name: unique + author: Brian Coca (@bcoca) + version_added: "1.4" + short_description: set of unique items of a list + description: + - Creates a list of unique elements (a set) from the provided input list. + options: + _input: + description: A list. + type: list + required: true + seealso: + - plugin_type: filter + plugin: ansible.builtin.difference + - plugin_type: filter + plugin: ansible.builtin.intersect + - plugin_type: filter + plugin: ansible.builtin.symmetric_difference + - plugin_type: filter + plugin: ansible.builtin.union +EXAMPLES: | + # return only the unique elements of list1 + # list1: [1, 2, 5, 1, 3, 4, 10] + {{ list1 | unique }} + # => [1, 2, 5, 3, 4, 10] +RETURN: + _value: + description: A list with unique elements, also known as a set. + type: list diff --git a/lib/ansible/plugins/filter/unvault.yml b/lib/ansible/plugins/filter/unvault.yml new file mode 100644 index 0000000..96a82ca --- /dev/null +++ b/lib/ansible/plugins/filter/unvault.yml @@ -0,0 +1,36 @@ +DOCUMENTATION: + name: unvault + author: Brian Coca (@bcoca) + version_added: "2.12" + short_description: Open an Ansible Vault + description: + - Retrieve your information from an encrypted Ansible Vault. + positional: secret + options: + _input: + description: Vault string, or an C(AnsibleVaultEncryptedUnicode) string object. + type: string + required: true + secret: + description: Vault secret, the key that lets you open the vault. + type: string + required: true + vault_id: + description: Secret identifier, used internally to try to best match a secret when multiple are provided. + type: string + default: 'filter_default' + +EXAMPLES: | + # simply decrypt my key from a vault + vars: + mykey: "{{ myvaultedkey|unvault(passphrase) }} " + + - name: save templated unvaulted data + template: src=dump_template_data.j2 dest=/some/key/clear.txt + vars: + template_data: '{{ secretdata|uvault(vaultsecret) }}' + +RETURN: + _value: + description: The string that was contained in the vault. + type: string diff --git a/lib/ansible/plugins/filter/urldecode.yml b/lib/ansible/plugins/filter/urldecode.yml new file mode 100644 index 0000000..dd76937 --- /dev/null +++ b/lib/ansible/plugins/filter/urldecode.yml @@ -0,0 +1,48 @@ +DOCUMENTATION: + name: urlsplit + version_added: "2.4" + short_description: get components from URL + description: + - Split a URL into its component parts. + positional: _input, query + options: + _input: + description: URL string to split. + type: str + required: true + query: + description: Specify a single component to return. + type: str + choices: ["fragment", "hostname", "netloc", "password", "path", "port", "query", "scheme", "username"] + +RETURN: + _value: + description: + - A dictionary with components as keyword and their value. + - If I(query) is provided, a string or integer will be returned instead, depending on I(query). + type: any + +EXAMPLES: | + + {{ "http://user:password@www.acme.com:9000/dir/index.html?query=term#fragment" | urlsplit }} + # => + # { + # "fragment": "fragment", + # "hostname": "www.acme.com", + # "netloc": "user:password@www.acme.com:9000", + # "password": "password", + # "path": "/dir/index.html", + # "port": 9000, + # "query": "query=term", + # "scheme": "http", + # "username": "user" + # } + + {{ "http://user:password@www.acme.com:9000/dir/index.html?query=term#fragment" | urlsplit('hostname') }} + # => 'www.acme.com' + + {{ "http://user:password@www.acme.com:9000/dir/index.html?query=term#fragment" | urlsplit('query') }} + # => 'query=term' + + {{ "http://user:password@www.acme.com:9000/dir/index.html?query=term#fragment" | urlsplit('path') }} + # => '/dir/index.html' diff --git a/lib/ansible/plugins/filter/urls.py b/lib/ansible/plugins/filter/urls.py new file mode 100644 index 0000000..fb7abc6 --- /dev/null +++ b/lib/ansible/plugins/filter/urls.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2012, Dag Wieers (@dagwieers) <dag@wieers.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from functools import partial + +from urllib.parse import unquote_plus + + +class FilterModule(object): + ''' Ansible core jinja2 filters ''' + + def filters(self): + return { + 'urldecode': partial(unquote_plus), + } diff --git a/lib/ansible/plugins/filter/urlsplit.py b/lib/ansible/plugins/filter/urlsplit.py new file mode 100644 index 0000000..cce54bb --- /dev/null +++ b/lib/ansible/plugins/filter/urlsplit.py @@ -0,0 +1,87 @@ +# Copyright (c) 2017 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = r''' + name: urlsplit + version_added: "2.4" + short_description: get components from URL + description: + - Split a URL into its component parts. + positional: _input, query + options: + _input: + description: URL string to split. + type: str + required: true + query: + description: Specify a single component to return. + type: str + choices: ["fragment", "hostname", "netloc", "password", "path", "port", "query", "scheme", "username"] +''' + +EXAMPLES = r''' + + parts: '{{ "http://user:password@www.acme.com:9000/dir/index.html?query=term#fragment" | urlsplit }}' + # => + # { + # "fragment": "fragment", + # "hostname": "www.acme.com", + # "netloc": "user:password@www.acme.com:9000", + # "password": "password", + # "path": "/dir/index.html", + # "port": 9000, + # "query": "query=term", + # "scheme": "http", + # "username": "user" + # } + + hostname: '{{ "http://user:password@www.acme.com:9000/dir/index.html?query=term#fragment" | urlsplit("hostname") }}' + # => 'www.acme.com' + + query: '{{ "http://user:password@www.acme.com:9000/dir/index.html?query=term#fragment" | urlsplit("query") }}' + # => 'query=term' + + path: '{{ "http://user:password@www.acme.com:9000/dir/index.html?query=term#fragment" | urlsplit("path") }}' + # => '/dir/index.html' +''' + +RETURN = r''' + _value: + description: + - A dictionary with components as keyword and their value. + - If I(query) is provided, a string or integer will be returned instead, depending on I(query). + type: any +''' + +from urllib.parse import urlsplit + +from ansible.errors import AnsibleFilterError +from ansible.utils import helpers + + +def split_url(value, query='', alias='urlsplit'): + + results = helpers.object_to_dict(urlsplit(value), exclude=['count', 'index', 'geturl', 'encode']) + + # If a query is supplied, make sure it's valid then return the results. + # If no option is supplied, return the entire dictionary. + if query: + if query not in results: + raise AnsibleFilterError(alias + ': unknown URL component: %s' % query) + return results[query] + else: + return results + + +# ---- Ansible filters ---- +class FilterModule(object): + ''' URI filter ''' + + def filters(self): + return { + 'urlsplit': split_url + } diff --git a/lib/ansible/plugins/filter/vault.yml b/lib/ansible/plugins/filter/vault.yml new file mode 100644 index 0000000..1ad541e --- /dev/null +++ b/lib/ansible/plugins/filter/vault.yml @@ -0,0 +1,48 @@ +DOCUMENTATION: + name: vault + author: Brian Coca (@bcoca) + version_added: "2.12" + short_description: vault your secrets + description: + - Put your information into an encrypted Ansible Vault. + positional: secret + options: + _input: + description: Data to vault. + type: string + required: true + secret: + description: Vault secret, the key that lets you open the vault. + type: string + required: true + salt: + description: + - Encryption salt, will be random if not provided. + - While providing one makes the resulting encrypted string reproducible, it can lower the security of the vault. + type: string + vault_id: + description: Secret identifier, used internally to try to best match a secret when multiple are provided. + type: string + default: 'filter_default' + wrap_object: + description: + - This toggle can force the return of an C(AnsibleVaultEncryptedUnicode) string object, when C(False), you get a simple string. + - Mostly useful when combining with the C(to_yaml) filter to output the 'inline vault' format. + type: bool + default: False + +EXAMPLES: | + # simply encrypt my key in a vault + vars: + myvaultedkey: "{{ keyrawdata|vault(passphrase) }} " + + - name: save templated vaulted data + template: src=dump_template_data.j2 dest=/some/key/vault.txt + vars: + mysalt: '{{2**256|random(seed=inventory_hostname)}}' + template_data: '{{ secretdata|vault(vaultsecret, salt=mysalt) }}' + +RETURN: + _value: + description: The vault string that contains the secret data (or C(AnsibleVaultEncryptedUnicode) string object). + type: string diff --git a/lib/ansible/plugins/filter/win_basename.yml b/lib/ansible/plugins/filter/win_basename.yml new file mode 100644 index 0000000..f89baa5 --- /dev/null +++ b/lib/ansible/plugins/filter/win_basename.yml @@ -0,0 +1,24 @@ +DOCUMENTATION: + name: win_basename + author: ansible core team + version_added: "2.0" + short_description: Get a Windows path's base name + description: + - Returns the last name component of a Windows path, what is left in the string that is not 'win_dirname'. + options: + _input: + description: A Windows path. + type: str + required: true + seealso: + - plugin_type: filter + plugin: ansible.builtin.win_dirname +EXAMPLES: | + + # To get the last name of a file Windows path, like 'foo.txt' out of 'C:\Users\asdf\foo.txt' + {{ mypath | win_basename }} + +RETURN: + _value: + description: The base name from the Windows path provided. + type: str diff --git a/lib/ansible/plugins/filter/win_dirname.yml b/lib/ansible/plugins/filter/win_dirname.yml new file mode 100644 index 0000000..dbc85c7 --- /dev/null +++ b/lib/ansible/plugins/filter/win_dirname.yml @@ -0,0 +1,24 @@ +DOCUMENTATION: + name: win_dirname + author: ansible core team + version_added: "2.0" + short_description: Get a Windows path's directory + description: + - Returns the directory component of a Windows path, what is left in the string that is not 'win_basename'. + options: + _input: + description: A Windows path. + type: str + required: true + seealso: + - plugin_type: filter + plugin: ansible.builtin.win_basename +EXAMPLES: | + + # To get the last name of a file Windows path, like 'C:\users\asdf' out of 'C:\Users\asdf\foo.txt' + {{ mypath | win_dirname }} + +RETURN: + _value: + description: The directory from the Windows path provided. + type: str diff --git a/lib/ansible/plugins/filter/win_splitdrive.yml b/lib/ansible/plugins/filter/win_splitdrive.yml new file mode 100644 index 0000000..828d1dd --- /dev/null +++ b/lib/ansible/plugins/filter/win_splitdrive.yml @@ -0,0 +1,29 @@ +DOCUMENTATION: + name: win_splitdrive + author: ansible core team + version_added: "2.0" + short_description: Split a Windows path by the drive letter + description: + - Returns a list with the first component being the drive letter and the second, the rest of the path. + options: + _input: + description: A Windows path. + type: str + required: true + +EXAMPLES: | + + # To get the last name of a file Windows path, like ['C', '\Users\asdf\foo.txt'] out of 'C:\Users\asdf\foo.txt' + {{ mypath | win_splitdrive }} + + # just the drive letter + {{ mypath | win_splitdrive | first }} + + # path w/o drive letter + {{ mypath | win_splitdrive | last }} + +RETURN: + _value: + description: List in which the first element is the drive letter and the second the rest of the path. + type: list + elements: str diff --git a/lib/ansible/plugins/filter/zip.yml b/lib/ansible/plugins/filter/zip.yml new file mode 100644 index 0000000..20d7a9b --- /dev/null +++ b/lib/ansible/plugins/filter/zip.yml @@ -0,0 +1,43 @@ +DOCUMENTATION: + name: zip + version_added: "2.3" + short_description: combine list elements + positional: _input, _additional_lists + description: Iterate over several iterables in parallel, producing tuples with an item from each one. + notes: + - This is mostly a passhtrough to Python's C(zip) function. + options: + _input: + description: Original list. + type: list + elements: any + required: yes + _additional_lists: + description: Additional list(s). + type: list + elements: any + required: yes + strict: + description: If C(True) return an error on mismatching list length, otherwise shortest list determines output. + type: bool + default: no + +EXAMPLES: | + + # two => [[1, "a"], [2, "b"], [3, "c"], [4, "d"], [5, "e"], [6, "f"]] + two: "{{ [1,2,3,4,5,6] | zip(['a','b','c','d','e','f']) }}" + + # three => [ [ 1, "a", "d" ], [ 2, "b", "e" ], [ 3, "c", "f" ] ] + three: "{{ [1,2,3] | zip(['a','b','c'], ['d','e','f']) }}" + + # shorter => [[1, "a"], [2, "b"], [3, "c"]] + shorter: "{{ [1,2,3] | zip(['a','b','c','d','e','f']) }}" + + # compose dict from lists of keys and values + mydcit: "{{ dict(keys_list | zip(values_list)) }}" + +RETURN: + _value: + description: List of lists made of elements matching the positions of the input lists. + type: list + elements: list diff --git a/lib/ansible/plugins/filter/zip_longest.yml b/lib/ansible/plugins/filter/zip_longest.yml new file mode 100644 index 0000000..db351b4 --- /dev/null +++ b/lib/ansible/plugins/filter/zip_longest.yml @@ -0,0 +1,36 @@ +DOCUMENTATION: + name: zip_longest + version_added: "2.3" + short_description: combine list elements, with filler + positional: _input, _additional_lists + description: + - Make an iterator that aggregates elements from each of the iterables. + If the iterables are of uneven length, missing values are filled-in with I(fillvalue). + Iteration continues until the longest iterable is exhausted. + notes: + - This is mostly a passhtrough to Python's C(itertools.zip_longest) function + options: + _input: + description: Original list. + type: list + elements: any + required: yes + _additional_lists: + description: Additional list(s). + type: list + elements: any + required: yes + fillvalue: + description: Filler value to add to output when one of the lists does not contain enough elements to match the others. + type: any + +EXAMPLES: | + + # X_fill => [[1, "a", 21], [2, "b", 22], [3, "c", 23], ["X", "d", "X"], ["X", "e", "X"], ["X", "f", "X"]] + X_fill: "{{ [1,2,3] | zip_longest(['a','b','c','d','e','f'], [21, 22, 23], fillvalue='X') }}" + +RETURN: + _value: + description: List of lists made of elements matching the positions of the input lists. + type: list + elements: list |