On Wed, 24 Feb 2016, Brian Coca wrote:

Dag, yes please! We've been meaning to write that but have never had the
time.

Not sure how to proceed next, but let me quickly show what I did for the filetree plugin. Since we have a need to run the existing playbooks with v1.9.4, but also test new stuff with v2.0.0.2 we have a need for a hybrid plugin.

The original v2.0 filetree plugin is available from:

    https://github.com/ansible/ansible/pull/14332

And the v1.9 filetree plugin is available from:

    https://github.com/ansible/ansible/pull/14628


The resulting hybrid lookup plugin required the following changes:

  - We need an implementation of LookupBase class on v1, with a custom
    v1 __init__()

  - In v1 we get the basedir from runner through __init__

  - Since we use the warning infrastructure, we need to have it imported
    from the right location (different from v1 and v2)

  - v1 also needs to import specialized functions, and we use the
    existence of these fuctions in globals() as the condition to see
    whether we are doing v1 or v2

  - We modified v1 LookupBase __init__() so it behaves a bit as v2
    LookupBase for our purpose, so the calls would be identical (we could
    have done something similar for other v1 functions, but prefered to
    stick as closely to v2 as possible, and consider v1 the exception as
    much as possible.

Below is the hybrid filetree plugin. Feedback is much appreciated:

--------
# (c) 2016 Dag Wieers <[email protected]>
#
# 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/>.
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

import os
import glob
import pwd
import grp

from ansible.errors import AnsibleError

HAVE_SELINUX=False
try:
    import selinux
    HAVE_SELINUX=True
except ImportError:
    pass

try:
    # Ansible v2
    from ansible.plugins.lookup import LookupBase
except ImportError:
    # Ansible v1
    class LookupBase(object):
        def __init__(self, basedir=None, runner=None, **kwargs):
            if runner:
                self.runner = runner
                self.basedir = self.runner.basedir
            elif basedir:
                self.basedir = basedir

        def get_basedir(self, variables):
            return self.basedir

try:
    # Ansible v1
    from ansible.utils import (listify_lookup_plugin_terms, path_dwim, warning)
except ImportError:
    # Ansible v2
    from __main__ import display
    warning = display.warning

def _to_filesystem_str(path):
    '''Returns filesystem path as a str, if it wasn't already.


    Used in selinux interactions because it cannot accept unicode
    instances, and specifying complex args in a playbook leaves
    you with unicode instances.  This method currently assumes
    that your filesystem encoding is UTF-8.

    '''
    if isinstance(path, unicode):
        path = path.encode("utf-8")
    return path

# If selinux fails to find a default, return an array of None
def selinux_context(path):
    context = [None, None, None, None]
    if HAVE_SELINUX and selinux.is_selinux_enabled():
        try:
            ret = selinux.lgetfilecon_raw(_to_filesystem_str(path))
        except OSError:
            return context
        if ret[0] != -1:
            # Limit split to 4 because the selevel, the last in the list,
            # may contain ':' characters
            context = ret[1].split(':', 3)
    return context

def file_props(root, path):
    ''' Returns dictionary with file properties, or return None on failure '''
    abspath = os.path.join(root, path)
    ret = dict(root=root, path=path, exists=True)

    try:
        if os.path.islink(abspath):
            ret['state'] = 'link'
            ret['src'] = os.readlink(abspath)
        elif os.path.isdir(abspath):
            ret['state'] = 'directory'
        elif os.path.isfile(abspath):
            ret['state'] = 'file'
            ret['src'] = abspath
    except OSError, e:
        warning('filetree: Error querying path %s (%s)' % (abspath, e))
        return None

    try:
        stat = os.stat(abspath)
    except OSError, e:
        warning('filetree: Error using stat() on path %s (%s)' % (abspath, e))
        return None

    ret['uid'] = stat.st_uid
    ret['gid'] = stat.st_gid
    ret['owner'] = pwd.getpwuid(stat.st_uid).pw_name
    ret['group'] = grp.getgrgid(stat.st_gid).gr_name
    ret['mode'] = str(oct(stat.st_mode))
    ret['size'] = stat.st_size
    ret['mtime'] = stat.st_mtime
    ret['ctime'] = stat.st_ctime

    if HAVE_SELINUX and selinux.is_selinux_enabled() == 1:
        context = selinux_context(abspath)
        ret['seuser'] = context[0]
        ret['serole'] = context[1]
        ret['setype'] = context[2]
        ret['selevel'] = context[3]

    return ret

    def run(self, terms, inject=None, variables=None, **kwargs):

        basedir = self.get_basedir(variables)

        # Ansible v1
        if 'listify_lookup_plugin_terms' in globals():
            terms = listify_lookup_plugin_terms(terms, basedir, inject)

        ret = []
        for term in terms:
            term_file = os.path.basename(term)
            if 'path_dwim' in globals():
                # Ansible v1
                dwimmed_path = path_dwim(basedir, os.path.dirname(term))
            else:
                # Ansible v2
                dwimmed_path = self._loader.path_dwim_relative(basedir, 
'files', os.path.dirname(term))
            path = os.path.join(dwimmed_path, term_file)
            for root, dirs, files in os.walk(path, topdown=True):
                for entry in dirs + files:
                    relpath = os.path.relpath(os.path.join(root, entry), path)
                   props = file_props(path, relpath)
                    if props != None:
                        ret.append(props)

        return ret
--------

--
Dag

Reply via email to