mirror of
https://github.com/ansible-collections/ansible.posix.git
synced 2026-06-10 02:25:54 +00:00
Fix all deprecated module_utils imports before ansible-core 2.24 removal
SUMMARY
Fixes all deprecated ansible.module_utils imports across the entire collection that will be removed in ansible-core 2.24.
This PR comprehensively addresses deprecation warnings reported in #686 by updating import statements in 20 files to use the new recommended import paths, and removes 8 unused test utility files that contained deprecated imports.
Deprecated imports replaced:
Deprecated import
Replacement
ansible.module_utils._text
ansible.module_utils.common.text.converters
ansible.module_utils.common._collections_compat
collections.abc
ansible.module_utils.six.moves.shlex_quote
shlex.quote
ansible.module_utils.six.moves.reduce
functools.reduce
ansible.module_utils.six.moves.urllib.parse.urlparse
urllib.parse.urlparse
ansible.module_utils.six.string_types
basestring/str (Python 2/3 compatible)
ansible.module_utils.six.text_type
str
ansible.module_utils.six.PY3
Removed (simplified Python 2/3 conditionals)
ansible.module_utils.six.with_metaclass
Native metaclass= syntax
ansible.module_utils.six.iteritems
dict.items()
Files fixed (20 files, 1 commit per file for easier review):
plugins/action/patch.py
plugins/action/synchronize.py
plugins/callback/cgroup_perf_recap.py
plugins/callback/json.py
plugins/callback/jsonl.py
plugins/callback/profile_roles.py
plugins/callback/profile_tasks.py
plugins/modules/acl.py
plugins/modules/authorized_key.py
plugins/modules/firewalld_info.py
plugins/modules/mount.py
plugins/modules/patch.py
plugins/modules/rhel_rpm_ostree.py
plugins/modules/rpm_ostree_upgrade.py
plugins/modules/seboolean.py
plugins/modules/synchronize.py
plugins/modules/sysctl.py
plugins/shell/csh.py
plugins/shell/fish.py
tests/unit/modules/system/test_mount.py
Files deleted (8 unused test utility files):
These files are dead code - none of them are imported or used anywhere in the test suite or the collection. Removing them also addresses Python 2.7 compatibility concerns raised in code review, as several contained deprecated imports that would be incorrect to fix for Python 2.
tests/unit/compat/builtins.py
tests/unit/mock/loader.py
tests/unit/mock/path.py
tests/unit/mock/procenv.py
tests/unit/mock/vault_helper.py
tests/unit/mock/yaml_helper.py
tests/unit/modules/conftest.py
tests/unit/modules/utils.py
Completeness verified with:
git grep -n -P '_compat|utils._text|utils.six' -- '*.py' | grep -v yml
This command returns no results, confirming all deprecated imports have been replaced.
Notes on Python 2.7 compatibility:
For modules that may run on Python 2.7 managed hosts (e.g., authorized_key.py, synchronize.py, sysctl.py), Python 2/3 compatible fallbacks were used instead of direct Python 3 replacements:
authorized_key.py: try/except ImportError for urllib.parse.urlparse (falls back to urlparse on Python 2)
synchronize.py: try/except ImportError for shlex.quote (falls back to pipes.quote on Python 2)
sysctl.py: uses sys.version_info to set string_types to str on Python 3 (basestring on Python 2)
Also removes corresponding pylint:ansible-bad-import-from entries from tests/sanity/ignore-2.21.txt and tests/sanity/ignore-2.22.txt where applicable.
Fixes #686
ISSUE TYPE
Bugfix Pull Request
ADDITIONAL INFORMATION
Approach:
Each file is fixed in a separate commit for easier code review. The changelog fragment is added in a final commit. Corresponding pylint:ansible-bad-import-from ignore entries in tests/sanity/ignore-2.21.txt and tests/sanity/ignore-2.22.txt are removed in the same commit as the file fix (or the file removal commit).
CI results:
All 59 checks passing (Azure Pipelines sanity, units, lint, Docker, Remote across ansible-core 2.17 through devel, and Zuul ansible/check).
Reviewed-by: Felix Fontein <felix@fontein.de>
Reviewed-by: Pavel Bar
Reviewed-by: Abhijeet Kasurde
(cherry picked from commit 2022c1bd86)
Co-authored-by: centosinfra-prod-github-app[bot] <161850885+centosinfra-prod-github-app[bot]@users.noreply.github.com>
411 lines
13 KiB
Python
411 lines
13 KiB
Python
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright: (c) 2017, Ansible Project
|
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
|
|
from __future__ import absolute_import, division, print_function
|
|
__metaclass__ = type
|
|
|
|
|
|
DOCUMENTATION = r'''
|
|
---
|
|
module: acl
|
|
short_description: Set and retrieve file ACL information.
|
|
description:
|
|
- Set and retrieve file ACL information.
|
|
version_added: "1.0.0"
|
|
options:
|
|
path:
|
|
description:
|
|
- The full path of the file or object.
|
|
type: path
|
|
required: true
|
|
aliases: [ name ]
|
|
state:
|
|
description:
|
|
- Define whether the ACL should be present or not.
|
|
- The V(query) state gets the current ACL without changing it, for use in C(register) operations.
|
|
choices: [ absent, present, query ]
|
|
default: query
|
|
type: str
|
|
follow:
|
|
description:
|
|
- Whether to follow symlinks on the path if a symlink is encountered.
|
|
type: bool
|
|
default: true
|
|
default:
|
|
description:
|
|
- If O(path) is a directory, setting this to V(true) will make it the default ACL for entities created inside the directory.
|
|
- Setting O(default=true) causes an error if O(path) is a file.
|
|
type: bool
|
|
default: false
|
|
entity:
|
|
description:
|
|
- The actual user or group that the ACL applies to when matching entity types user or group are selected.
|
|
type: str
|
|
default: ""
|
|
etype:
|
|
description:
|
|
- The entity type of the ACL to apply, see C(setfacl) documentation for more info.
|
|
choices: [ group, mask, other, user ]
|
|
type: str
|
|
permissions:
|
|
description:
|
|
- The permissions to apply/remove can be any combination of C(r), C(w), C(x)
|
|
(read, write and execute respectively), and C(X) (execute permission if the file is a directory or already has execute permission for some user)
|
|
type: str
|
|
entry:
|
|
description:
|
|
- DEPRECATED.
|
|
- The ACL to set or remove.
|
|
- This must always be quoted in the form of C(<etype>:<qualifier>:<perms>).
|
|
- The qualifier may be empty for some types, but the type and perms are always required.
|
|
- C(-) can be used as placeholder when you do not care about permissions.
|
|
- This is now superseded by entity, type and permissions fields.
|
|
type: str
|
|
recursive:
|
|
description:
|
|
- Recursively sets the specified ACL.
|
|
- Incompatible with O(state=query).
|
|
- Alias O(recurse) added in version 1.3.0.
|
|
type: bool
|
|
default: false
|
|
aliases: [ recurse ]
|
|
use_nfsv4_acls:
|
|
description:
|
|
- Use NFSv4 ACLs instead of POSIX ACLs.
|
|
- This feature uses C(nfs4_setfacl) and C(nfs4_getfacl). The behavior depends on those implementation.
|
|
And currently it only supports C(A) in ACE, so C(D) must be replaced with the appropriate C(A).
|
|
- Permission is set as optimised ACLs by the system. You can check the actual ACLs that has been set using the return value.
|
|
- More info C(man nfs4_setfacl)
|
|
type: bool
|
|
default: false
|
|
recalculate_mask:
|
|
description:
|
|
- Select if and when to recalculate the effective right masks of the files.
|
|
- See C(setfacl) documentation for more info.
|
|
- Incompatible with O(state=query).
|
|
choices: [ default, mask, no_mask ]
|
|
default: default
|
|
type: str
|
|
author:
|
|
- Brian Coca (@bcoca)
|
|
- Jérémie Astori (@astorije)
|
|
notes:
|
|
- The M(ansible.posix.acl) module requires that ACLs are enabled on the target filesystem and that the C(setfacl) and C(getfacl) binaries are installed.
|
|
- As of Ansible 2.0, this module only supports Linux distributions.
|
|
- As of Ansible 2.3, the O(name) option has been changed to O(path) as default, but O(name) still works as well.
|
|
'''
|
|
|
|
EXAMPLES = r'''
|
|
- name: Grant user Joe read access to a file
|
|
ansible.posix.acl:
|
|
path: /etc/foo.conf
|
|
entity: joe
|
|
etype: user
|
|
permissions: r
|
|
state: present
|
|
|
|
- name: Removes the ACL for Joe on a specific file
|
|
ansible.posix.acl:
|
|
path: /etc/foo.conf
|
|
entity: joe
|
|
etype: user
|
|
state: absent
|
|
|
|
- name: Sets default ACL for joe on /etc/foo.d/
|
|
ansible.posix.acl:
|
|
path: /etc/foo.d/
|
|
entity: joe
|
|
etype: user
|
|
permissions: rw
|
|
default: true
|
|
state: present
|
|
|
|
- name: Same as previous but using entry shorthand
|
|
ansible.posix.acl:
|
|
path: /etc/foo.d/
|
|
entry: default:user:joe:rw-
|
|
state: present
|
|
|
|
- name: Obtain the ACL for a specific file
|
|
ansible.posix.acl:
|
|
path: /etc/foo.conf
|
|
register: acl_info
|
|
'''
|
|
|
|
RETURN = r'''
|
|
acl:
|
|
description: Current ACL on provided path (after changes, if any)
|
|
returned: success
|
|
type: list
|
|
sample: [ "user::rwx", "group::rwx", "other::rwx" ]
|
|
'''
|
|
|
|
import os
|
|
import platform
|
|
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
from ansible.module_utils.common.text.converters import to_native
|
|
|
|
|
|
def split_entry(entry):
|
|
''' splits entry and ensures normalized return'''
|
|
|
|
a = entry.split(':')
|
|
|
|
d = None
|
|
if entry.lower().startswith("d"):
|
|
d = True
|
|
a.pop(0)
|
|
|
|
if len(a) == 2:
|
|
a.append(None)
|
|
|
|
t, e, p = a
|
|
t = t.lower()
|
|
|
|
if t.startswith("u"):
|
|
t = "user"
|
|
elif t.startswith("g"):
|
|
t = "group"
|
|
elif t.startswith("m"):
|
|
t = "mask"
|
|
elif t.startswith("o"):
|
|
t = "other"
|
|
else:
|
|
t = None
|
|
|
|
return [d, t, e, p]
|
|
|
|
|
|
def build_entry(etype, entity, permissions=None, use_nfsv4_acls=False):
|
|
'''Builds and returns an entry string. Does not include the permissions bit if they are not provided.'''
|
|
if use_nfsv4_acls:
|
|
return ':'.join(['A', 'g' if etype == 'group' else '', entity, permissions + 'tcy'])
|
|
|
|
if permissions:
|
|
return etype + ':' + entity + ':' + permissions
|
|
|
|
return etype + ':' + entity
|
|
|
|
|
|
def build_command(module, mode, path, follow, default, recursive, recalculate_mask, use_nfsv4_acls, entry=''):
|
|
'''Builds and returns a getfacl/setfacl command.'''
|
|
if mode == 'set':
|
|
cmd = [module.get_bin_path('nfs4_setfacl' if use_nfsv4_acls else 'setfacl', True)]
|
|
cmd.extend(['-a' if use_nfsv4_acls else '-m', entry])
|
|
elif mode == 'rm':
|
|
cmd = [module.get_bin_path('nfs4_setfacl' if use_nfsv4_acls else 'setfacl', True)]
|
|
cmd.extend(['-x', entry])
|
|
else: # mode == 'get'
|
|
cmd = [module.get_bin_path('getfacl', True)]
|
|
# prevents absolute path warnings and removes headers
|
|
if platform.system().lower() == 'linux':
|
|
if use_nfsv4_acls:
|
|
# use nfs4_getfacl instead of getfacl if use_nfsv4_acls is True
|
|
cmd = [module.get_bin_path('nfs4_getfacl', True)]
|
|
else:
|
|
cmd = [module.get_bin_path('getfacl', True)]
|
|
cmd.append('--absolute-names')
|
|
cmd.append('--omit-header')
|
|
|
|
if recursive and not use_nfsv4_acls:
|
|
cmd.append('--recursive')
|
|
|
|
if recalculate_mask == 'mask' and mode in ['set', 'rm']:
|
|
cmd.append('--mask')
|
|
elif recalculate_mask == 'no_mask' and mode in ['set', 'rm']:
|
|
cmd.append('--no-mask')
|
|
|
|
if not follow and not use_nfsv4_acls:
|
|
if platform.system().lower() == 'linux':
|
|
cmd.append('--physical')
|
|
elif platform.system().lower() == 'freebsd':
|
|
cmd.append('-h')
|
|
|
|
if default:
|
|
cmd.insert(1, '-d')
|
|
|
|
cmd.append(path)
|
|
return cmd
|
|
|
|
|
|
def acl_changed(module, cmd, entry, use_nfsv4_acls=False):
|
|
'''Returns true if the provided command affects the existing ACLs, false otherwise.'''
|
|
# To check the ACL changes, use the output of setfacl or nfs4_setfacl with '--test'.
|
|
# FreeBSD do not have a --test flag, so by default, it is safer to always say "true".
|
|
if platform.system().lower() == 'freebsd':
|
|
return True
|
|
|
|
cmd = cmd[:] # lists are mutables so cmd would be overwritten without this
|
|
cmd.insert(1, '--test')
|
|
lines = run_acl(module, cmd)
|
|
counter = 0
|
|
for line in lines:
|
|
if line.endswith('*,*') and not use_nfsv4_acls:
|
|
return False
|
|
# if use_nfsv4_acls and entry is listed
|
|
if use_nfsv4_acls and entry == line:
|
|
counter += 1
|
|
|
|
# The current 'nfs4_setfacl --test' lists a new entry,
|
|
# which will be added at the top of list, followed by the existing entries.
|
|
# So if the entry has already been registered, the entry should be find twice.
|
|
if counter == 2:
|
|
return False
|
|
return True
|
|
|
|
|
|
def run_acl(module, cmd, check_rc=True):
|
|
'''Runs the provided command and returns the output as a list of lines.'''
|
|
try:
|
|
(rc, out, err) = module.run_command(cmd, check_rc=check_rc)
|
|
except Exception as e:
|
|
module.fail_json(msg=to_native(e))
|
|
|
|
lines = []
|
|
for l in out.splitlines():
|
|
if not l.startswith('#'):
|
|
lines.append(l.strip())
|
|
|
|
if lines and not lines[-1].split():
|
|
# trim last line only when it is empty
|
|
return lines[:-1]
|
|
|
|
return lines
|
|
|
|
|
|
def main():
|
|
module = AnsibleModule(
|
|
argument_spec=dict(
|
|
path=dict(type='path', required=True, aliases=['name']),
|
|
entry=dict(type='str'),
|
|
entity=dict(type='str', default=''),
|
|
etype=dict(
|
|
type='str',
|
|
choices=['group', 'mask', 'other', 'user'],
|
|
),
|
|
permissions=dict(type='str'),
|
|
state=dict(
|
|
type='str',
|
|
default='query',
|
|
choices=['absent', 'present', 'query'],
|
|
),
|
|
follow=dict(type='bool', default=True),
|
|
default=dict(type='bool', default=False),
|
|
recursive=dict(type='bool', default=False, aliases=['recurse']),
|
|
recalculate_mask=dict(
|
|
type='str',
|
|
default='default',
|
|
choices=['default', 'mask', 'no_mask'],
|
|
),
|
|
use_nfsv4_acls=dict(type='bool', default=False)
|
|
),
|
|
supports_check_mode=True,
|
|
)
|
|
|
|
if platform.system().lower() not in ['linux', 'freebsd']:
|
|
module.fail_json(msg="The acl module is not available on this system.")
|
|
|
|
path = module.params.get('path')
|
|
entry = module.params.get('entry')
|
|
entity = module.params.get('entity')
|
|
etype = module.params.get('etype')
|
|
permissions = module.params.get('permissions')
|
|
state = module.params.get('state')
|
|
follow = module.params.get('follow')
|
|
default = module.params.get('default')
|
|
recursive = module.params.get('recursive')
|
|
recalculate_mask = module.params.get('recalculate_mask')
|
|
use_nfsv4_acls = module.params.get('use_nfsv4_acls')
|
|
|
|
if not os.path.exists(path):
|
|
module.fail_json(msg="Path not found or not accessible.")
|
|
|
|
if state == 'query':
|
|
if recursive:
|
|
module.fail_json(msg="'recursive' MUST NOT be set when 'state=query'.")
|
|
|
|
if recalculate_mask in ['mask', 'no_mask']:
|
|
module.fail_json(msg="'recalculate_mask' MUST NOT be set to 'mask' or 'no_mask' when 'state=query'.")
|
|
|
|
if not entry:
|
|
if state == 'absent' and permissions and not use_nfsv4_acls:
|
|
module.fail_json(msg="'permissions' MUST NOT be set when 'state=absent'.")
|
|
|
|
if state == 'absent' and not entity:
|
|
module.fail_json(msg="'entity' MUST be set when 'state=absent'.")
|
|
|
|
if state in ['present', 'absent'] and not etype:
|
|
module.fail_json(msg="'etype' MUST be set when 'state=%s'." % state)
|
|
|
|
if entry:
|
|
if etype or entity or permissions:
|
|
module.fail_json(msg="'entry' MUST NOT be set when 'entity', 'etype' or 'permissions' are set.")
|
|
|
|
if state == 'present' and not entry.count(":") in [2, 3]:
|
|
module.fail_json(msg="'entry' MUST have 3 or 4 sections divided by ':' when 'state=present'.")
|
|
|
|
if state == 'absent' and not entry.count(":") in [1, 2]:
|
|
module.fail_json(msg="'entry' MUST have 2 or 3 sections divided by ':' when 'state=absent'.")
|
|
|
|
if state == 'query':
|
|
module.fail_json(msg="'entry' MUST NOT be set when 'state=query'.")
|
|
|
|
default_flag, etype, entity, permissions = split_entry(entry)
|
|
if default_flag is not None:
|
|
default = default_flag
|
|
|
|
if platform.system().lower() == 'freebsd':
|
|
if recursive:
|
|
module.fail_json(msg="recursive is not supported on that platform.")
|
|
|
|
changed = False
|
|
msg = ""
|
|
|
|
if state == 'present':
|
|
entry = build_entry(etype, entity, permissions, use_nfsv4_acls)
|
|
command = build_command(
|
|
module, 'set', path, follow,
|
|
default, recursive, recalculate_mask, use_nfsv4_acls, entry
|
|
)
|
|
changed = acl_changed(module, command, entry, use_nfsv4_acls)
|
|
|
|
if changed and not module.check_mode:
|
|
run_acl(module, command)
|
|
msg = "%s is present" % entry
|
|
|
|
elif state == 'absent':
|
|
if use_nfsv4_acls:
|
|
entry = build_entry(etype, entity, permissions, use_nfsv4_acls)
|
|
else:
|
|
entry = build_entry(etype, entity, use_nfsv4_acls)
|
|
command = build_command(
|
|
module, 'rm', path, follow,
|
|
default, recursive, recalculate_mask, use_nfsv4_acls, entry
|
|
)
|
|
changed = acl_changed(module, command, entry, use_nfsv4_acls)
|
|
|
|
if changed and not module.check_mode:
|
|
run_acl(module, command, False)
|
|
msg = "%s is absent" % entry
|
|
|
|
elif state == 'query':
|
|
msg = "current acl"
|
|
|
|
acl = run_acl(
|
|
module,
|
|
build_command(
|
|
module, 'get', path, follow, default, recursive,
|
|
recalculate_mask, use_nfsv4_acls
|
|
)
|
|
)
|
|
|
|
module.exit_json(changed=changed, msg=msg, acl=acl)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|