mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
[PR #6546/c76af60a backport][stable-7] ini_file: Don't creates new file instead of following symlink (#6598)
ini_file: Don't creates new file instead of following symlink (#6546)
* ini_file: Don't creates new file instead of following symlink
This is a bug fix that address a situation where `community.general.ini_file`
was destroying symlinks instead of updating of updating their targets.
Closes: #6470
* ini_file: add the follow parameter
If `poth` points on a symlink and `follow` is true, the `ini_file` plugin
will preserve the symlink and modify the target file.
* adjust the documentation of the new key
- yes/no -> true/false.
- new key will be introduced in 7.1.0.
- clean up the `state=link` part.
(cherry picked from commit c76af60a73
)
Co-authored-by: Gonéri Le Bouder <goneri@lebouder.net>
This commit is contained in:
parent
5cec31586f
commit
c4ebd482eb
3 changed files with 86 additions and 10 deletions
3
changelogs/fragments/ini_file-preserve-symlink.yml
Normal file
3
changelogs/fragments/ini_file-preserve-symlink.yml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
---
|
||||||
|
bugfixes:
|
||||||
|
- "ini_file - add the ``follow`` paramter to follow the symlinks instead of replacing them (https://github.com/ansible-collections/community.general/pull/6546)."
|
|
@ -109,6 +109,13 @@ options:
|
||||||
- Allow option without value and without '=' symbol.
|
- Allow option without value and without '=' symbol.
|
||||||
type: bool
|
type: bool
|
||||||
default: false
|
default: false
|
||||||
|
follow:
|
||||||
|
description:
|
||||||
|
- This flag indicates that filesystem links, if they exist, should be followed.
|
||||||
|
- I(follow=true) can modify I(src) when combined with parameters such as I(mode).
|
||||||
|
type: bool
|
||||||
|
default: false
|
||||||
|
version_added: 7.1.0
|
||||||
notes:
|
notes:
|
||||||
- While it is possible to add an I(option) without specifying a I(value), this makes no sense.
|
- While it is possible to add an I(option) without specifying a I(value), this makes no sense.
|
||||||
- As of Ansible 2.3, the I(dest) option has been changed to I(path) as default, but I(dest) still works as well.
|
- As of Ansible 2.3, the I(dest) option has been changed to I(path) as default, but I(dest) still works as well.
|
||||||
|
@ -191,7 +198,7 @@ def update_section_line(changed, section_lines, index, changed_lines, newline, m
|
||||||
|
|
||||||
def do_ini(module, filename, section=None, option=None, values=None,
|
def do_ini(module, filename, section=None, option=None, values=None,
|
||||||
state='present', exclusive=True, backup=False, no_extra_spaces=False,
|
state='present', exclusive=True, backup=False, no_extra_spaces=False,
|
||||||
create=True, allow_no_value=False):
|
create=True, allow_no_value=False, follow=False):
|
||||||
|
|
||||||
if section is not None:
|
if section is not None:
|
||||||
section = to_text(section)
|
section = to_text(section)
|
||||||
|
@ -210,15 +217,20 @@ def do_ini(module, filename, section=None, option=None, values=None,
|
||||||
after_header='%s (content)' % filename,
|
after_header='%s (content)' % filename,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not os.path.exists(filename):
|
if follow and os.path.islink(filename):
|
||||||
|
target_filename = os.path.realpath(filename)
|
||||||
|
else:
|
||||||
|
target_filename = filename
|
||||||
|
|
||||||
|
if not os.path.exists(target_filename):
|
||||||
if not create:
|
if not create:
|
||||||
module.fail_json(rc=257, msg='Destination %s does not exist!' % filename)
|
module.fail_json(rc=257, msg='Destination %s does not exist!' % target_filename)
|
||||||
destpath = os.path.dirname(filename)
|
destpath = os.path.dirname(target_filename)
|
||||||
if not os.path.exists(destpath) and not module.check_mode:
|
if not os.path.exists(destpath) and not module.check_mode:
|
||||||
os.makedirs(destpath)
|
os.makedirs(destpath)
|
||||||
ini_lines = []
|
ini_lines = []
|
||||||
else:
|
else:
|
||||||
with io.open(filename, 'r', encoding="utf-8-sig") as ini_file:
|
with io.open(target_filename, 'r', encoding="utf-8-sig") as ini_file:
|
||||||
ini_lines = [to_text(line) for line in ini_file.readlines()]
|
ini_lines = [to_text(line) for line in ini_file.readlines()]
|
||||||
|
|
||||||
if module._diff:
|
if module._diff:
|
||||||
|
@ -404,7 +416,7 @@ def do_ini(module, filename, section=None, option=None, values=None,
|
||||||
backup_file = None
|
backup_file = None
|
||||||
if changed and not module.check_mode:
|
if changed and not module.check_mode:
|
||||||
if backup:
|
if backup:
|
||||||
backup_file = module.backup_local(filename)
|
backup_file = module.backup_local(target_filename)
|
||||||
|
|
||||||
encoded_ini_lines = [to_bytes(line) for line in ini_lines]
|
encoded_ini_lines = [to_bytes(line) for line in ini_lines]
|
||||||
try:
|
try:
|
||||||
|
@ -416,10 +428,10 @@ def do_ini(module, filename, section=None, option=None, values=None,
|
||||||
module.fail_json(msg="Unable to create temporary file %s", traceback=traceback.format_exc())
|
module.fail_json(msg="Unable to create temporary file %s", traceback=traceback.format_exc())
|
||||||
|
|
||||||
try:
|
try:
|
||||||
module.atomic_move(tmpfile, filename)
|
module.atomic_move(tmpfile, target_filename)
|
||||||
except IOError:
|
except IOError:
|
||||||
module.ansible.fail_json(msg='Unable to move temporary \
|
module.ansible.fail_json(msg='Unable to move temporary \
|
||||||
file %s to %s, IOError' % (tmpfile, filename), traceback=traceback.format_exc())
|
file %s to %s, IOError' % (tmpfile, target_filename), traceback=traceback.format_exc())
|
||||||
|
|
||||||
return (changed, backup_file, diff, msg)
|
return (changed, backup_file, diff, msg)
|
||||||
|
|
||||||
|
@ -438,7 +450,8 @@ def main():
|
||||||
exclusive=dict(type='bool', default=True),
|
exclusive=dict(type='bool', default=True),
|
||||||
no_extra_spaces=dict(type='bool', default=False),
|
no_extra_spaces=dict(type='bool', default=False),
|
||||||
allow_no_value=dict(type='bool', default=False),
|
allow_no_value=dict(type='bool', default=False),
|
||||||
create=dict(type='bool', default=True)
|
create=dict(type='bool', default=True),
|
||||||
|
follow=dict(type='bool', default=False)
|
||||||
),
|
),
|
||||||
mutually_exclusive=[
|
mutually_exclusive=[
|
||||||
['value', 'values']
|
['value', 'values']
|
||||||
|
@ -458,6 +471,7 @@ def main():
|
||||||
no_extra_spaces = module.params['no_extra_spaces']
|
no_extra_spaces = module.params['no_extra_spaces']
|
||||||
allow_no_value = module.params['allow_no_value']
|
allow_no_value = module.params['allow_no_value']
|
||||||
create = module.params['create']
|
create = module.params['create']
|
||||||
|
follow = module.params['follow']
|
||||||
|
|
||||||
if state == 'present' and not allow_no_value and value is None and not values:
|
if state == 'present' and not allow_no_value and value is None and not values:
|
||||||
module.fail_json(msg="Parameter 'value(s)' must be defined if state=present and allow_no_value=False.")
|
module.fail_json(msg="Parameter 'value(s)' must be defined if state=present and allow_no_value=False.")
|
||||||
|
@ -467,7 +481,7 @@ def main():
|
||||||
elif values is None:
|
elif values is None:
|
||||||
values = []
|
values = []
|
||||||
|
|
||||||
(changed, backup_file, diff, msg) = do_ini(module, path, section, option, values, state, exclusive, backup, no_extra_spaces, create, allow_no_value)
|
(changed, backup_file, diff, msg) = do_ini(module, path, section, option, values, state, exclusive, backup, no_extra_spaces, create, allow_no_value, follow)
|
||||||
|
|
||||||
if not module.check_mode and os.path.exists(path):
|
if not module.check_mode and os.path.exists(path):
|
||||||
file_args = module.load_file_common_arguments(module.params)
|
file_args = module.load_file_common_arguments(module.params)
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
---
|
||||||
|
# Copyright (c) Ansible Project
|
||||||
|
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
- block: &prepare
|
||||||
|
- name: Create the final file
|
||||||
|
ansible.builtin.copy:
|
||||||
|
content: |
|
||||||
|
[main]
|
||||||
|
foo=BAR
|
||||||
|
dest: my_original_file.ini
|
||||||
|
- name: Clean up symlink.ini
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: symlink.ini
|
||||||
|
state: absent
|
||||||
|
- name: Create a symbolic link
|
||||||
|
ansible.builtin.file:
|
||||||
|
src: my_original_file.ini
|
||||||
|
dest: symlink.ini
|
||||||
|
state: link
|
||||||
|
|
||||||
|
- name: Set the proxy key on the symlink which will be converted as a file
|
||||||
|
community.general.ini_file:
|
||||||
|
path: symlink.ini
|
||||||
|
section: main
|
||||||
|
option: proxy
|
||||||
|
value: 'http://proxy.myorg.org:3128'
|
||||||
|
- name: Set the proxy key on the final file that is still unchanged
|
||||||
|
community.general.ini_file:
|
||||||
|
path: my_original_file.ini
|
||||||
|
section: main
|
||||||
|
option: proxy
|
||||||
|
value: 'http://proxy.myorg.org:3128'
|
||||||
|
register: result
|
||||||
|
- ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
|
||||||
|
# With follow
|
||||||
|
- block: *prepare
|
||||||
|
- name: Set the proxy key on the symlink which will be preserved
|
||||||
|
community.general.ini_file:
|
||||||
|
path: symlink.ini
|
||||||
|
section: main
|
||||||
|
option: proxy
|
||||||
|
value: 'http://proxy.myorg.org:3128'
|
||||||
|
follow: true
|
||||||
|
register: result
|
||||||
|
- name: Set the proxy key on the target directly that was changed in the previous step
|
||||||
|
community.general.ini_file:
|
||||||
|
path: my_original_file.ini
|
||||||
|
section: main
|
||||||
|
option: proxy
|
||||||
|
value: 'http://proxy.myorg.org:3128'
|
||||||
|
register: result
|
||||||
|
- ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- "not (result is changed)"
|
Loading…
Reference in a new issue