summaryrefslogtreecommitdiffstats
path: root/ansible_collections/community/general/plugins/lookup/filetree.py
diff options
context:
space:
mode:
Diffstat (limited to 'ansible_collections/community/general/plugins/lookup/filetree.py')
-rw-r--r--ansible_collections/community/general/plugins/lookup/filetree.py225
1 files changed, 225 insertions, 0 deletions
diff --git a/ansible_collections/community/general/plugins/lookup/filetree.py b/ansible_collections/community/general/plugins/lookup/filetree.py
new file mode 100644
index 000000000..f12cc4519
--- /dev/null
+++ b/ansible_collections/community/general/plugins/lookup/filetree.py
@@ -0,0 +1,225 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2016 Dag Wieers <dag@wieers.com>
+# Copyright (c) 2017 Ansible Project
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+DOCUMENTATION = r'''
+name: filetree
+author: Dag Wieers (@dagwieers) <dag@wieers.com>
+short_description: recursively match all files in a directory tree
+description:
+- This lookup enables you to template a complete tree of files on a target system while retaining permissions and ownership.
+- Supports directories, files and symlinks, including SELinux and other file properties.
+- If you provide more than one path, it will implement a first_found logic, and will not process entries it already processed in previous paths.
+ This enables merging different trees in order of importance, or add role_vars to specific paths to influence different instances of the same role.
+options:
+ _terms:
+ description: path(s) of files to read
+ required: true
+'''
+
+EXAMPLES = r"""
+- name: Create directories
+ ansible.builtin.file:
+ path: /web/{{ item.path }}
+ state: directory
+ mode: '{{ item.mode }}'
+ with_community.general.filetree: web/
+ when: item.state == 'directory'
+
+- name: Template files (explicitly skip directories in order to use the 'src' attribute)
+ ansible.builtin.template:
+ src: '{{ item.src }}'
+ # Your template files should be stored with a .j2 file extension,
+ # but should not be deployed with it. splitext|first removes it.
+ dest: /web/{{ item.path | splitext | first }}
+ mode: '{{ item.mode }}'
+ with_community.general.filetree: web/
+ when: item.state == 'file'
+
+- name: Recreate symlinks
+ ansible.builtin.file:
+ src: '{{ item.src }}'
+ dest: /web/{{ item.path }}
+ state: link
+ follow: false # avoid corrupting target files if the link already exists
+ force: true
+ mode: '{{ item.mode }}'
+ with_community.general.filetree: web/
+ when: item.state == 'link'
+
+- name: list all files under web/
+ ansible.builtin.debug:
+ msg: "{{ lookup('community.general.filetree', 'web/') }}"
+"""
+
+RETURN = r"""
+ _raw:
+ description: List of dictionaries with file information.
+ type: list
+ elements: dict
+ contains:
+ src:
+ description:
+ - Full path to file.
+ - Not returned when I(item.state) is set to C(directory).
+ type: path
+ root:
+ description: Allows filtering by original location.
+ type: path
+ path:
+ description: Contains the relative path to root.
+ type: path
+ mode:
+ description: The permissions the resulting file or directory.
+ type: str
+ state:
+ description: TODO
+ type: str
+ owner:
+ description: Name of the user that owns the file/directory.
+ type: raw
+ group:
+ description: Name of the group that owns the file/directory.
+ type: raw
+ seuser:
+ description: The user part of the SELinux file context.
+ type: raw
+ serole:
+ description: The role part of the SELinux file context.
+ type: raw
+ setype:
+ description: The type part of the SELinux file context.
+ type: raw
+ selevel:
+ description: The level part of the SELinux file context.
+ type: raw
+ uid:
+ description: Owner ID of the file/directory.
+ type: int
+ gid:
+ description: Group ID of the file/directory.
+ type: int
+ size:
+ description: Size of the target.
+ type: int
+ mtime:
+ description: Time of last modification.
+ type: float
+ ctime:
+ description: Time of last metadata update or creation (depends on OS).
+ type: float
+"""
+import os
+import pwd
+import grp
+import stat
+
+HAVE_SELINUX = False
+try:
+ import selinux
+ HAVE_SELINUX = True
+except ImportError:
+ pass
+
+from ansible.plugins.lookup import LookupBase
+from ansible.module_utils.common.text.converters import to_native, to_text
+from ansible.utils.display import Display
+
+display = Display()
+
+
+# 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:
+ # note: the selinux module uses byte strings on python2 and text
+ # strings on python3
+ ret = selinux.lgetfilecon_raw(to_native(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)
+
+ try:
+ st = os.lstat(abspath)
+ except OSError as e:
+ display.warning('filetree: Error using stat() on path %s (%s)' % (abspath, e))
+ return None
+
+ ret = dict(root=root, path=path)
+
+ if stat.S_ISLNK(st.st_mode):
+ ret['state'] = 'link'
+ ret['src'] = os.readlink(abspath)
+ elif stat.S_ISDIR(st.st_mode):
+ ret['state'] = 'directory'
+ elif stat.S_ISREG(st.st_mode):
+ ret['state'] = 'file'
+ ret['src'] = abspath
+ else:
+ display.warning('filetree: Error file type of %s is not supported' % abspath)
+ return None
+
+ ret['uid'] = st.st_uid
+ ret['gid'] = st.st_gid
+ try:
+ ret['owner'] = pwd.getpwuid(st.st_uid).pw_name
+ except KeyError:
+ ret['owner'] = st.st_uid
+ try:
+ ret['group'] = to_text(grp.getgrgid(st.st_gid).gr_name)
+ except KeyError:
+ ret['group'] = st.st_gid
+ ret['mode'] = '0%03o' % (stat.S_IMODE(st.st_mode))
+ ret['size'] = st.st_size
+ ret['mtime'] = st.st_mtime
+ ret['ctime'] = st.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
+
+
+class LookupModule(LookupBase):
+
+ def run(self, terms, variables=None, **kwargs):
+ self.set_options(var_options=variables, direct=kwargs)
+
+ basedir = self.get_basedir(variables)
+
+ ret = []
+ for term in terms:
+ term_file = os.path.basename(term)
+ dwimmed_path = self._loader.path_dwim_relative(basedir, 'files', os.path.dirname(term))
+ path = os.path.join(dwimmed_path, term_file)
+ display.debug("Walking '{0}'".format(path))
+ 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)
+
+ # Skip if relpath was already processed (from another root)
+ if relpath not in [entry['path'] for entry in ret]:
+ props = file_props(path, relpath)
+ if props is not None:
+ display.debug(" found '{0}'".format(os.path.join(path, relpath)))
+ ret.append(props)
+
+ return ret