diff --git a/docsite/rst/playbooks.rst b/docsite/rst/playbooks.rst index 5e1c1b98695..de8072cb312 100644 --- a/docsite/rst/playbooks.rst +++ b/docsite/rst/playbooks.rst @@ -22,6 +22,7 @@ It is recommended to look at `Example Playbooks =') }} - -If ``ansible_distribution_version`` is greater than or equal to 12, this filter will return True, otherwise it will return False. - -The ``version_compare`` filter accepts the following operators:: - - <, lt, <=, le, >, gt, >=, ge, ==, =, eq, !=, <>, ne - -This filter also accepts a 3rd parameter, ``strict`` which defines if strict version parsing should -be used. The default is ``False``, and if set as ``True`` will use more strict version parsing:: - - {{ sample_version_var | version_compare('1.0', operator='lt', strict=True) }} .. _random_filter: @@ -245,10 +192,6 @@ Math .. versionadded:: 1.9 -To see if something is actually a number:: - - {{ myvar | isnan }} - Get the logarithm (default is e):: {{ myvar | log }} @@ -539,20 +482,6 @@ doesn't know it is a boolean value:: - debug: msg=test when: some_string_value | bool -To match strings against a regex, use the "match" or "search" filter:: - - vars: - url: "http://example.com/users/foo/resources/bar" - - tasks: - - shell: "msg='matched pattern 1'" - when: url | match("http://example.com/users/.*/resources/.*") - - - debug: "msg='matched pattern 2'" - when: url | search("/users/.*/resources/.*") - -'match' will require a complete match in the string, while 'search' will require a match inside of the string. - .. versionadded:: 1.6 To replace text in a string with regex, use the "regex_replace" filter:: diff --git a/docsite/rst/playbooks_tests.rst b/docsite/rst/playbooks_tests.rst new file mode 100644 index 00000000000..c341268d8a0 --- /dev/null +++ b/docsite/rst/playbooks_tests.rst @@ -0,0 +1,165 @@ +Jinja2 tests +============ + +.. contents:: Topics + + +Tests in Jinja2 are a way of evaluating template expressions and returning True or False. +Jinja2 ships with many of these. See `builtin tests`_ in the official Jinja2 template documentation. +Tests are very similar to filters and are used mostly the same way, but they can also be used in list +processing filters, like C(map()) and C(select()) to choose items in the list. + +Like filters, tests always execute on the Ansible controller, **not** on the target of a task, as they test local data. + +In addition to those Jinja2 tests, Ansible supplies a few more and users can easily create their own. + +.. _testing_strings: + +Testing strings +--------------- + +To match strings against a substring or a regex, use the "match" or "search" filter:: + + vars: + url: "http://example.com/users/foo/resources/bar" + + tasks: + - shell: "msg='matched pattern 1'" + when: url | match("http://example.com/users/.*/resources/.*") + + - debug: "msg='matched pattern 2'" + when: url | search("/users/.*/resources/.*") + + - debug: "msg='matched pattern 3'" + when: url | search("/users/") + +'match' requires a complete match in the string, while 'search' only requires matching a subset of the string. + + +.. _testing_versions: + +Version Comparison +------------------ + +.. versionadded:: 1.6 + +To compare a version number, such as checking if the ``ansible_distribution_version`` +version is greater than or equal to '12.04', you can use the ``version_compare`` filter. + +The ``version_compare`` filter can also be used to evaluate the ``ansible_distribution_version``:: + + {{ ansible_distribution_version | version_compare('12.04', '>=') }} + +If ``ansible_distribution_version`` is greater than or equal to 12, this filter returns True, otherwise False. + +The ``version_compare`` filter accepts the following operators:: + + <, lt, <=, le, >, gt, >=, ge, ==, =, eq, !=, <>, ne + +This test also accepts a 3rd parameter, ``strict`` which defines if strict version parsing should +be used. The default is ``False``, but this setting as ``True`` uses more strict version parsing:: + + {{ sample_version_var | version_compare('1.0', operator='lt', strict=True) }} + + +.. _math_tests: + +Group theory tests +------------------ + +To see if a list includes or is included by another list, you can use 'issubset' and 'issuperset':: + + vars: + a: [1,2,3,4,5] + b: [2,3] + tasks: + - debug: msg="A includes B" + when: a|issuperset(b) + + - debug: msg="B is included in A" + when: b|issubset(a) + + +.. _path_tests: + +Testing paths +------------- + +The following tests can provide information about a path on the controller:: + + - debug: msg="path is a directory" + when: mypath|isdir + + - debug: msg="path is a file" + when: mypath|is_file + + - debug: msg="path is a symlink" + when: mypath|is_link + + - debug: msg="path already exists" + when: mypath|exists + + - debug: msg="path is {{ (mypath|is_abs)|ternary('absolute','relative')}}" + + - debug: msg="path is the same file as path2" + when: mypath|samefile(path2) + + - debug: msg="path is a mount" + when: mypath|ismount + + +.. _test_task_results: + +Task results +------------ + +The following tasks are illustrative of the tests meant to check the status of tasks:: + + tasks: + + - shell: /usr/bin/foo + register: result + ignore_errors: True + + - debug: msg="it failed" + when: result|failed + + # in most cases you'll want a handler, but if you want to do something right now, this is nice + - debug: msg="it changed" + when: result|changed + + - debug: msg="it succeeded in Ansible >= 2.1" + when: result|succeeded + + - debug: msg="it succeeded" + when: result|success + + - debug: msg="it was skipped" + when: result|skipped + +.. note:: From 2.1, you can also use success, failure, change, and skip so that the grammar matches, for those who need to be strict about it. + + + +.. _builtin tests: http://jinja.pocoo.org/docs/templates/#builtin-tests + +.. seealso:: + + :doc:`playbooks` + An introduction to playbooks + :doc:`playbooks_conditionals` + Conditional statements in playbooks + :doc:`playbooks_variables` + All about variables + :doc:`playbooks_loops` + Looping in playbooks + :doc:`playbooks_roles` + Playbook organization by roles + :doc:`playbooks_best_practices` + Best practices in playbooks + `User Mailing List `_ + Have a question? Stop by the google group! + `irc.freenode.net `_ + #ansible IRC chat channel + + diff --git a/lib/ansible/plugins/filter/core.py b/lib/ansible/plugins/filter/core.py index 9dc01042f25..25961b4837a 100644 --- a/lib/ansible/plugins/filter/core.py +++ b/lib/ansible/plugins/filter/core.py @@ -26,7 +26,6 @@ import itertools import json import os.path import ntpath -import types import pipes import glob import re @@ -34,13 +33,11 @@ import crypt import hashlib import string from functools import partial -import operator as py_operator from random import SystemRandom, shuffle import uuid import yaml from jinja2.filters import environmentfilter -from distutils.version import LooseVersion, StrictVersion from ansible.compat.six import iteritems, string_types from ansible import errors @@ -186,32 +183,6 @@ def ternary(value, true_val, false_val): return false_val -def version_compare(value, version, operator='eq', strict=False): - ''' Perform a version comparison on a value ''' - op_map = { - '==': 'eq', '=': 'eq', 'eq': 'eq', - '<': 'lt', 'lt': 'lt', - '<=': 'le', 'le': 'le', - '>': 'gt', 'gt': 'gt', - '>=': 'ge', 'ge': 'ge', - '!=': 'ne', '<>': 'ne', 'ne': 'ne' - } - - if strict: - Version = StrictVersion - else: - Version = LooseVersion - - if operator in op_map: - operator = op_map[operator] - else: - raise errors.AnsibleFilterError('Invalid operator type') - - try: - method = getattr(py_operator, operator) - return method(Version(str(value)), Version(str(version))) - except Exception as e: - raise errors.AnsibleFilterError('Version comparison: %s' % e) def regex_escape(string): '''Escape all regular expressions special characters from STRING.''' @@ -261,7 +232,6 @@ def get_encrypted_password(password, hashtype='sha512', salt=None): 'sha512': '6', } - hastype = hashtype.lower() if hashtype in cryptmethod: if salt is None: r = SystemRandom() @@ -461,9 +431,6 @@ class FilterModule(object): 'ternary': ternary, # list - # version comparison - 'version_compare': version_compare, - # random stuff 'random': rand, 'shuffle': randomize_list, diff --git a/lib/ansible/plugins/filter/ipaddr.py b/lib/ansible/plugins/filter/ipaddr.py index 4afe4222dc8..23c3c7182b8 100644 --- a/lib/ansible/plugins/filter/ipaddr.py +++ b/lib/ansible/plugins/filter/ipaddr.py @@ -585,7 +585,6 @@ def nthhost(value, query=''): return False try: - vsize = ipaddr(v, 'size') nth = int(query) if value.size > nth: return value[nth] diff --git a/lib/ansible/plugins/filter/mathstuff.py b/lib/ansible/plugins/filter/mathstuff.py index 599d9982d7b..f18e3baf0e0 100644 --- a/lib/ansible/plugins/filter/mathstuff.py +++ b/lib/ansible/plugins/filter/mathstuff.py @@ -70,12 +70,6 @@ def max(a): _max = __builtins__.get('max') return _max(a); -def isnotanumber(x): - try: - return math.isnan(x) - except TypeError: - return False - def logarithm(x, base=math.e): try: @@ -136,7 +130,6 @@ class FilterModule(object): def filters(self): return { # general math - 'isnan': isnotanumber, 'min' : min, 'max' : max, diff --git a/lib/ansible/plugins/test/core.py b/lib/ansible/plugins/test/core.py index 93b65b9481a..b6acadc80d2 100644 --- a/lib/ansible/plugins/test/core.py +++ b/lib/ansible/plugins/test/core.py @@ -20,6 +20,9 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type import re +import operator as py_operator +from distutils.version import LooseVersion, StrictVersion + from ansible import errors def failed(*a, **kw): @@ -84,6 +87,33 @@ def search(value, pattern='', ignorecase=False, multiline=False): ''' Perform a `re.search` returning a boolean ''' return regex(value, pattern, ignorecase, multiline, 'search') +def version_compare(value, version, operator='eq', strict=False): + ''' Perform a version comparison on a value ''' + op_map = { + '==': 'eq', '=': 'eq', 'eq': 'eq', + '<': 'lt', 'lt': 'lt', + '<=': 'le', 'le': 'le', + '>': 'gt', 'gt': 'gt', + '>=': 'ge', 'ge': 'ge', + '!=': 'ne', '<>': 'ne', 'ne': 'ne' + } + + if strict: + Version = StrictVersion + else: + Version = LooseVersion + + if operator in op_map: + operator = op_map[operator] + else: + raise errors.AnsibleFilterError('Invalid operator type') + + try: + method = getattr(py_operator, operator) + return method(Version(str(value)), Version(str(version))) + except Exception as e: + raise errors.AnsibleFilterError('Version comparison: %s' % e) + class TestModule(object): ''' Ansible core jinja2 tests ''' @@ -108,4 +138,7 @@ class TestModule(object): 'search': search, 'regex': regex, + # version comparison + 'version_compare': version_compare, + } diff --git a/lib/ansible/plugins/test/mathstuff.py b/lib/ansible/plugins/test/mathstuff.py index 54b33b70b6d..c736bbe6b66 100644 --- a/lib/ansible/plugins/test/mathstuff.py +++ b/lib/ansible/plugins/test/mathstuff.py @@ -18,12 +18,20 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type +import math + def issubset(a, b): return set(a) <= set(b) def issuperset(a, b): return set(a) >= set(b) +def isnotanumber(x): + try: + return math.isnan(x) + except TypeError: + return False + class TestModule: ''' Ansible math jinja2 tests ''' @@ -32,4 +40,5 @@ class TestModule: # set theory 'issubset': issubset, 'issuperset': issuperset, + 'isnan': isnotanumber, }