From 8cf7a0d3f0fc71bb148ceb6501851b39fe6a6f68 Mon Sep 17 00:00:00 2001 From: sbettid Date: Thu, 6 Oct 2022 19:53:23 +0200 Subject: [PATCH] Fix collection install from source respects symlinks (#78983) * Fix installation from source transforms symlinks of dirs to empty dirs * Add test to check symlinks to dirs are respected when installing from source * Add changelog for collection install from source symlink to dirs issue --- ...all-from-source-respects-dir-symlinks.yaml | 2 + lib/ansible/galaxy/collection/__init__.py | 15 ++++-- .../tasks/install.yml | 50 +++++++++++++++++++ 3 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 changelogs/fragments/78983-fix-collection-install-from-source-respects-dir-symlinks.yaml diff --git a/changelogs/fragments/78983-fix-collection-install-from-source-respects-dir-symlinks.yaml b/changelogs/fragments/78983-fix-collection-install-from-source-respects-dir-symlinks.yaml new file mode 100644 index 00000000000..5b8974e5d4c --- /dev/null +++ b/changelogs/fragments/78983-fix-collection-install-from-source-respects-dir-symlinks.yaml @@ -0,0 +1,2 @@ +bugfixes: + - ansible-galaxy collection install - respect symlinks when installing from source or local repository (https://github.com/ansible/ansible/issues/78442) diff --git a/lib/ansible/galaxy/collection/__init__.py b/lib/ansible/galaxy/collection/__init__.py index 7f04052e89f..0b6edc2f318 100644 --- a/lib/ansible/galaxy/collection/__init__.py +++ b/lib/ansible/galaxy/collection/__init__.py @@ -1372,17 +1372,24 @@ def _build_collection_dir(b_collection_path, b_collection_output, collection_man src_file = os.path.join(b_collection_path, to_bytes(file_info['name'], errors='surrogate_or_strict')) dest_file = os.path.join(b_collection_output, to_bytes(file_info['name'], errors='surrogate_or_strict')) - existing_is_exec = os.stat(src_file).st_mode & stat.S_IXUSR + existing_is_exec = os.stat(src_file, follow_symlinks=False).st_mode & stat.S_IXUSR mode = 0o0755 if existing_is_exec else 0o0644 - if os.path.isdir(src_file): + # ensure symlinks to dirs are not translated to empty dirs + if os.path.isdir(src_file) and not os.path.islink(src_file): mode = 0o0755 base_directories.append(src_file) os.mkdir(dest_file, mode) else: - shutil.copyfile(src_file, dest_file) + # do not follow symlinks to ensure the original link is used + shutil.copyfile(src_file, dest_file, follow_symlinks=False) + + # avoid setting specific permission on symlinks since it does not + # support avoid following symlinks and will thrown an exception if the + # symlink target does not exist + if not os.path.islink(dest_file): + os.chmod(dest_file, mode) - os.chmod(dest_file, mode) collection_output = to_text(b_collection_output) return collection_output diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/install.yml b/test/integration/targets/ansible-galaxy-collection/tasks/install.yml index 8916faf5ba6..a225d6a3ba1 100644 --- a/test/integration/targets/ansible-galaxy-collection/tasks/install.yml +++ b/test/integration/targets/ansible-galaxy-collection/tasks/install.yml @@ -771,6 +771,56 @@ - install_symlink_actual.results[5].stat.islnk - install_symlink_actual.results[5].stat.lnk_target == '../REÅDMÈ.md' + +# Testing an install from source to check that symlinks to directories +# are preserved (see issue https://github.com/ansible/ansible/issues/78442) +- name: symlink_dirs collection install from source test + block: + + - name: create symlink_dirs collection + command: ansible-galaxy collection init symlink_dirs.symlink_dirs --init-path "{{ galaxy_dir }}/scratch" + + - name: create directory in collection + file: + path: "{{ galaxy_dir }}/scratch/symlink_dirs/symlink_dirs/folderA" + state: directory + + - name: create symlink to folderA + file: + dest: "{{ galaxy_dir }}/scratch/symlink_dirs/symlink_dirs/folderB" + src: ./folderA + state: link + force: yes + + - name: install symlink_dirs collection from source + command: ansible-galaxy collection install {{ galaxy_dir }}/scratch/symlink_dirs/ + environment: + ANSIBLE_COLLECTIONS_PATHS: '{{ galaxy_dir }}/ansible_collections' + register: install_symlink_dirs + + - name: get result of install collection with symlink_dirs - {{ test_id }} + stat: + path: '{{ galaxy_dir }}/ansible_collections/symlink_dirs/symlink_dirs/{{ path }}' + register: install_symlink_dirs_actual + loop_control: + loop_var: path + loop: + - folderA + - folderB + + - name: assert install collection with symlink_dirs - {{ test_id }} + assert: + that: + - '"Installing ''symlink_dirs.symlink_dirs:1.0.0'' to" in install_symlink_dirs.stdout' + - install_symlink_dirs_actual.results[0].stat.isdir + - install_symlink_dirs_actual.results[1].stat.islnk + - install_symlink_dirs_actual.results[1].stat.lnk_target == './folderA' + always: + - name: clean up symlink_dirs collection directory + file: + path: "{{ galaxy_dir }}/scratch/symlink_dirs" + state: absent + - name: remove install directory for the next test because parent_dep.parent_collection was installed - {{ test_id }} file: path: '{{ galaxy_dir }}/ansible_collections'