mirror of
https://github.com/ansible-collections/ansible.posix.git
synced 2026-06-09 18:15: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>
436 lines
15 KiB
Python
436 lines
15 KiB
Python
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# (c) 2012, David "DaviXX" CHANIAL <david.chanial@gmail.com>
|
|
# (c) 2014, James Tanner <tanner.jc@gmail.com>
|
|
# 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: sysctl
|
|
short_description: Manage entries in sysctl.conf.
|
|
description:
|
|
- This module manipulates sysctl entries and optionally performs a C(/sbin/sysctl -p) after changing them.
|
|
version_added: "1.0.0"
|
|
options:
|
|
name:
|
|
description:
|
|
- The dot-separated path (also known as O(key)) specifying the sysctl variable.
|
|
required: true
|
|
aliases: [ 'key' ]
|
|
type: str
|
|
value:
|
|
description:
|
|
- Desired value of the sysctl key.
|
|
aliases: [ 'val' ]
|
|
type: str
|
|
state:
|
|
description:
|
|
- Whether the entry should be present or absent in the sysctl file.
|
|
choices: [ "present", "absent" ]
|
|
default: present
|
|
type: str
|
|
ignoreerrors:
|
|
description:
|
|
- Use this option to ignore errors about unknown keys.
|
|
type: bool
|
|
default: false
|
|
reload:
|
|
description:
|
|
- If V(true), performs a C(/sbin/sysctl -p) if the O(sysctl_file) is
|
|
updated. If V(false), does not reload C(sysctl) even if the
|
|
O(sysctl_file) is updated.
|
|
type: bool
|
|
default: true
|
|
sysctl_file:
|
|
description:
|
|
- Specifies the absolute path to C(sysctl.conf), if not C(/etc/sysctl.conf).
|
|
default: /etc/sysctl.conf
|
|
type: path
|
|
sysctl_set:
|
|
description:
|
|
- Verify token value with the sysctl command and set with C(-w) if necessary.
|
|
type: bool
|
|
default: false
|
|
author:
|
|
- David CHANIAL (@davixx)
|
|
'''
|
|
|
|
EXAMPLES = r'''
|
|
# Set vm.swappiness to 5 in /etc/sysctl.conf
|
|
- ansible.posix.sysctl:
|
|
name: vm.swappiness
|
|
value: '5'
|
|
state: present
|
|
|
|
# Remove kernel.panic entry from /etc/sysctl.conf
|
|
- ansible.posix.sysctl:
|
|
name: kernel.panic
|
|
state: absent
|
|
sysctl_file: /etc/sysctl.conf
|
|
|
|
# Set kernel.panic to 3 in /tmp/test_sysctl.conf
|
|
- ansible.posix.sysctl:
|
|
name: kernel.panic
|
|
value: '3'
|
|
sysctl_file: /tmp/test_sysctl.conf
|
|
reload: false
|
|
|
|
# Enable resource limits management in FreeBSD
|
|
- ansible.posix.sysctl:
|
|
name: kern.racct.enable
|
|
value: '1'
|
|
sysctl_file: /boot/loader.conf
|
|
reload: false
|
|
|
|
# Set ip forwarding on in /proc and verify token value with the sysctl command
|
|
- ansible.posix.sysctl:
|
|
name: net.ipv4.ip_forward
|
|
value: '1'
|
|
sysctl_set: true
|
|
|
|
# Set ip forwarding on in /proc and in the sysctl file and reload if necessary
|
|
- ansible.posix.sysctl:
|
|
name: net.ipv4.ip_forward
|
|
value: '1'
|
|
sysctl_set: true
|
|
state: present
|
|
reload: true
|
|
'''
|
|
|
|
# ==============================================================
|
|
|
|
import os
|
|
import platform
|
|
import re
|
|
import sys
|
|
import tempfile
|
|
|
|
# TODO(Python2): On Python 2, string_types is basestring (str + unicode).
|
|
# This module may run on target hosts with Python 2.7.
|
|
# Remove the Python 2 branch when Python 2 support is dropped.
|
|
if sys.version_info >= (3, 0):
|
|
string_types = str
|
|
else:
|
|
string_types = basestring # pylint: disable=undefined-variable
|
|
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
from ansible.module_utils.common.text.converters import to_native
|
|
from ansible.module_utils.parsing.convert_bool import BOOLEANS_FALSE, BOOLEANS_TRUE
|
|
|
|
|
|
class SysctlModule(object):
|
|
|
|
# We have to use LANG=C because we are capturing STDERR of sysctl to detect
|
|
# success or failure.
|
|
LANG_ENV = {'LANG': 'C', 'LC_ALL': 'C', 'LC_MESSAGES': 'C'}
|
|
|
|
def __init__(self, module):
|
|
self.module = module
|
|
self.args = self.module.params
|
|
|
|
self.sysctl_cmd = self.module.get_bin_path('sysctl', required=True)
|
|
self.sysctl_file = self.args['sysctl_file']
|
|
|
|
self.proc_value = None # current token value in proc fs
|
|
self.file_value = None # current token value in file
|
|
self.file_lines = [] # all lines in the file
|
|
self.file_values = {} # dict of token values
|
|
|
|
self.changed = False # will change occur
|
|
self.set_proc = False # does sysctl need to set value
|
|
self.write_file = False # does the sysctl file need to be reloaded
|
|
|
|
self.process()
|
|
|
|
# ==============================================================
|
|
# LOGIC
|
|
# ==============================================================
|
|
|
|
def process(self):
|
|
|
|
self.platform = platform.system().lower()
|
|
|
|
# Whitespace is bad
|
|
self.args['name'] = self.args['name'].strip()
|
|
self.args['value'] = self._parse_value(self.args['value'])
|
|
|
|
thisname = self.args['name']
|
|
|
|
# get the current proc fs value
|
|
self.proc_value = self.get_token_curr_value(thisname)
|
|
|
|
# get the current sysctl file value
|
|
self.read_sysctl_file()
|
|
if thisname not in self.file_values:
|
|
self.file_values[thisname] = None
|
|
|
|
# update file contents with desired token/value
|
|
self.fix_lines()
|
|
|
|
# what do we need to do now?
|
|
if self.file_values[thisname] is None and self.args['state'] == "present":
|
|
self.changed = True
|
|
self.write_file = True
|
|
elif self.file_values[thisname] is None and self.args['state'] == "absent":
|
|
self.changed = False
|
|
elif self.file_values[thisname] and self.args['state'] == "absent":
|
|
self.changed = True
|
|
self.write_file = True
|
|
elif self.file_values[thisname] != self.args['value']:
|
|
self.changed = True
|
|
self.write_file = True
|
|
# with reload=yes we should check if the current system values are
|
|
# correct, so that we know if we should reload
|
|
elif self.args['reload']:
|
|
if self.proc_value is None:
|
|
self.changed = True
|
|
elif not self._values_is_equal(self.proc_value, self.args['value']):
|
|
self.changed = True
|
|
|
|
# use the sysctl command or not?
|
|
if self.args['sysctl_set'] and self.args['state'] == "present":
|
|
if self.proc_value is None:
|
|
self.changed = True
|
|
elif not self._values_is_equal(self.proc_value, self.args['value']):
|
|
self.changed = True
|
|
self.set_proc = True
|
|
|
|
# Do the work
|
|
if not self.module.check_mode:
|
|
if self.set_proc:
|
|
self.set_token_value(self.args['name'], self.args['value'])
|
|
if self.write_file:
|
|
self.write_sysctl()
|
|
if self.changed and self.args['reload']:
|
|
self.reload_sysctl()
|
|
|
|
def _values_is_equal(self, a, b):
|
|
"""Expects two string values. It will split the string by whitespace
|
|
and compare each value. It will return True if both lists are the same,
|
|
contain the same elements and the same order."""
|
|
if a is None or b is None:
|
|
return False
|
|
|
|
a = a.split()
|
|
b = b.split()
|
|
|
|
if len(a) != len(b):
|
|
return False
|
|
|
|
return len([i for i, j in zip(a, b) if i == j]) == len(a)
|
|
|
|
def _parse_value(self, value):
|
|
if value is None:
|
|
return ''
|
|
elif isinstance(value, bool):
|
|
if value:
|
|
return '1'
|
|
else:
|
|
return '0'
|
|
elif isinstance(value, string_types):
|
|
if value.lower() in BOOLEANS_TRUE:
|
|
return '1'
|
|
elif value.lower() in BOOLEANS_FALSE:
|
|
return '0'
|
|
else:
|
|
return value.strip()
|
|
else:
|
|
return value
|
|
|
|
def _stderr_failed(self, err):
|
|
# sysctl can fail to set a value even if it returns an exit status 0
|
|
# (https://bugzilla.redhat.com/show_bug.cgi?id=1264080). That's why we
|
|
# also have to check stderr for errors. For now we will only fail on
|
|
# specific errors defined by the regex below.
|
|
errors_regex = r'^sysctl: setting key "[^"]+": (Invalid argument|Read-only file system)$'
|
|
return re.search(errors_regex, err, re.MULTILINE) is not None
|
|
|
|
# ==============================================================
|
|
# SYSCTL COMMAND MANAGEMENT
|
|
# ==============================================================
|
|
|
|
# Use the sysctl command to find the current value
|
|
def get_token_curr_value(self, token):
|
|
if self.platform == 'openbsd':
|
|
# openbsd doesn't support -e, just drop it
|
|
thiscmd = "%s -n %s" % (self.sysctl_cmd, token)
|
|
else:
|
|
thiscmd = "%s -e -n %s" % (self.sysctl_cmd, token)
|
|
rc, out, err = self.module.run_command(thiscmd, environ_update=self.LANG_ENV)
|
|
if rc != 0:
|
|
return None
|
|
else:
|
|
return out
|
|
|
|
# Use the sysctl command to set the current value
|
|
def set_token_value(self, token, value):
|
|
if len(value.split()) > 0:
|
|
value = '"' + value + '"'
|
|
if self.platform == 'openbsd':
|
|
# openbsd doesn't accept -w, but since it's not needed, just drop it
|
|
thiscmd = "%s %s=%s" % (self.sysctl_cmd, token, value)
|
|
elif self.platform == 'freebsd':
|
|
ignore_missing = ''
|
|
if self.args['ignoreerrors']:
|
|
ignore_missing = '-i'
|
|
# freebsd doesn't accept -w, but since it's not needed, just drop it
|
|
thiscmd = "%s %s %s=%s" % (self.sysctl_cmd, ignore_missing, token, value)
|
|
else:
|
|
ignore_missing = ''
|
|
if self.args['ignoreerrors']:
|
|
ignore_missing = '-e'
|
|
thiscmd = "%s %s -w %s=%s" % (self.sysctl_cmd, ignore_missing, token, value)
|
|
rc, out, err = self.module.run_command(thiscmd, environ_update=self.LANG_ENV)
|
|
if rc != 0 or self._stderr_failed(err):
|
|
self.module.fail_json(msg='setting %s failed: %s' % (token, out + err))
|
|
else:
|
|
return rc
|
|
|
|
# Run sysctl -p
|
|
def reload_sysctl(self):
|
|
if self.platform == 'freebsd':
|
|
# freebsd doesn't support -p, so reload the sysctl service
|
|
rc, out, err = self.module.run_command('/etc/rc.d/sysctl reload', environ_update=self.LANG_ENV)
|
|
elif self.platform == 'openbsd':
|
|
# openbsd doesn't support -p and doesn't have a sysctl service,
|
|
# so we have to set every value with its own sysctl call
|
|
for k, v in self.file_values.items():
|
|
rc = 0
|
|
if k != self.args['name']:
|
|
rc = self.set_token_value(k, v)
|
|
# FIXME this check is probably not needed as set_token_value would fail_json if rc != 0
|
|
if rc != 0:
|
|
break
|
|
if rc == 0 and self.args['state'] == "present":
|
|
rc = self.set_token_value(self.args['name'], self.args['value'])
|
|
|
|
# set_token_value would have called fail_json in case of failure
|
|
# so return here and do not continue to the error processing below
|
|
# https://github.com/ansible/ansible/issues/58158
|
|
return
|
|
else:
|
|
# system supports reloading via the -p flag to sysctl, so we'll use that
|
|
sysctl_args = [self.sysctl_cmd, '-p', self.sysctl_file]
|
|
if self.args['ignoreerrors']:
|
|
sysctl_args.insert(1, '-e')
|
|
|
|
rc, out, err = self.module.run_command(sysctl_args, environ_update=self.LANG_ENV)
|
|
|
|
if rc != 0 or self._stderr_failed(err):
|
|
self.module.fail_json(msg="Failed to reload sysctl: %s" % to_native(out) + to_native(err))
|
|
|
|
# ==============================================================
|
|
# SYSCTL FILE MANAGEMENT
|
|
# ==============================================================
|
|
|
|
# Get the token value from the sysctl file
|
|
def read_sysctl_file(self):
|
|
|
|
lines = []
|
|
if os.path.isfile(self.sysctl_file):
|
|
try:
|
|
with open(self.sysctl_file, "r") as read_file:
|
|
lines = read_file.readlines()
|
|
except IOError as e:
|
|
self.module.fail_json(msg="Failed to open %s: %s" % (to_native(self.sysctl_file), to_native(e)))
|
|
|
|
for line in lines:
|
|
line = line.strip()
|
|
self.file_lines.append(line)
|
|
|
|
# don't split empty lines or comments or line without equal sign
|
|
if not line or line.startswith(("#", ";")) or "=" not in line:
|
|
continue
|
|
|
|
k, v = line.split('=', 1)
|
|
k = k.strip()
|
|
v = v.strip()
|
|
self.file_values[k] = v.strip()
|
|
|
|
# Fix the value in the sysctl file content
|
|
def fix_lines(self):
|
|
checked = []
|
|
self.fixed_lines = []
|
|
for line in self.file_lines:
|
|
if not line.strip() or line.strip().startswith(("#", ";")) or "=" not in line:
|
|
self.fixed_lines.append(line)
|
|
continue
|
|
tmpline = line.strip()
|
|
k, v = tmpline.split('=', 1)
|
|
k = k.strip()
|
|
v = v.strip()
|
|
if k not in checked:
|
|
checked.append(k)
|
|
if k == self.args['name']:
|
|
if self.args['state'] == "present":
|
|
new_line = "%s=%s\n" % (k, self.args['value'])
|
|
self.fixed_lines.append(new_line)
|
|
else:
|
|
new_line = "%s=%s\n" % (k, v)
|
|
self.fixed_lines.append(new_line)
|
|
|
|
if self.args['name'] not in checked and self.args['state'] == "present":
|
|
new_line = "%s=%s\n" % (self.args['name'], self.args['value'])
|
|
self.fixed_lines.append(new_line)
|
|
|
|
# Completely rewrite the sysctl file
|
|
def write_sysctl(self):
|
|
# open a tmp file
|
|
fd, tmp_path = tempfile.mkstemp('.conf', '.ansible_m_sysctl_', os.path.dirname(os.path.realpath(self.sysctl_file)))
|
|
f = open(tmp_path, "w")
|
|
try:
|
|
for l in self.fixed_lines:
|
|
f.write(l.strip() + "\n")
|
|
except IOError as e:
|
|
self.module.fail_json(msg="Failed to write to file %s: %s" % (tmp_path, to_native(e)))
|
|
f.flush()
|
|
f.close()
|
|
|
|
# replace the real one
|
|
self.module.atomic_move(tmp_path, os.path.realpath(self.sysctl_file))
|
|
|
|
|
|
# ==============================================================
|
|
# main
|
|
|
|
def main():
|
|
|
|
# defining module
|
|
module = AnsibleModule(
|
|
argument_spec=dict(
|
|
name=dict(aliases=['key'], required=True),
|
|
value=dict(aliases=['val'], required=False, type='str'),
|
|
state=dict(default='present', choices=['present', 'absent']),
|
|
reload=dict(default=True, type='bool'),
|
|
sysctl_set=dict(default=False, type='bool'),
|
|
ignoreerrors=dict(default=False, type='bool'),
|
|
sysctl_file=dict(default='/etc/sysctl.conf', type='path')
|
|
),
|
|
supports_check_mode=True,
|
|
required_if=[('state', 'present', ['value'])],
|
|
)
|
|
|
|
if module.params['name'] is None:
|
|
module.fail_json(msg="name cannot be None")
|
|
if module.params['state'] == 'present' and module.params['value'] is None:
|
|
module.fail_json(msg="value cannot be None")
|
|
|
|
# In case of in-line params
|
|
if module.params['name'] == '':
|
|
module.fail_json(msg="name cannot be blank")
|
|
if module.params['state'] == 'present' and module.params['value'] == '':
|
|
module.fail_json(msg="value cannot be blank")
|
|
|
|
result = SysctlModule(module)
|
|
|
|
module.exit_json(changed=result.changed)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|