Compare commits

...

9 commits

Author SHA1 Message Date
Axionize
ff3745e1a7
Merge 6e7c537956 into 9bfed58f7f 2024-02-06 21:51:59 +00:00
softwarefactory-project-zuul[bot]
9bfed58f7f
Merge pull request #333 from NeodymiumFerBore/fix/doc/mount_absent_description
Fix absent state documentation

SUMMARY
Fix the documentation of the state absent so it describes its actual behavior:

absent does not specify that (quote) a device mount's entry will be removed from fstab. It specifies that a mount point entry will be removed from fstab
absent does not unmount recursively, and the module will fail if multiple devices are mounted on the same mount point
absent with a mount point that is not registered in the fstab has no effect. The state unmounted should be used instead.
src is ignored with state absent or unmounted

ISSUE TYPE

Docs Pull Request

COMPONENT NAME
mount
ADDITIONAL INFORMATION
This PR addresses a fix for issue 322.
2024-02-06 16:13:16 +00:00
Adam Miller
a18d180246
Merge branch 'main' into fix/doc/mount_absent_description 2024-02-06 09:29:55 -06:00
Marty Winkler
51b94f536c
Feat/add summary only option to profile callbacks (#511)
* profile_tasks callback: add parameter to show only summary
2024-02-06 09:21:42 -06:00
Petr Lautrbach
0a07bdb358
seboolean: make it work with disabled SELinux (#496)
Sometimes it's necessary to configure SELinux before it's enabled on the
system. There's `ignore_selinux_state` which should allow it. Before
this change `seboolean` module failed on SELinux disabled system even
with `ignore_selinux_state: true` and SELinux policy installed while
`semanage boolean` worked as expected:

    $ ansible -i 192.168.121.153, -m seboolean -a "name=ssh_sysadm_login state=on ignore_selinux_state=true" all
    192.168.121.153 | FAILED! => {
        "ansible_facts": {
            "discovered_interpreter_python": "/usr/bin/python3"
        },
        "changed": false,
        "msg": "Failed to get list of boolean names"
    }

    $ ssh root@192.168.121.153 semanage boolean -l | grep ssh_sysadm_login
    ssh_sysadm_login               (off  ,  off)  Allow ssh to sysadm login

It's caused by `selinux.security_get_boolean_names()` and
`selinux.security_get_boolean_active(name)` which required SELinux
enabled system.

This change adds a fallback to semanage API which works in SELinux
disabled system when SELinux targeted policy is installed:

    ANSIBLE_LIBRARY=plugins/modules ansible -i 192.168.121.153, -m seboolean -a "name=ssh_sysadm_login state=on persistent=true ignore_selinux_state=true" all
    192.168.121.153 | CHANGED => {
        "ansible_facts": {
            "discovered_interpreter_python": "/usr/bin/python3"
        },
        "changed": true,
        "name": "ssh_sysadm_login",
        "persistent": true,
        "state": true
    }

    $ ssh root@192.168.121.153 semanage boolean -l | grep ssh_sysadm_login
    ssh_sysadm_login               (on   ,   on)  Allow ssh to sysadm login

Note that without `persistent=true` this module is effectively NO-OP now.

Signed-off-by: Petr Lautrbach <lautrbach@redhat.com>
2024-02-06 09:21:27 -06:00
Axionize
6e7c537956 Add example to documentation 2023-12-25 00:58:33 -05:00
Axionize
d0ea1143ee Make synchronize work with multiple src paths 2023-12-25 00:54:16 -05:00
NdFeB
fa4dd35d66 Add changelog fragment for PR 333 2022-03-12 15:37:13 +01:00
NdFeB
211e6c74b5 Fix absent state documentation 2022-03-12 15:25:38 +01:00
9 changed files with 96 additions and 42 deletions

View file

@ -0,0 +1,4 @@
---
trivial:
- mount - fix description in the documentation of the state ``absent`` to match its actual behavior
and point out that ``src`` is ignored with state ``absent`` and ``unmounted`` (https://github.com/ansible-collections/ansible.posix/issues/322)

View file

@ -0,0 +1,3 @@
---
bugfixes:
- seboolean - make it work with disabled SELinux

View file

@ -0,0 +1,3 @@
---
minor_changes:
- "Add summary_only parameter to profile_roles and profile_tasks callbacks."

View file

@ -332,6 +332,8 @@ class ActionModule(ActionBase):
dest = _tmp_args.get('dest', None) dest = _tmp_args.get('dest', None)
if src is None or dest is None: if src is None or dest is None:
return dict(failed=True, msg="synchronize requires both src and dest parameters are set") return dict(failed=True, msg="synchronize requires both src and dest parameters are set")
if isinstance(src, str):
src = [src]
# Determine if we need a user@ and a password # Determine if we need a user@ and a password
user = None user = None
@ -358,11 +360,11 @@ class ActionModule(ActionBase):
# use the mode to define src and dest's url # use the mode to define src and dest's url
if _tmp_args.get('mode', 'push') == 'pull': if _tmp_args.get('mode', 'push') == 'pull':
# src is a remote path: <user>@<host>, dest is a local path # src is a remote path: <user>@<host>, dest is a local path
src = self._process_remote(_tmp_args, src_host, src, user, inv_port in localhost_ports) src = [self._process_remote(_tmp_args, src_host, e, user, inv_port in localhost_ports) for e in src]
dest = self._process_origin(dest_host, dest, user) dest = self._process_origin(dest_host, dest, user)
else: else:
# src is a local path, dest is a remote path: <user>@<host> # src is a local path, dest is a remote path: <user>@<host>
src = self._process_origin(src_host, src, user) src = [self._process_origin(src_host, e, user) for e in src]
dest = self._process_remote(_tmp_args, dest_host, dest, user, inv_port in localhost_ports) dest = self._process_remote(_tmp_args, dest_host, dest, user, inv_port in localhost_ports)
password = dest_host_inventory_vars.get('ansible_ssh_pass', None) or dest_host_inventory_vars.get('ansible_password', None) password = dest_host_inventory_vars.get('ansible_ssh_pass', None) or dest_host_inventory_vars.get('ansible_password', None)
@ -371,7 +373,7 @@ class ActionModule(ActionBase):
else: else:
# Still need to munge paths (to account for roles) even if we aren't # Still need to munge paths (to account for roles) even if we aren't
# copying files between hosts # copying files between hosts
src = self._get_absolute_path(path=src) src = [self._get_absolute_path(path=e) for e in src]
dest = self._get_absolute_path(path=dest) dest = self._get_absolute_path(path=dest)
_tmp_args['_local_rsync_password'] = password _tmp_args['_local_rsync_password'] = password

View file

@ -14,6 +14,19 @@ DOCUMENTATION = '''
- This callback module provides profiling for ansible roles. - This callback module provides profiling for ansible roles.
requirements: requirements:
- whitelisting in configuration - whitelisting in configuration
options:
summary_only:
description:
- Only show summary, not individual task profiles.
Especially usefull in combination with C(DISPLAY_SKIPPED_HOSTS=false) and/or C(ANSIBLE_DISPLAY_OK_HOSTS=false).
type: bool
default: False
env:
- name: PROFILE_ROLES_SUMMARY_ONLY
ini:
- section: callback_profile_roles
key: summary_only
version_added: 1.5.0
''' '''
import collections import collections
@ -76,13 +89,26 @@ class CallbackModule(CallbackBase):
self.stats = collections.Counter() self.stats = collections.Counter()
self.totals = collections.Counter() self.totals = collections.Counter()
self.current = None self.current = None
self.summary_only = None
super(CallbackModule, self).__init__() super(CallbackModule, self).__init__()
def set_options(self, task_keys=None, var_options=None, direct=None):
super(CallbackModule, self).set_options(task_keys=task_keys, var_options=var_options, direct=direct)
self.summary_only = self.get_option('summary_only')
def _display_tasktime(self):
if not self.summary_only:
self._display.display(tasktime())
def _record_task(self, task): def _record_task(self, task):
""" """
Logs the start of each task Logs the start of each task
""" """
self._display.display(tasktime()) self._display_tasktime()
timestamp(self) timestamp(self)
if task._role: if task._role:
@ -99,10 +125,10 @@ class CallbackModule(CallbackBase):
self._record_task(task) self._record_task(task)
def playbook_on_setup(self): def playbook_on_setup(self):
self._display.display(tasktime()) self._display_tasktime()
def playbook_on_stats(self, stats): def playbook_on_stats(self, stats):
self._display.display(tasktime()) self._display_tasktime()
self._display.display(filled("", fchar="=")) self._display.display(filled("", fchar="="))
timestamp(self) timestamp(self)

View file

@ -40,6 +40,18 @@ DOCUMENTATION = '''
ini: ini:
- section: callback_profile_tasks - section: callback_profile_tasks
key: sort_order key: sort_order
summary_only:
description:
- Only show summary, not individual task profiles.
Especially usefull in combination with C(DISPLAY_SKIPPED_HOSTS=false) and/or C(ANSIBLE_DISPLAY_OK_HOSTS=false).
type: bool
default: False
env:
- name: PROFILE_TASKS_SUMMARY_ONLY
ini:
- section: callback_profile_tasks
key: summary_only
version_added: 1.5.0
''' '''
EXAMPLES = ''' EXAMPLES = '''
@ -120,6 +132,7 @@ class CallbackModule(CallbackBase):
self.current = None self.current = None
self.sort_order = None self.sort_order = None
self.summary_only = None
self.task_output_limit = None self.task_output_limit = None
super(CallbackModule, self).__init__() super(CallbackModule, self).__init__()
@ -137,6 +150,8 @@ class CallbackModule(CallbackBase):
elif self.sort_order == 'none': elif self.sort_order == 'none':
self.sort_order = None self.sort_order = None
self.summary_only = self.get_option('summary_only')
self.task_output_limit = self.get_option('output_limit') self.task_output_limit = self.get_option('output_limit')
if self.task_output_limit is not None: if self.task_output_limit is not None:
if self.task_output_limit == 'all': if self.task_output_limit == 'all':
@ -144,11 +159,15 @@ class CallbackModule(CallbackBase):
else: else:
self.task_output_limit = int(self.task_output_limit) self.task_output_limit = int(self.task_output_limit)
def _display_tasktime(self):
if not self.summary_only:
self._display.display(tasktime())
def _record_task(self, task): def _record_task(self, task):
""" """
Logs the start of each task Logs the start of each task
""" """
self._display.display(tasktime()) self._display_tasktime()
timestamp(self) timestamp(self)
# Record the start time of the current task # Record the start time of the current task
@ -171,10 +190,10 @@ class CallbackModule(CallbackBase):
self._record_task(task) self._record_task(task)
def playbook_on_setup(self): def playbook_on_setup(self):
self._display.display(tasktime()) self._display_tasktime()
def playbook_on_stats(self, stats): def playbook_on_stats(self, stats):
self._display.display(tasktime()) self._display_tasktime()
self._display.display(filled("", fchar="=")) self._display.display(filled("", fchar="="))
timestamp(self) timestamp(self)

View file

@ -32,6 +32,7 @@ options:
description: description:
- Device (or NFS volume, or something else) to be mounted on I(path). - Device (or NFS volume, or something else) to be mounted on I(path).
- Required when I(state) set to C(present), C(mounted) or C(ephemeral). - Required when I(state) set to C(present), C(mounted) or C(ephemeral).
- Ignored when I(state) set to C(absent) or C(unmounted).
type: path type: path
fstype: fstype:
description: description:
@ -75,9 +76,13 @@ options:
the module will fail to avoid unexpected unmount or mount point override. the module will fail to avoid unexpected unmount or mount point override.
If the mount point is not present, the mount point will be created. If the mount point is not present, the mount point will be created.
The I(fstab) is completely ignored. This option is added in version 1.5.0. The I(fstab) is completely ignored. This option is added in version 1.5.0.
- C(absent) specifies that the device mount's entry will be removed from - C(absent) specifies that the mount point entry I(path) will be removed
I(fstab) and will also unmount the device and remove the mount from I(fstab) and will also unmount the mounted device and remove the
point. mount point. A mounted device will be unmounted regardless of I(src) or its
real source. C(absent) does not unmount recursively, and the module will
fail if multiple devices are mounted on the same mount point. Using
C(absent) with a mount point that is not registered in the I(fstab) has
no effect. Use C(unmounted) instead..
- C(remounted) specifies that the device will be remounted for when you - C(remounted) specifies that the device will be remounted for when you
want to force a refresh on the mount itself (added in 2.9). This will want to force a refresh on the mount itself (added in 2.9). This will
always return changed=true. If I(opts) is set, the options will be always return changed=true. If I(opts) is set, the options will be

View file

@ -73,8 +73,7 @@ except ImportError:
HAVE_SEMANAGE = False HAVE_SEMANAGE = False
from ansible.module_utils.basic import AnsibleModule, missing_required_lib from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils.six import binary_type from ansible.module_utils._text import to_text
from ansible.module_utils._text import to_bytes, to_text
from ansible_collections.ansible.posix.plugins.module_utils._respawn import respawn_module, HAS_RESPAWN_UTIL from ansible_collections.ansible.posix.plugins.module_utils._respawn import respawn_module, HAS_RESPAWN_UTIL
@ -82,23 +81,6 @@ def get_runtime_status(ignore_selinux_state=False):
return True if ignore_selinux_state is True else selinux.is_selinux_enabled() return True if ignore_selinux_state is True else selinux.is_selinux_enabled()
def has_boolean_value(module, name):
bools = []
try:
rc, bools = selinux.security_get_boolean_names()
except OSError:
module.fail_json(msg="Failed to get list of boolean names")
# work around for selinux who changed its API, see
# https://github.com/ansible/ansible/issues/25651
if len(bools) > 0:
if isinstance(bools[0], binary_type):
name = to_bytes(name)
if name in bools:
return True
else:
return False
def get_boolean_value(module, name): def get_boolean_value(module, name):
state = 0 state = 0
try: try:
@ -174,7 +156,10 @@ def semanage_set_boolean_value(module, handle, name, value):
semanage.semanage_handle_destroy(handle) semanage.semanage_handle_destroy(handle)
module.fail_json(msg="Failed to modify boolean key with semanage") module.fail_json(msg="Failed to modify boolean key with semanage")
if semanage.semanage_bool_set_active(handle, boolkey, sebool) < 0: if (
selinux.is_selinux_enabled()
and semanage.semanage_bool_set_active(handle, boolkey, sebool) < 0
):
semanage.semanage_handle_destroy(handle) semanage.semanage_handle_destroy(handle)
module.fail_json(msg="Failed to set boolean key active with semanage") module.fail_json(msg="Failed to set boolean key active with semanage")
@ -315,12 +300,9 @@ def main():
# Feature only available in selinux library since 2012. # Feature only available in selinux library since 2012.
name = selinux.selinux_boolean_sub(name) name = selinux.selinux_boolean_sub(name)
if not has_boolean_value(module, name):
module.fail_json(msg="SELinux boolean %s does not exist." % name)
if persistent: if persistent:
changed = semanage_boolean_value(module, name, state) changed = semanage_boolean_value(module, name, state)
else: elif selinux.is_selinux_enabled():
cur_value = get_boolean_value(module, name) cur_value = get_boolean_value(module, name)
if cur_value != state: if cur_value != state:
changed = True changed = True

View file

@ -360,6 +360,17 @@ EXAMPLES = r'''
src: /tmp/localpath/ src: /tmp/localpath/
dest: /tmp/remotepath dest: /tmp/remotepath
rsync_path: /usr/gnu/bin/rsync rsync_path: /usr/gnu/bin/rsync
# Source files from multiple folders and merge them on the remote
# Files of the same name in /tmp/path_c/ will take precedence over those in /tmp/path_b/, and same for path_b to path_a
- name: Copy files from multiple folders and merge them into dest
ansible.posix.synchronize:
src:
- /tmp/path_a/
- /tmp/path_b/
- /tmp/path_c/
dest: /tmp/dest/
recursive: True
''' '''
@ -395,9 +406,9 @@ def substitute_controller(path):
def is_rsh_needed(source, dest): def is_rsh_needed(source, dest):
if source.startswith('rsync://') or dest.startswith('rsync://'): if all(e.startswith('rsync://') for e in source) or dest.startswith('rsync://'):
return False return False
if ':' in source or ':' in dest: if any(':' in e for e in source) or ':' in dest:
return True return True
return False return False
@ -405,7 +416,7 @@ def is_rsh_needed(source, dest):
def main(): def main():
module = AnsibleModule( module = AnsibleModule(
argument_spec=dict( argument_spec=dict(
src=dict(type='path', required=True), src=dict(type='list', required=True),
dest=dict(type='path', required=True), dest=dict(type='path', required=True),
dest_port=dict(type='int'), dest_port=dict(type='int'),
delete=dict(type='bool', default=False), delete=dict(type='bool', default=False),
@ -539,11 +550,10 @@ def main():
if dirs: if dirs:
cmd.append('--dirs') cmd.append('--dirs')
if source.startswith('rsync://') and dest.startswith('rsync://'): if all(e.startswith('rsync://') for e in source) and dest.startswith('rsync://'):
module.fail_json(msg='either src or dest must be a localhost', rc=1) module.fail_json(msg='either src or dest must be a localhost', rc=1)
if is_rsh_needed(source, dest): if is_rsh_needed(source, dest):
# https://github.com/ansible/ansible/issues/15907 # https://github.com/ansible/ansible/issues/15907
has_rsh = False has_rsh = False
for rsync_opt in rsync_opts: for rsync_opt in rsync_opts:
@ -599,7 +609,7 @@ def main():
changed_marker = '<<CHANGED>>' changed_marker = '<<CHANGED>>'
cmd.append('--out-format=%s' % shlex_quote(changed_marker + '%i %n%L')) cmd.append('--out-format=%s' % shlex_quote(changed_marker + '%i %n%L'))
cmd.append(shlex_quote(source)) [cmd.append(shlex_quote(e)) for e in source]
cmd.append(shlex_quote(dest)) cmd.append(shlex_quote(dest))
cmdstr = ' '.join(cmd) cmdstr = ' '.join(cmd)