Compare commits

...

7 commits

Author SHA1 Message Date
Axionize
eda9009f53
Merge 6e7c537956 into 5321a9ecb5 2024-09-25 12:22:19 +02:00
softwarefactory-project-zuul[bot]
5321a9ecb5
Merge pull request #571 from saito-hideki/pr/bump_core_version
[CI] bump ansible-core version to 2.19 for devel branch

SUMMARY

bump devel test to ansible-core 2.19
add ansible-core 2.18 to the stable list (CI only covers sanity tests at the moment)

ISSUE TYPE

CI Pull Request

COMPONENT NAME
ansible.posix
ADDITIONAL INFORMATION
None
2024-09-25 01:24:12 +00:00
Hideki Saito
7194b6bb13
CI - bump ansible-core version
* bump devel test to ansible-core 2.19
* add ansible-core 2.18 to stable list but CI only covers sanity test at the moment.

Signed-off-by: Hideki Saito <saito@fgrep.org>
2024-09-25 09:37:42 +09:00
softwarefactory-project-zuul[bot]
a85108e25e
Merge pull request #570 from saito-hideki/pr/AAP-29225
Fixed to set ACLs on paths mounted with NFSv4 correctly

SUMMARY
Fixed to set ACLs on paths mounted with NFSv4 correctly.

Fixed #240

ISSUE TYPE

Bugfix Pull Request

COMPONENT NAME
ansible.posix.acl
ADDITIONAL INFORMATION
None
2024-09-20 05:44:24 +00:00
Hideki Saito
c353e43a90
Fixed to set ACLs on paths mounted with NFSv4 correctly
* Fixed #240

Signed-off-by: Hideki Saito <saito@fgrep.org>
2024-09-20 10:10:15 +09: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
8 changed files with 125 additions and 34 deletions

View file

@ -57,6 +57,21 @@ stages:
test: units test: units
- name: Lint - name: Lint
test: lint test: lint
- stage: Sanity_2_18
displayName: Ansible 2.18 sanity
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
nameFormat: "{0}"
testFormat: 2.18/{0}
targets:
- name: Sanity
test: sanity
- name: Units
test: units
- name: Lint
test: lint
- stage: Sanity_2_17 - stage: Sanity_2_17
displayName: Ansible 2.17 sanity displayName: Ansible 2.17 sanity
dependsOn: [] dependsOn: []
@ -113,6 +128,20 @@ stages:
test: ubuntu2204 test: ubuntu2204
- name: Ubuntu 24.04 - name: Ubuntu 24.04
test: ubuntu2404 test: ubuntu2404
- stage: Docker_2_18
displayName: Docker devel
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
testFormat: 2.18/linux/{0}/1
targets:
- name: Fedora 40
test: fedora40
- name: Ubuntu 22.04
test: ubuntu2204
- name: Ubuntu 24.04
test: ubuntu2404
- stage: Docker_2_17 - stage: Docker_2_17
displayName: Docker 2.17 displayName: Docker 2.17
dependsOn: [] dependsOn: []
@ -176,6 +205,18 @@ stages:
test: rhel/9.4 test: rhel/9.4
- name: FreeBSD 13.3 - name: FreeBSD 13.3
test: freebsd/13.3 test: freebsd/13.3
- stage: Remote_2_18
displayName: Remote devel
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
testFormat: 2.18/{0}/1
targets:
- name: RHEL 9.4
test: rhel/9.4
- name: FreeBSD 13.3
test: freebsd/13.3
- stage: Remote_2_17 - stage: Remote_2_17
displayName: Remote 2.17 displayName: Remote 2.17
dependsOn: [] dependsOn: []
@ -234,8 +275,11 @@ stages:
- Sanity_2_17 - Sanity_2_17
- Remote_2_17 - Remote_2_17
- Docker_2_17 - Docker_2_17
- Sanity_2_18
- Remote_2_18
- Docker_2_18
- Sanity_devel - Sanity_devel
- Remote_devel # - Remote_devel # Wait for test environment release
- Docker_devel # - Docker_devel # Wait for test environment release
jobs: jobs:
- template: templates/coverage.yml - template: templates/coverage.yml

View file

@ -74,11 +74,14 @@ None
<!-- List the versions of Ansible the collection has been tested with. Must match what is in galaxy.yml. --> <!-- List the versions of Ansible the collection has been tested with. Must match what is in galaxy.yml. -->
- ansible-core 2.18 (devel) - ansible-core 2.19 (devel)
- ansible-core 2.18 (stable) *
- ansible-core 2.17 (stable) - ansible-core 2.17 (stable)
- ansible-core 2.16 (stable) - ansible-core 2.16 (stable)
- ansible-core 2.15 (stable) - ansible-core 2.15 (stable)
*Note: For ansible-core 2.18, CI only covers sanity tests and no integration tests will be run until the test environment is released.*
## Roadmap ## Roadmap
<!-- Optional. Include the roadmap for this collection, and the proposed release/versioning strategy so users can anticipate the upgrade/update cycle. --> <!-- Optional. Include the roadmap for this collection, and the proposed release/versioning strategy so users can anticipate the upgrade/update cycle. -->

View file

@ -0,0 +1,3 @@
---
bugfixes:
- acl - Fixed to set ACLs on paths mounted with NFS version 4 correctly (https://github.com/ansible-collections/ansible.posix/issues/240).

View file

@ -0,0 +1,3 @@
---
trivial:
- Bump ansible-core version to 2.19 of devel branch and add 2.18 to CI.

View file

@ -339,6 +339,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
@ -365,11 +367,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)
@ -378,7 +380,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

@ -75,6 +75,10 @@ options:
use_nfsv4_acls: use_nfsv4_acls:
description: description:
- Use NFSv4 ACLs instead of POSIX ACLs. - 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 type: bool
default: false default: false
recalculate_mask: recalculate_mask:
@ -179,7 +183,7 @@ def split_entry(entry):
def build_entry(etype, entity, permissions=None, use_nfsv4_acls=False): 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.''' '''Builds and returns an entry string. Does not include the permissions bit if they are not provided.'''
if use_nfsv4_acls: if use_nfsv4_acls:
return ':'.join([etype, entity, permissions, 'allow']) return ':'.join(['A', 'g' if etype == 'group' else '', entity, permissions + 'tcy'])
if permissions: if permissions:
return etype + ':' + entity + ':' + permissions return etype + ':' + entity + ':' + permissions
@ -187,22 +191,27 @@ def build_entry(etype, entity, permissions=None, use_nfsv4_acls=False):
return etype + ':' + entity return etype + ':' + entity
def build_command(module, mode, path, follow, default, recursive, recalculate_mask, entry=''): def build_command(module, mode, path, follow, default, recursive, recalculate_mask, use_nfsv4_acls, entry=''):
'''Builds and returns a getfacl/setfacl command.''' '''Builds and returns a getfacl/setfacl command.'''
if mode == 'set': if mode == 'set':
cmd = [module.get_bin_path('setfacl', True)] cmd = [module.get_bin_path('nfs4_setfacl' if use_nfsv4_acls else 'setfacl', True)]
cmd.extend(['-m', entry]) cmd.extend(['-a' if use_nfsv4_acls else '-m', entry])
elif mode == 'rm': elif mode == 'rm':
cmd = [module.get_bin_path('setfacl', True)] cmd = [module.get_bin_path('nfs4_setfacl' if use_nfsv4_acls else 'setfacl', True)]
cmd.extend(['-x', entry]) cmd.extend(['-x', entry])
else: # mode == 'get' else: # mode == 'get'
cmd = [module.get_bin_path('getfacl', True)] cmd = [module.get_bin_path('getfacl', True)]
# prevents absolute path warnings and removes headers # prevents absolute path warnings and removes headers
if platform.system().lower() == 'linux': 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') cmd.append('--omit-header')
cmd.append('--absolute-names')
if recursive: if recursive and not use_nfsv4_acls:
cmd.append('--recursive') cmd.append('--recursive')
if recalculate_mask == 'mask' and mode in ['set', 'rm']: if recalculate_mask == 'mask' and mode in ['set', 'rm']:
@ -210,7 +219,7 @@ def build_command(module, mode, path, follow, default, recursive, recalculate_ma
elif recalculate_mask == 'no_mask' and mode in ['set', 'rm']: elif recalculate_mask == 'no_mask' and mode in ['set', 'rm']:
cmd.append('--no-mask') cmd.append('--no-mask')
if not follow: if not follow and not use_nfsv4_acls:
if platform.system().lower() == 'linux': if platform.system().lower() == 'linux':
cmd.append('--physical') cmd.append('--physical')
elif platform.system().lower() == 'freebsd': elif platform.system().lower() == 'freebsd':
@ -223,24 +232,34 @@ def build_command(module, mode, path, follow, default, recursive, recalculate_ma
return cmd return cmd
def acl_changed(module, cmd): def acl_changed(module, cmd, entry, use_nfsv4_acls=False):
'''Returns true if the provided command affects the existing ACLs, false otherwise.''' '''Returns true if the provided command affects the existing ACLs, false otherwise.'''
# FreeBSD do not have a --test flag, so by default, it is safer to always say "true" # 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': if platform.system().lower() == 'freebsd':
return True return True
cmd = cmd[:] # lists are mutables so cmd would be overwritten without this cmd = cmd[:] # lists are mutables so cmd would be overwritten without this
cmd.insert(1, '--test') cmd.insert(1, '--test')
lines = run_acl(module, cmd) lines = run_acl(module, cmd)
counter = 0
for line in lines: for line in lines:
if not line.endswith('*,*'): if line.endswith('*,*') and not use_nfsv4_acls:
return True return False
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): def run_acl(module, cmd, check_rc=True):
'''Runs the provided command and returns the output as a list of lines.'''
try: try:
(rc, out, err) = module.run_command(cmd, check_rc=check_rc) (rc, out, err) = module.run_command(cmd, check_rc=check_rc)
except Exception as e: except Exception as e:
@ -313,7 +332,7 @@ def main():
module.fail_json(msg="'recalculate_mask' MUST NOT be set to 'mask' or 'no_mask' when 'state=query'.") module.fail_json(msg="'recalculate_mask' MUST NOT be set to 'mask' or 'no_mask' when 'state=query'.")
if not entry: if not entry:
if state == 'absent' and permissions: if state == 'absent' and permissions and not use_nfsv4_acls:
module.fail_json(msg="'permissions' MUST NOT be set when 'state=absent'.") module.fail_json(msg="'permissions' MUST NOT be set when 'state=absent'.")
if state == 'absent' and not entity: if state == 'absent' and not entity:
@ -350,21 +369,24 @@ def main():
entry = build_entry(etype, entity, permissions, use_nfsv4_acls) entry = build_entry(etype, entity, permissions, use_nfsv4_acls)
command = build_command( command = build_command(
module, 'set', path, follow, module, 'set', path, follow,
default, recursive, recalculate_mask, entry default, recursive, recalculate_mask, use_nfsv4_acls, entry
) )
changed = acl_changed(module, command) changed = acl_changed(module, command, entry, use_nfsv4_acls)
if changed and not module.check_mode: if changed and not module.check_mode:
run_acl(module, command) run_acl(module, command)
msg = "%s is present" % entry msg = "%s is present" % entry
elif state == 'absent': elif state == 'absent':
entry = build_entry(etype, entity, use_nfsv4_acls) 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( command = build_command(
module, 'rm', path, follow, module, 'rm', path, follow,
default, recursive, recalculate_mask, entry default, recursive, recalculate_mask, use_nfsv4_acls, entry
) )
changed = acl_changed(module, command) changed = acl_changed(module, command, entry, use_nfsv4_acls)
if changed and not module.check_mode: if changed and not module.check_mode:
run_acl(module, command, False) run_acl(module, command, False)
@ -375,7 +397,10 @@ def main():
acl = run_acl( acl = run_acl(
module, module,
build_command(module, 'get', path, follow, default, recursive, recalculate_mask) build_command(
module, 'get', path, follow, default, recursive,
recalculate_mask, use_nfsv4_acls
)
) )
module.exit_json(changed=changed, msg=msg, acl=acl) module.exit_json(changed=changed, msg=msg, acl=acl)

View file

@ -361,6 +361,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
''' '''
@ -396,9 +407,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
@ -406,7 +417,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),
@ -540,11 +551,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:
@ -600,7 +610,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)

View file

@ -0,0 +1 @@
tests/utils/shippable/timing.py shebang