[PR #10417/44ca3661 backport][stable-11] sysrc: refactor (#10504)

sysrc: refactor (#10417)

* sysrc: refactor

* sysrc: refactor changelog fragment

* sysrc: forgot the os import

* sysrc: update test to edit the correct file

* sysrc: Added copyright info to the test conf file

* sysrc: Added full copyright info to the test conf file

* sysrc: Detect permission denied when using sysrc

* sysrc: Fixed the permission check and 2.7 compatibility

* sysrc: Fix typo of import

* sysrc: Fix err.find check

* sysrc: Add bugfixes changelog fragment

* sysrc: Use `StateModuleHelper`

* sysrc: updated imports

* sysrc: remove re import and set errno.EACCES on the OSError

* sysrc: format code properly

* sysrc: fix Python 2.7 compatibility and set changed manually

* sysrc: add missing name format check

Also use `self.module.fail_json` through out

* sysrc: Removed os import by accident

* sysrc: updated per review, and the way the existing value is retrieved

(cherry picked from commit 44ca366173)

Co-authored-by: David Lundgren <dlundgren@syberisle.net>
This commit is contained in:
patchback[bot] 2025-07-28 19:21:55 +02:00 committed by GitHub
commit 6e0c62d3e2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 177 additions and 136 deletions

View file

@ -7,6 +7,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r"""
@ -102,157 +103,121 @@ changed:
sample: true
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.module_helper import StateModuleHelper
import os
import re
class Sysrc(object):
def __init__(self, module, name, value, path, delim, jail):
self.module = module
self.name = name
self.changed = False
self.value = value
self.path = path
self.delim = delim
self.jail = jail
self.sysrc = module.get_bin_path('sysrc', True)
def has_unknown_variable(self, out, err):
# newer versions of sysrc use stderr instead of stdout
return err.find("unknown variable") > 0 or out.find("unknown variable") > 0
def exists(self):
"""
Tests whether the name is in the file. If parameter value is defined,
then tests whether name=value is in the file. These tests are necessary
because sysrc doesn't use exit codes. Instead, let sysrc read the
file's content and create a dictionary comprising the configuration.
Use this dictionary to preform the tests.
"""
(rc, out, err) = self.run_sysrc('-e', '-a')
conf = dict([i.split('=', 1) for i in out.splitlines()])
if self.value is None:
return self.name in conf
else:
return self.name in conf and conf[self.name] == '"%s"' % self.value
def contains(self):
(rc, out, err) = self.run_sysrc('-n', self.name)
if self.has_unknown_variable(out, err):
return False
return self.value in out.strip().split(self.delim)
def present(self):
if self.exists():
return
if not self.module.check_mode:
(rc, out, err) = self.run_sysrc("%s=%s" % (self.name, self.value))
self.changed = True
def absent(self):
if not self.exists():
return
# inversed since we still need to mark as changed
if not self.module.check_mode:
(rc, out, err) = self.run_sysrc('-x', self.name)
if self.has_unknown_variable(out, err):
return
self.changed = True
def value_present(self):
if self.contains():
return
if self.module.check_mode:
self.changed = True
return
setstring = '%s+=%s%s' % (self.name, self.delim, self.value)
(rc, out, err) = self.run_sysrc(setstring)
if out.find("%s:" % self.name) == 0:
values = out.split(' -> ')[1].strip().split(self.delim)
if self.value in values:
self.changed = True
def value_absent(self):
if not self.contains():
return
if self.module.check_mode:
self.changed = True
return
setstring = '%s-=%s%s' % (self.name, self.delim, self.value)
(rc, out, err) = self.run_sysrc(setstring)
if out.find("%s:" % self.name) == 0:
values = out.split(' -> ')[1].strip().split(self.delim)
if self.value not in values:
self.changed = True
def run_sysrc(self, *args):
cmd = [self.sysrc, '-f', self.path]
if self.jail:
cmd += ['-j', self.jail]
cmd.extend(args)
(rc, out, err) = self.module.run_command(cmd)
return (rc, out, err)
def main():
module = AnsibleModule(
class Sysrc(StateModuleHelper):
module = dict(
argument_spec=dict(
name=dict(type='str', required=True),
value=dict(type='str', default=None),
state=dict(type='str', default='present', choices=['absent', 'present', 'value_present', 'value_absent']),
path=dict(type='str', default='/etc/rc.conf'),
delim=dict(type='str', default=' '),
jail=dict(type='str', default=None),
jail=dict(type='str', default=None)
),
supports_check_mode=True,
supports_check_mode=True
)
output_params = ('value',)
use_old_vardict = False
name = module.params.pop('name')
# OID style names are not supported
if not re.match('^[a-zA-Z0-9_]+$', name):
module.fail_json(
msg="Name may only contain alphanumeric and underscore characters"
)
def __init_module__(self):
# OID style names are not supported
if not re.match(r'^\w+$', self.vars.name, re.ASCII):
self.module.fail_json(msg="Name may only contain alpha-numeric and underscore characters")
value = module.params.pop('value')
state = module.params.pop('state')
path = module.params.pop('path')
delim = module.params.pop('delim')
jail = module.params.pop('jail')
result = dict(
name=name,
state=state,
value=value,
path=path,
delim=delim,
jail=jail
)
self.sysrc = self.module.get_bin_path('sysrc', True)
rc_value = Sysrc(module, name, value, path, delim, jail)
def _contains(self):
value = self._get()
if value is None:
return False, None
if state == 'present':
rc_value.present()
elif state == 'absent':
rc_value.absent()
elif state == 'value_present':
rc_value.value_present()
elif state == 'value_absent':
rc_value.value_absent()
value = value.split(self.vars.delim)
result['changed'] = rc_value.changed
return self.vars.value in value, value
module.exit_json(**result)
def _get(self):
if not os.path.exists(self.vars.path):
return None
(rc, out, err) = self._sysrc('-v', '-n', self.vars.name)
if "unknown variable" in err or "unknown variable" in out:
# Prior to FreeBSD 11.1 sysrc would write "unknown variable" to stdout and not stderr
# https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=229806
return None
if out.startswith(self.vars.path):
return out.split(':', 1)[1].strip()
return None
def _modify(self, op, changed):
(rc, out, err) = self._sysrc("%s%s=%s%s" % (self.vars.name, op, self.vars.delim, self.vars.value))
if out.startswith("%s:" % self.vars.name):
return changed(out.split(' -> ')[1].strip().split(self.vars.delim))
return False
def _sysrc(self, *args):
cmd = [self.sysrc, '-f', self.vars.path]
if self.vars.jail:
cmd += ['-j', self.vars.jail]
cmd.extend(args)
(rc, out, err) = self.module.run_command(cmd)
if "Permission denied" in err:
self.module.fail_json(msg="Permission denied for %s" % self.vars.path)
return rc, out, err
def state_absent(self):
if self._get() is None:
return
if not self.check_mode:
self._sysrc('-x', self.vars.name)
self.changed = True
def state_present(self):
value = self._get()
if value == self.vars.value:
return
if self.vars.value is None:
self.vars.set('value', value)
return
if not self.check_mode:
self._sysrc("%s=%s" % (self.vars.name, self.vars.value))
self.changed = True
def state_value_absent(self):
(contains, _unused) = self._contains()
if not contains:
return
self.changed = self.check_mode or self._modify('-', lambda values: self.vars.value not in values)
def state_value_present(self):
(contains, value) = self._contains()
if contains:
return
if self.vars.value is None:
self.vars.set('value', value)
return
self.changed = self.check_mode or self._modify('+', lambda values: self.vars.value in values)
def main():
Sysrc.execute()
if __name__ == '__main__':