From c58dda14c203c3f60e093ccc6c18d4e0f2687a7e Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Fri, 28 Apr 2023 12:08:45 +0200 Subject: [PATCH] passwordstore plugin: vendor FileLock that was removed from ansible-core devel (#6447) Vendor FileLock that was removed from ansible-core devel. --- changelogs/fragments/passwordstore-lock.yml | 2 + plugins/lookup/passwordstore.py | 3 +- plugins/module_utils/_filelock.py | 109 ++++++++++++++++++++ 3 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 changelogs/fragments/passwordstore-lock.yml create mode 100644 plugins/module_utils/_filelock.py diff --git a/changelogs/fragments/passwordstore-lock.yml b/changelogs/fragments/passwordstore-lock.yml new file mode 100644 index 0000000000..0d1f3cc9c3 --- /dev/null +++ b/changelogs/fragments/passwordstore-lock.yml @@ -0,0 +1,2 @@ +bugfixes: + - "passwordstore lookup plugin - make compatible with ansible-core 2.16 (https://github.com/ansible-collections/community.general/pull/6447)." diff --git a/plugins/lookup/passwordstore.py b/plugins/lookup/passwordstore.py index 6face16f39..7e37a37855 100644 --- a/plugins/lookup/passwordstore.py +++ b/plugins/lookup/passwordstore.py @@ -209,7 +209,6 @@ import time import yaml from ansible.errors import AnsibleError, AnsibleAssertionError -from ansible.module_utils.common.file import FileLock from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text from ansible.module_utils.parsing.convert_bool import boolean from ansible.utils.display import Display @@ -217,6 +216,8 @@ from ansible.utils.encrypt import random_password from ansible.plugins.lookup import LookupBase from ansible import constants as C +from ansible_collections.community.general.plugins.module_utils._filelock import FileLock + display = Display() diff --git a/plugins/module_utils/_filelock.py b/plugins/module_utils/_filelock.py new file mode 100644 index 0000000000..a35d0b91cf --- /dev/null +++ b/plugins/module_utils/_filelock.py @@ -0,0 +1,109 @@ +# Copyright (c) 2018, Ansible Project +# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) +# SPDX-License-Identifier: BSD-2-Clause + +# NOTE: +# This has been vendored from ansible.module_utils.common.file. This code has been removed from there for ansible-core 2.16. + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import stat +import time +import fcntl +import sys + +from contextlib import contextmanager + + +class LockTimeout(Exception): + pass + + +class FileLock: + ''' + Currently FileLock is implemented via fcntl.flock on a lock file, however this + behaviour may change in the future. Avoid mixing lock types fcntl.flock, + fcntl.lockf and module_utils.common.file.FileLock as it will certainly cause + unwanted and/or unexpected behaviour + ''' + def __init__(self): + self.lockfd = None + + @contextmanager + def lock_file(self, path, tmpdir, lock_timeout=None): + ''' + Context for lock acquisition + ''' + try: + self.set_lock(path, tmpdir, lock_timeout) + yield + finally: + self.unlock() + + def set_lock(self, path, tmpdir, lock_timeout=None): + ''' + Create a lock file based on path with flock to prevent other processes + using given path. + Please note that currently file locking only works when it's executed by + the same user, I.E single user scenarios + + :kw path: Path (file) to lock + :kw tmpdir: Path where to place the temporary .lock file + :kw lock_timeout: + Wait n seconds for lock acquisition, fail if timeout is reached. + 0 = Do not wait, fail if lock cannot be acquired immediately, + Default is None, wait indefinitely until lock is released. + :returns: True + ''' + lock_path = os.path.join(tmpdir, 'ansible-{0}.lock'.format(os.path.basename(path))) + l_wait = 0.1 + r_exception = IOError + if sys.version_info[0] == 3: + r_exception = BlockingIOError + + self.lockfd = open(lock_path, 'w') + + if lock_timeout <= 0: + fcntl.flock(self.lockfd, fcntl.LOCK_EX | fcntl.LOCK_NB) + os.chmod(lock_path, stat.S_IWRITE | stat.S_IREAD) + return True + + if lock_timeout: + e_secs = 0 + while e_secs < lock_timeout: + try: + fcntl.flock(self.lockfd, fcntl.LOCK_EX | fcntl.LOCK_NB) + os.chmod(lock_path, stat.S_IWRITE | stat.S_IREAD) + return True + except r_exception: + time.sleep(l_wait) + e_secs += l_wait + continue + + self.lockfd.close() + raise LockTimeout('{0} sec'.format(lock_timeout)) + + fcntl.flock(self.lockfd, fcntl.LOCK_EX) + os.chmod(lock_path, stat.S_IWRITE | stat.S_IREAD) + + return True + + def unlock(self): + ''' + Make sure lock file is available for everyone and Unlock the file descriptor + locked by set_lock + + :returns: True + ''' + if not self.lockfd: + return True + + try: + fcntl.flock(self.lockfd, fcntl.LOCK_UN) + self.lockfd.close() + except ValueError: # file wasn't opened, let context manager fail gracefully + pass + + return True