From 265f5e724cdda586a6f898a9cd69431549f0154c Mon Sep 17 00:00:00 2001 From: Abhijeet Kasurde Date: Tue, 28 Nov 2023 07:20:57 -0800 Subject: [PATCH] apt_repository: Do not convert symlink repo to a normal file (#82271) * apt_repository: Do not convert symlink repo to a normal file * module manually writes to repo files. If the file is a symlink module used to re-write and convert a symlink to a normal file. This fix tracks the symlink and keeps the changes intact. Fixes: #49809 Signed-off-by: Abhijeet Kasurde --- changelogs/fragments/49809_apt_repository.yml | 3 ++ lib/ansible/modules/apt_repository.py | 11 ++++- .../targets/apt_repository/tasks/apt.yml | 40 +++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 changelogs/fragments/49809_apt_repository.yml diff --git a/changelogs/fragments/49809_apt_repository.yml b/changelogs/fragments/49809_apt_repository.yml new file mode 100644 index 00000000000..0a37a7ef183 --- /dev/null +++ b/changelogs/fragments/49809_apt_repository.yml @@ -0,0 +1,3 @@ +--- +bugfixes: +- apt_repository - do not modify repo files if the file is a symlink (https://github.com/ansible/ansible/issues/49809). diff --git a/lib/ansible/modules/apt_repository.py b/lib/ansible/modules/apt_repository.py index 9d26d0b6d7e..fb69f5e5672 100644 --- a/lib/ansible/modules/apt_repository.py +++ b/lib/ansible/modules/apt_repository.py @@ -230,6 +230,7 @@ class SourcesList(object): def __init__(self, module): self.module = module self.files = {} # group sources by file + self.files_mapping = {} # internal DS for tracking symlinks # Repositories that we're adding -- used to implement mode param self.new_repos = set() self.default_file = self._apt_cfg_file('Dir::Etc::sourcelist') @@ -240,6 +241,8 @@ class SourcesList(object): # read sources.list.d for file in glob.iglob('%s/*.list' % self._apt_cfg_dir('Dir::Etc::sourceparts')): + if os.path.islink(file): + self.files_mapping[file] = os.readlink(file) self.load(file) def __iter__(self): @@ -372,7 +375,11 @@ class SourcesList(object): f.write(line) except IOError as ex: self.module.fail_json(msg="Failed to write to file %s: %s" % (tmp_path, to_native(ex))) - self.module.atomic_move(tmp_path, filename) + if filename in self.files_mapping: + # Write to symlink target instead of replacing symlink as a normal file + self.module.atomic_move(tmp_path, self.files_mapping[filename]) + else: + self.module.atomic_move(tmp_path, filename) # allow the user to override the default mode if filename in self.new_repos: @@ -417,7 +424,7 @@ class SourcesList(object): def _add_valid_source(self, source_new, comment_new, file): # We'll try to reuse disabled source if we have it. # If we have more than one entry, we will enable them all - no advanced logic, remember. - self.module.log('ading source file: %s | %s | %s' % (source_new, comment_new, file)) + self.module.log('adding source file: %s | %s | %s' % (source_new, comment_new, file)) found = False for filename, n, enabled, source, comment in self: if source == source_new: diff --git a/test/integration/targets/apt_repository/tasks/apt.yml b/test/integration/targets/apt_repository/tasks/apt.yml index 28bb902913b..afb92cb2126 100644 --- a/test/integration/targets/apt_repository/tasks/apt.yml +++ b/test/integration/targets/apt_repository/tasks/apt.yml @@ -253,6 +253,46 @@ - result is failed - result.msg == 'Please set argument \'repo\' to a non-empty value' +# +# TEST: keep symlink +# +- import_tasks: 'cleanup.yml' + +- name: install local-apt-repository with apt + apt: pkg=local-apt-repository state=present + +- name: Check if local apt repo file is a symlink + stat: + path: /etc/apt/sources.list.d/local-apt-repository.list + register: stat_result + +- name: Assert if local apt repo file is a symlink + assert: + that: + - stat_result.stat.islnk is defined and stat_result.stat.islnk + - stat_result.stat.lnk_source == "/usr/lib/local-apt-repository/local-apt-repository.list" + +- name: Try installing an invalid repo + apt_repository: + repo: deb http://dl.google.com/linux/chrome/deb2/ stable main + state: present + filename: google-chrome + ignore_errors: true + +- name: Check the stat for the given symlink + stat: + path: /etc/apt/sources.list.d/local-apt-repository.list + register: stat_result2 + +- name: Assert that the symlink is intact after apt_repository operation + assert: + that: + - stat_result2.stat.islnk is defined and stat_result2.stat.islnk + - stat_result2.stat.lnk_source == "/usr/lib/local-apt-repository/local-apt-repository.list" + +- name: uninstall local-apt-repository with apt + apt: pkg=local-apt-repository state=absent purge=yes + # # TEARDOWN #