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 <
d...@wieers.com>
#
# 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