前言:
filter_plugins是什么? 这个单词拆解下,filter ! 熟悉jinja2模板的人,到知道他是过滤器,可以在模板中以管道的方式用pyhton的代码处理字符串。 ansible模板调用的是jinja2,这个大家都知道 。 这个filter_plugins插件代码,就是为了更好的处理jinja2模板中的字符串和逻辑判断的。 先前,我和沈灿讨论一个模板的问题,实在蛋疼的要命,总是达不到我们要的数据,当时是做一个数据的统计和rabbitmq的配置,有些地方用jinja2模板自身的语法,处理起来实在是费劲, 用了多级{% set %}和各种length 。 其实一开始我就想用jinja2的filter,结果没看到相应的文档说明。 原来还计划着,在纠结下ansible template的代码,把jinja2的过滤器搞进去,要是不成功,自己外置一个api做处理。 看来,ansible那帮人,把能想到的东西都给做了。
这两天又看了下文档,发现讲插件的list里面有个filter_plugins,然后看了下说明,果然是他 !
尼玛,爬虫! 注释下原文的地址,blog.xiaorui.cc
ansible支持jinja2中默认的内置过滤器用法的,这些是一部分 ! 具体的每个功能我就不详细说了,大家自己测测就知道用途了。
abs(number) 绝对值 attr(obj, name) 属性 {{ my_variable|default('my_variable is not defined') }} 如果没有值,可以定义默认的 {% for item in mydict|dictsort %} sort the dict by key, case insensitive {% for item in mydict|dictsort(true) %} sort the dict by key, case sensitive {% for item in mydict|dictsort(false, 'value') %} sort the dict by key, case insensitive, sorted normally and ordered by value. escape(s) 安全的代码模式 first(seq) 第一个 float(value, default=0.0) 浮点型 forceescape(value) 强制html转义 indent(s, width=4, indentfirst=False) {{ mytext|indent(2, true) }} {{ [1, 2, 3]|join('|') }} -> 1|2|3 {{ [1, 2, 3]|join }} -> 123 {{ users|join(', ', attribute='username') }} last(seq) Return the last item of a sequence. length(object) Return the number of items of a sequence or mapping. lower(s) Convert a value to lowercase. random(seq) Return a random item from the sequence. reject() Filters a sequence of objects by appying a test to the object and rejecting the ones with the test succeeding. Example usage: {{ numbers|reject("odd") }} New in version 2.7. rejectattr() Filters a sequence of objects by appying a test to an attribute of an object or the attribute and rejecting the ones with the test succeeding. {{ users|rejectattr("is_active") }} {{ users|rejectattr("email", "none") }} New in version 2.7. replace(s, old, new, count=None) {{ "Hello World"|replace("Hello", "Goodbye") }} -> Goodbye World {{ "aaaaargh"|replace("a", "d'oh, ", 2) }} -> d'oh, d'oh, aaargh round(value, precision=0, method='common') {{ 42.55|round }} -> 43.0 {{ 42.55|round(1, 'floor') }} -> 42.5 Note that even if rounded to 0 precision, a float is returned. If you need a real integer, pipe it through int: {{ 42.55|round|int }} -> 43 safe(value) Mark the value as safe which means that in an environment with automatic escaping enabled this variable will not be escaped. select() Filters a sequence of objects by appying a test to the object and only selecting the ones with the test succeeding. Example usage: {{ numbers|select("odd") }} {{ numbers|select("odd") }} New in version 2.7. selectattr() Example usage: {{ users|selectattr("is_active") }} {{ users|selectattr("email", "none") }} {% for item in iterable|sort %} ... {% endfor %} It is also possible to sort by an attribute (for example to sort by the date of an object) by specifying the attribute parameter: {% for item in iterable|sort(attribute='date') %} ... {% endfor %} Changed in version 2.6: The attribute parameter was added. string(object) Make a string unicode if it isn’t already. That way a markup string is not converted back to unicode. upper(s) Convert a value to uppercase. urlencode(value) Escape strings for use in URLs (uses UTF-8 encoding). It accepts both dictionaries and regular strings as well as pairwise iterables. wordcount(s) 个数
下面是实现自定义的jinja2 filter的代码。 里面已经实现了调用ansible的template的时候,有可能会用到的filter过滤器。
#xiaorui.cc import base64 import json import os.path import yaml import types import pipes import glob import re import operator as py_operator from ansible import errors from ansible.utils import md5s from distutils.version import LooseVersion, StrictVersion from random import SystemRandom from jinja2.filters import environmentfilter def to_nice_yaml(*a, **kw): '''Make verbose, human readable yaml''' return yaml.safe_dump(*a, indent=4, allow_unicode=True, default_flow_style=False, **kw) def to_json(a, *args, **kw): ''' Convert the value to JSON ''' return json.dumps(a, *args, **kw) def to_nice_json(a, *args, **kw): '''Make verbose, human readable JSON''' return json.dumps(a, indent=4, sort_keys=True, *args, **kw) def failed(*a, **kw): ''' Test if task result yields failed ''' item = a[0] if type(item) != dict: raise errors.AnsibleFilterError("|failed expects a dictionary") rc = item.get('rc',0) failed = item.get('failed',False) if rc != 0 or failed: return True else: return False def success(*a, **kw): ''' Test if task result yields success ''' return not failed(*a, **kw) def changed(*a, **kw): ''' Test if task result yields changed ''' item = a[0] if type(item) != dict: raise errors.AnsibleFilterError("|changed expects a dictionary") if not 'changed' in item: changed = False if ('results' in item # some modules return a 'results' key and type(item['results']) == list and type(item['results'][0]) == dict): for result in item['results']: changed = changed or result.get('changed', False) else: changed = item.get('changed', False) return changed def skipped(*a, **kw): ''' Test if task result yields skipped ''' item = a[0] if type(item) != dict: raise errors.AnsibleFilterError("|skipped expects a dictionary") skipped = item.get('skipped', False) return skipped def mandatory(a): ''' Make a variable mandatory ''' try: a except NameError: raise errors.AnsibleFilterError('Mandatory variable not defined.') else: return a def bool(a): ''' return a bool for the arg ''' if a is None or type(a) == bool: return a if type(a) in types.StringTypes: a = a.lower() if a in ['yes', 'on', '1', 'true', 1]: return True else: return False def quote(a): ''' return its argument quoted for shell usage ''' return pipes.quote(a) def fileglob(pathname): ''' return list of matched files for glob ''' return glob.glob(pathname) def regex(value='', pattern='', ignorecase=False, match_type='search'): ''' Expose `re` as a boolean filter using the `search` method by default. This is likely only useful for `search` and `match` which already have their own filters. ''' if ignorecase: flags = re.I else: flags = 0 _re = re.compile(pattern, flags=flags) _bool = __builtins__.get('bool') return _bool(getattr(_re, match_type, 'search')(value)) def match(value, pattern='', ignorecase=False): ''' Perform a `re.match` returning a boolean ''' return regex(value, pattern, ignorecase, 'match') def search(value, pattern='', ignorecase=False): ''' Perform a `re.search` returning a boolean ''' return regex(value, pattern, ignorecase, 'search') def regex_replace(value='', pattern='', replacement='', ignorecase=False): ''' Perform a `re.sub` returning a string ''' if not isinstance(value, basestring): value = str(value) if ignorecase: flags = re.I else: flags = 0 _re = re.compile(pattern, flags=flags) return _re.sub(replacement, value) def unique(a): return set(a) def intersect(a, b): return set(a).intersection(b) def difference(a, b): return set(a).difference(b) def symmetric_difference(a, b): return set(a).symmetric_difference(b) def union(a, b): return set(a).union(b) 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, e: raise errors.AnsibleFilterError('Version comparison: %s' % e) @environmentfilter def rand(environment, end, start=None, step=None): r = SystemRandom() if isinstance(end, (int, long)): 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 errors.AnsibleFilterError('start and step can only be used with integer values') return r.choice(end) else: raise errors.AnsibleFilterError('random can only be used on sequences and integers') class FilterModule(object): ''' Ansible core jinja2 filters ''' def filters(self): return { # base 64 'b64decode': base64.b64decode, 'b64encode': base64.b64encode, # json 'to_json': to_json, 'to_nice_json': to_nice_json, 'from_json': json.loads, # yaml 'to_yaml': yaml.safe_dump, 'to_nice_yaml': to_nice_yaml, 'from_yaml': yaml.safe_load, # path 'basename': os.path.basename, 'dirname': os.path.dirname, 'expanduser': os.path.expanduser, 'realpath': os.path.realpath, 'relpath': os.path.relpath, # failure testing 'failed' : failed, 'success' : success, # changed testing 'changed' : changed, # skip testing 'skipped' : skipped, # variable existence 'mandatory': mandatory, # value as boolean 'bool': bool, # quote string for shell usage 'quote': quote, # md5 hex digest of string 'md5': md5s, # file glob 'fileglob': fileglob, # regex 'match': match, 'search': search, 'regex': regex, 'regex_replace': regex_replace, # list 'unique' : unique, 'intersect': intersect, 'difference': difference, 'symmetric_difference': symmetric_difference, 'union': union, # version comparison 'version_compare': version_compare, # random numbers 'random': rand, }
模板的代码,这里只是做个测试而已。模板里面的变量不是从外面引入的,是我自己设的变量。
this is ceshi {% set list1 = [1,2,3,4,5,6,7,8,9,10] %} {% for i in list1 %} {{ i }} {% endfor %} {{ list1|to_nice_yaml }} {% set list2 = ['k_1','k_2','k_3'] %} to_replace ..... {% for i in list2 %} {{ i|to_replace }} {% endfor %}
这里是结果,可以看到咱们搞的那两个过滤器成功执行了。
尼玛,爬虫! 原文地址,blog.xiaorui.cc
ansible有个filter_plugins插件利器,在使用jinja2模板渲染数据的时候,就更加的方便了 !!!