mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
[WIP] Create preserved_copy function in basic.py to perserve file ownership. (#27344)
Create preserved_copy function in basic.py to perserve file ownership. * Add a test for template preserved backup * Use a script to get the random names * bytes to strings * Remove dump of hostvars * Stop being fancy and create a testuser instead * Fix pep8 * set file attributes * Pass the correct data to set_attributes_if_different * Use -j instead -b and pass the attributes as a string instead of a list * remove debugging message * Use shell to softly set the attr Fixes #24408
This commit is contained in:
parent
b266204afa
commit
baf1ed9100
3 changed files with 100 additions and 1 deletions
|
@ -2355,7 +2355,7 @@ class AnsibleModule(object):
|
||||||
backupdest = '%s.%s.%s' % (fn, os.getpid(), ext)
|
backupdest = '%s.%s.%s' % (fn, os.getpid(), ext)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
shutil.copy2(fn, backupdest)
|
self.preserved_copy(fn, backupdest)
|
||||||
except (shutil.Error, IOError):
|
except (shutil.Error, IOError):
|
||||||
e = get_exception()
|
e = get_exception()
|
||||||
self.fail_json(msg='Could not make backup of %s to %s: %s' % (fn, backupdest, e))
|
self.fail_json(msg='Could not make backup of %s to %s: %s' % (fn, backupdest, e))
|
||||||
|
@ -2370,6 +2370,42 @@ class AnsibleModule(object):
|
||||||
e = get_exception()
|
e = get_exception()
|
||||||
sys.stderr.write("could not cleanup %s: %s" % (tmpfile, e))
|
sys.stderr.write("could not cleanup %s: %s" % (tmpfile, e))
|
||||||
|
|
||||||
|
def preserved_copy(self, src, dest):
|
||||||
|
"""Copy a file with preserved ownership, permissions and context"""
|
||||||
|
|
||||||
|
# shutil.copy2(src, dst)
|
||||||
|
# Similar to shutil.copy(), but metadata is copied as well - in fact,
|
||||||
|
# this is just shutil.copy() followed by copystat(). This is similar
|
||||||
|
# to the Unix command cp -p.
|
||||||
|
#
|
||||||
|
# shutil.copystat(src, dst)
|
||||||
|
# Copy the permission bits, last access time, last modification time,
|
||||||
|
# and flags from src to dst. The file contents, owner, and group are
|
||||||
|
# unaffected. src and dst are path names given as strings.
|
||||||
|
|
||||||
|
shutil.copy2(src, dest)
|
||||||
|
|
||||||
|
# Set the context
|
||||||
|
if self.selinux_enabled():
|
||||||
|
context = self.selinux_context(src)
|
||||||
|
self.set_context_if_different(dest, context, False)
|
||||||
|
|
||||||
|
# chown it
|
||||||
|
try:
|
||||||
|
dest_stat = os.stat(src)
|
||||||
|
tmp_stat = os.stat(dest)
|
||||||
|
if dest_stat and (tmp_stat.st_uid != dest_stat.st_uid or tmp_stat.st_gid != dest_stat.st_gid):
|
||||||
|
os.chown(dest, dest_stat.st_uid, dest_stat.st_gid)
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno != errno.EPERM:
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Set the attributes
|
||||||
|
current_attribs = self.get_file_attributes(src)
|
||||||
|
current_attribs = current_attribs.get('attr_flags', [])
|
||||||
|
current_attribs = ''.join(current_attribs)
|
||||||
|
self.set_attributes_if_different(dest, current_attribs, True)
|
||||||
|
|
||||||
def atomic_move(self, src, dest, unsafe_writes=False):
|
def atomic_move(self, src, dest, unsafe_writes=False):
|
||||||
'''atomically move src to dest, copying attributes from dest, returns true on success
|
'''atomically move src to dest, copying attributes from dest, returns true on success
|
||||||
it uses os.rename to ensure this as it is an atomic operation, rest of the function is
|
it uses os.rename to ensure this as it is an atomic operation, rest of the function is
|
||||||
|
|
60
test/integration/targets/template/tasks/backup_test.yml
Normal file
60
test/integration/targets/template/tasks/backup_test.yml
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
# https://github.com/ansible/ansible/issues/24408
|
||||||
|
|
||||||
|
- set_fact:
|
||||||
|
t_username: templateuser1
|
||||||
|
t_groupname: templateuser1
|
||||||
|
|
||||||
|
- name: create the test group
|
||||||
|
group:
|
||||||
|
name: "{{ t_groupname }}"
|
||||||
|
|
||||||
|
- name: create the test user
|
||||||
|
user:
|
||||||
|
name: "{{ t_username }}"
|
||||||
|
group: "{{ t_groupname }}"
|
||||||
|
createhome: no
|
||||||
|
|
||||||
|
- name: set the dest file
|
||||||
|
set_fact:
|
||||||
|
t_dest: "{{ output_dir + '/tfile_dest.txt' }}"
|
||||||
|
|
||||||
|
- name: create the old file
|
||||||
|
file:
|
||||||
|
path: "{{ t_dest }}"
|
||||||
|
state: touch
|
||||||
|
mode: 0777
|
||||||
|
owner: "{{ t_username }}"
|
||||||
|
group: "{{ t_groupname }}"
|
||||||
|
|
||||||
|
- name: failsafe attr change incase underlying system does not support it
|
||||||
|
shell: chattr =j "{{ t_dest }}"
|
||||||
|
ignore_errors: True
|
||||||
|
|
||||||
|
- name: run the template
|
||||||
|
template:
|
||||||
|
src: foo.j2
|
||||||
|
dest: "{{ t_dest }}"
|
||||||
|
backup: True
|
||||||
|
register: t_backup_res
|
||||||
|
|
||||||
|
- name: check the data for the backup
|
||||||
|
stat:
|
||||||
|
path: "{{ t_backup_res.backup_file }}"
|
||||||
|
register: t_backup_stats
|
||||||
|
|
||||||
|
- name: validate result of preserved backup
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- 't_backup_stats.stat.mode == "0777"'
|
||||||
|
- 't_backup_stats.stat.pw_name == t_username'
|
||||||
|
- 't_backup_stats.stat.gr_name == t_groupname'
|
||||||
|
|
||||||
|
- name: cleanup the user
|
||||||
|
user:
|
||||||
|
name: "{{ t_username }}"
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: cleanup the group
|
||||||
|
user:
|
||||||
|
name: "{{ t_groupname }}"
|
||||||
|
state: absent
|
|
@ -369,3 +369,6 @@
|
||||||
that:
|
that:
|
||||||
- 'diff_result.stdout == ""'
|
- 'diff_result.stdout == ""'
|
||||||
- "diff_result.rc == 0"
|
- "diff_result.rc == 0"
|
||||||
|
|
||||||
|
# aliases file requires root for template tests so this should be safe
|
||||||
|
- include: backup_test.yml
|
||||||
|
|
Loading…
Add table
Reference in a new issue