[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:
jctanner 2017-08-02 10:04:09 -04:00 committed by GitHub
parent b266204afa
commit baf1ed9100
3 changed files with 100 additions and 1 deletions

View file

@ -2355,7 +2355,7 @@ class AnsibleModule(object):
backupdest = '%s.%s.%s' % (fn, os.getpid(), ext)
try:
shutil.copy2(fn, backupdest)
self.preserved_copy(fn, backupdest)
except (shutil.Error, IOError):
e = get_exception()
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()
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):
'''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

View 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

View file

@ -369,3 +369,6 @@
that:
- 'diff_result.stdout == ""'
- "diff_result.rc == 0"
# aliases file requires root for template tests so this should be safe
- include: backup_test.yml