diff --git a/changelogs/fragments/85918-handle-missing-results.yml b/changelogs/fragments/85918-handle-missing-results.yml new file mode 100644 index 00000000000..313178d2037 --- /dev/null +++ b/changelogs/fragments/85918-handle-missing-results.yml @@ -0,0 +1,6 @@ +--- +bugfixes: + - galaxy - previously, some corrupted cache files could cause Ansible Galaxy to fail + with a traceback. This has been corrected to display a clear error message explaining + how to resolve the problem. + (https://github.com/ansible/ansible/issues/85918) diff --git a/lib/ansible/galaxy/api.py b/lib/ansible/galaxy/api.py index 23c1e307f83..e145687b70f 100644 --- a/lib/ansible/galaxy/api.py +++ b/lib/ansible/galaxy/api.py @@ -363,7 +363,15 @@ class GalaxyAPI: res['results'].append(result) else: - res = path_cache['results'] + try: + res = path_cache['results'] + except KeyError as cache_miss_error: + raise AnsibleError( + f"Missing expected 'results' in ansible-galaxy cache: {path_cache!r}. " + "This may indicate cache corruption (for example, from concurrent ansible-galaxy runs) " + "or a bug in how the cache was generated. " + "Try running with --clear-response-cache or --no-cache to work around the issue." + ) from cache_miss_error return res diff --git a/test/units/galaxy/test_api.py b/test/units/galaxy/test_api.py index 58bda34f713..a2dbf0b1abc 100644 --- a/test/units/galaxy/test_api.py +++ b/test/units/galaxy/test_api.py @@ -1265,3 +1265,37 @@ def test_clear_cache(cache_dir): def test_cache_id(url, expected): actual = galaxy_api.get_cache_id(url) assert actual == expected + + +def test_cache_missing_results_raises_descriptive_error(mocker): + api = GalaxyAPI(None, "test", "https://galaxy.ansible.com/api/") + + mock_cache_entry = { + 'expires': '2023-01-02T12:00:00Z', + 'paginated': False + } + + api._cache = {'galaxy.ansible.com': {'/api/v1/roles/': mock_cache_entry}} + + mocker.patch('ansible.galaxy.api.urlparse') + mocker.patch('ansible.galaxy.api.parse_qs', return_value={}) + mocker.patch('ansible.galaxy.api.get_cache_id', + return_value='galaxy.ansible.com') + + mock_datetime = mocker.patch('ansible.galaxy.api.datetime') + + mock_comparison = mocker.Mock() + mock_comparison.__bool__ = mocker.Mock(return_value=True) + + mock_now = mocker.Mock() + mock_expires = mocker.Mock() + mock_now.__lt__ = mocker.Mock(return_value=True) + mock_datetime.datetime.now.return_value = mock_now + mock_datetime.datetime.strptime.return_value = mock_expires + + with pytest.raises(AnsibleError, match="Missing expected 'results' in ansible-galaxy cache"): + api._call_galaxy( + url='https://galaxy.ansible.com/api/v1/roles/', + cache=True, + cache_key='/api/v1/roles/' + )