diff --git a/changelogs/fragments/a-g-col-prevent-reinstalling-satisfied-req.yml b/changelogs/fragments/a-g-col-prevent-reinstalling-satisfied-req.yml new file mode 100644 index 00000000000..9ac32c5d970 --- /dev/null +++ b/changelogs/fragments/a-g-col-prevent-reinstalling-satisfied-req.yml @@ -0,0 +1,7 @@ +bugfixes: +- >- + ``ansible-galaxy`` now considers all collection paths when identifying which collection requirements are already installed. + Use the ``COLLECTIONS_PATHS`` and ``COLLECTIONS_SCAN_SYS_PATHS`` config options to modify these. + Previously only the install path was considered when resolving the candidates. + The install path will remain the only one potentially modified. + (https://github.com/ansible/ansible/issues/79767, https://github.com/ansible/ansible/issues/81163) diff --git a/lib/ansible/cli/galaxy.py b/lib/ansible/cli/galaxy.py index 8d1ccfcd133..d8a7c03756b 100755 --- a/lib/ansible/cli/galaxy.py +++ b/lib/ansible/cli/galaxy.py @@ -1397,7 +1397,19 @@ class GalaxyCLI(CLI): upgrade = context.CLIARGS.get('upgrade', False) collections_path = C.COLLECTIONS_PATHS - if len([p for p in collections_path if p.startswith(path)]) == 0: + + managed_paths = set(validate_collection_path(p) for p in C.COLLECTIONS_PATHS) + read_req_paths = set(validate_collection_path(p) for p in AnsibleCollectionConfig.collection_paths) + + unexpected_path = not any(p.startswith(path) for p in managed_paths) + if unexpected_path and any(p.startswith(path) for p in read_req_paths): + display.warning( + f"The specified collections path '{path}' appears to be part of the pip Ansible package. " + "Managing these directly with ansible-galaxy could break the Ansible package. " + "Install collections to a configured collections path, which will take precedence over " + "collections found in the PYTHONPATH." + ) + elif unexpected_path: display.warning("The specified collections path '%s' is not part of the configured Ansible " "collections paths '%s'. The installed collection will not be picked up in an Ansible " "run, unless within a playbook-adjacent collections directory." % (to_text(path), to_text(":".join(collections_path)))) @@ -1414,6 +1426,7 @@ class GalaxyCLI(CLI): artifacts_manager=artifacts_manager, disable_gpg_verify=disable_gpg_verify, offline=context.CLIARGS.get('offline', False), + read_requirement_paths=read_req_paths, ) return 0 diff --git a/lib/ansible/galaxy/collection/__init__.py b/lib/ansible/galaxy/collection/__init__.py index 056871b44e5..14e2b935c84 100644 --- a/lib/ansible/galaxy/collection/__init__.py +++ b/lib/ansible/galaxy/collection/__init__.py @@ -654,6 +654,7 @@ def install_collections( artifacts_manager, # type: ConcreteArtifactsManager disable_gpg_verify, # type: bool offline, # type: bool + read_requirement_paths, # type: set[str] ): # type: (...) -> None """Install Ansible collections to the path specified. @@ -668,7 +669,8 @@ def install_collections( """ existing_collections = { Requirement(coll.fqcn, coll.ver, coll.src, coll.type, None) - for coll in find_existing_collections(output_path, artifacts_manager) + for path in {output_path} | read_requirement_paths + for coll in find_existing_collections(path, artifacts_manager) } unsatisfied_requirements = set( diff --git a/test/integration/targets/ansible-galaxy-collection-scm/tasks/multi_collection_repo_all.yml b/test/integration/targets/ansible-galaxy-collection-scm/tasks/multi_collection_repo_all.yml index f22f98447d8..91ed9124120 100644 --- a/test/integration/targets/ansible-galaxy-collection-scm/tasks/multi_collection_repo_all.yml +++ b/test/integration/targets/ansible-galaxy-collection-scm/tasks/multi_collection_repo_all.yml @@ -14,6 +14,8 @@ command: 'ansible-galaxy collection install {{ artifact_path }} -p {{ alt_install_path }} --no-deps' vars: artifact_path: "{{ galaxy_dir }}/ansible_test-collection_1-1.0.0.tar.gz" + environment: + ANSIBLE_COLLECTIONS_PATH: "" - name: check if the files and folders in build_ignore were respected stat: diff --git a/test/units/galaxy/test_collection_install.py b/test/units/galaxy/test_collection_install.py index f8fb6d57ed2..c88de0f572d 100644 --- a/test/units/galaxy/test_collection_install.py +++ b/test/units/galaxy/test_collection_install.py @@ -894,7 +894,8 @@ def test_install_collections_from_tar(collection_artifact, monkeypatch): concrete_artifact_cm = collection.concrete_artifact_manager.ConcreteArtifactsManager(temp_path, validate_certs=False) requirements = [Requirement('ansible_namespace.collection', '0.1.0', to_text(collection_tar), 'file', None)] - collection.install_collections(requirements, to_text(temp_path), [], False, False, False, False, False, False, concrete_artifact_cm, True, False) + collection.install_collections( + requirements, to_text(temp_path), [], False, False, False, False, False, False, concrete_artifact_cm, True, False, set()) assert os.path.isdir(collection_path) @@ -932,7 +933,8 @@ def test_install_collection_with_circular_dependency(collection_artifact, monkey concrete_artifact_cm = collection.concrete_artifact_manager.ConcreteArtifactsManager(temp_path, validate_certs=False) requirements = [Requirement('ansible_namespace.collection', '0.1.0', to_text(collection_tar), 'file', None)] - collection.install_collections(requirements, to_text(temp_path), [], False, False, False, False, False, False, concrete_artifact_cm, True, False) + collection.install_collections( + requirements, to_text(temp_path), [], False, False, False, False, False, False, concrete_artifact_cm, True, False, set()) assert os.path.isdir(collection_path) @@ -969,7 +971,8 @@ def test_install_collection_with_no_dependency(collection_artifact, monkeypatch) concrete_artifact_cm = collection.concrete_artifact_manager.ConcreteArtifactsManager(temp_path, validate_certs=False) requirements = [Requirement('ansible_namespace.collection', '0.1.0', to_text(collection_tar), 'file', None)] - collection.install_collections(requirements, to_text(temp_path), [], False, False, False, False, False, False, concrete_artifact_cm, True, False) + collection.install_collections( + requirements, to_text(temp_path), [], False, False, False, False, False, False, concrete_artifact_cm, True, False, set()) assert os.path.isdir(collection_path)