ansible插件之filter_plugins实现jinja2自定义filter过滤器

前言:

   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 %}



wKioL1PL7xnAJwxpAAMl37ayClo826.jpg

这里是结果,可以看到咱们搞的那两个过滤器成功执行了。 

wKioL1PL7yOyvBxdAAJylh0LJAQ496.jpg

尼玛,爬虫! 原文地址,blog.xiaorui.cc

ansible有个filter_plugins插件利器,在使用jinja2模板渲染数据的时候,就更加的方便了 !!!


大家觉得文章对你有些作用! 如果想赏钱,可以用微信扫描下面的二维码,感谢!
另外再次标注博客原地址  xiaorui.cc

发表评论

邮箱地址不会被公开。 必填项已用*标注