{"record":{"id":"9a42458d24791460","repo":"ansible/ansible","slug":"invalid-non-absolute-download-url-data-download","errorCode":null,"errorMessage":"Invalid non absolute download_url: {data[\"download_url\"]}","messagePattern":"Invalid non absolute download_url: (.+?)","errorType":"exception","errorClass":"AnsibleError","httpStatus":null,"severity":"error","filePath":"lib/ansible/galaxy/api.py","lineNumber":795,"sourceCode":"        n_collection_url = _urljoin(*url_paths)\n        error_context_msg = 'Error when getting collection version metadata for %s.%s:%s from %s (%s)' \\\n                            % (namespace, name, version, self.name, self.api_server)\n        data = self._call_galaxy(n_collection_url, error_context_msg=error_context_msg, cache=True)\n        self._set_cache()\n\n        signatures = data.get('signatures') or []\n\n        # NOTE: Galaxy and Hub already populated the cache when listing versions.\n        # NOTE: Allow 3rd party servers to provide version-specific metadata lazily.\n        if (requires_ansible := data.get('requires_ansible')):\n            self.requires_ansible[f\"{namespace}.{name}\"][version] = requires_ansible\n\n        download_url_info = urlparse(data['download_url'])\n        if not download_url_info.scheme and not download_url_info.path.startswith('/'):\n            # galaxy does a lot of redirects, with much more complex pathing than we use\n            # within this codebase, without updating _call_galaxy to be able to return\n            # the final URL, we can't reliably build a relative URL.\n            raise AnsibleError(f'Invalid non absolute download_url: {data[\"download_url\"]}')\n\n        download_url = urljoin(self.api_server, data['download_url'])\n\n        return CollectionVersionMetadata(data['namespace']['name'], data['collection']['name'], data['version'],\n                                         download_url, data['artifact']['sha256'],\n                                         data['metadata']['dependencies'], data['href'], signatures)\n\n    @g_connect(['v3'])\n    def get_collection_versions(self, namespace, name):\n        \"\"\"\n        Gets a list of available versions for a collection on a Galaxy server.\n\n        :param namespace: The collection namespace.\n        :param name: The collection name.\n        :return: A list of versions that are available.\n        \"\"\"\n        api_path = self.available_api_versions['v3']\n        pagination_path = ['links', 'next']","sourceCodeStart":777,"sourceCodeEnd":813,"githubUrl":"https://github.com/ansible/ansible/blob/9cf16a4aca7898481c257f1e17ad28d0b67b1f85/lib/ansible/galaxy/api.py#L777-L813","documentation":"Raised in the CollectionVersionMetadata construction path (get_collection_version_metadata) when a Galaxy server returns a version payload whose 'download_url' has neither a scheme nor a path starting with '/'. Because _call_galaxy cannot return the final redirected URL, a relative URL that is not root-relative cannot be reliably joined onto api_server, so ansible refuses it rather than download from a wrong URL.","triggerScenarios":"Installing a collection from a 3rd-party Galaxy-compatible server whose API returns a relative download_url like 'download/xyz' (no leading slash and no scheme). Standard galaxy.ansible.com and automation hub return absolute URLs, so this almost always indicates a nonconforming or misconfigured proxy/galaxy_ng deployment.","commonSituations":"Custom in-house Galaxy implementations; reverse proxies that rewrite Location/download URLs and strip the leading slash; installing from a requirements.yml pointing at such a server via -s/--server.","solutions":["Fix the server (or its reverse proxy) to return an absolute URL or a root-relative path ('/api/v2/...') in download_url","Update galaxy_ng to a version that conforms to the published API contract","As a workaround, download the tarball manually and 'ansible-galaxy collection install <tarball>'","If a proxy rewrites URLs, disable that rewrite for the Galaxy API paths"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"from urllib.parse import urlparse\n\ndef server_download_urls_look_valid(sample_version_payload):\n    u = urlparse(sample_version_payload['download_url'])\n    return bool(u.scheme) or u.path.startswith('/')","typeGuard":null,"tryCatchPattern":null,"preventionTips":["When onboarding a third-party Galaxy server, run 'ansible-galaxy collection list -s <server>' as a smoke test to exercise download_url and pagination early","Keep galaxy_ng and its reverse proxy on supported versions and test after proxy config changes"],"tags":["ansible-galaxy","install","download-url","server-side","third-party-server"],"backgroundTag":null,"analyzedSha":"9cf16a4aca7898481c257f1e17ad28d0b67b1f85","analyzedAt":"2026-08-15T00:15:47.100Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}