From e780b5e56da2de4b0fa4ae554eb5568794731d49 Mon Sep 17 00:00:00 2001 From: Sloane Hertel <19572925+s-hertel@users.noreply.github.com> Date: Mon, 26 Jun 2023 13:10:41 -0400 Subject: [PATCH] ansible-galaxy - add config option for the default ansible-galaxy timeout (#81108) allow configuring the default server timeout via env/ini --- .../ansible-galaxy-server-timeout.yml | 2 + lib/ansible/cli/galaxy.py | 13 +++-- lib/ansible/config/base.yml | 9 ++++ test/units/galaxy/test_collection.py | 50 +++++++++++++++++++ 4 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 changelogs/fragments/ansible-galaxy-server-timeout.yml diff --git a/changelogs/fragments/ansible-galaxy-server-timeout.yml b/changelogs/fragments/ansible-galaxy-server-timeout.yml new file mode 100644 index 00000000000..77b19ada990 --- /dev/null +++ b/changelogs/fragments/ansible-galaxy-server-timeout.yml @@ -0,0 +1,2 @@ +minor_changes: + - Add a general ``GALAXY_SERVER_TIMEOUT`` config option for distribution servers (https://github.com/ansible/ansible/issues/79833). diff --git a/lib/ansible/cli/galaxy.py b/lib/ansible/cli/galaxy.py index 70472c90929..ea0b779f076 100755 --- a/lib/ansible/cli/galaxy.py +++ b/lib/ansible/cli/galaxy.py @@ -82,7 +82,7 @@ SERVER_DEF = [ SERVER_ADDITIONAL = { 'api_version': {'default': None, 'choices': [2, 3]}, 'validate_certs': {'cli': [{'name': 'validate_certs'}]}, - 'timeout': {'default': '60', 'cli': [{'name': 'timeout'}]}, + 'timeout': {'default': C.GALAXY_SERVER_TIMEOUT, 'cli': [{'name': 'timeout'}]}, 'token': {'default': None}, } @@ -246,7 +246,11 @@ class GalaxyCLI(CLI): help='The Ansible Galaxy API key which can be found at ' 'https://galaxy.ansible.com/me/preferences.') common.add_argument('-c', '--ignore-certs', action='store_true', dest='ignore_certs', help='Ignore SSL certificate validation errors.', default=None) - common.add_argument('--timeout', dest='timeout', type=int, default=60, + + # --timeout uses the default None to handle two different scenarios. + # * --timeout > C.GALAXY_SERVER_TIMEOUT for non-configured servers + # * --timeout > server-specific timeout > C.GALAXY_SERVER_TIMEOUT for configured servers. + common.add_argument('--timeout', dest='timeout', type=int, help="The time to wait for operations against the galaxy server, defaults to 60s.") opt_help.add_verbosity_options(common) @@ -621,7 +625,7 @@ class GalaxyCLI(CLI): return config_def galaxy_options = {} - for optional_key in ['clear_response_cache', 'no_cache', 'timeout']: + for optional_key in ['clear_response_cache', 'no_cache']: if optional_key in context.CLIARGS: galaxy_options[optional_key] = context.CLIARGS[optional_key] @@ -697,6 +701,7 @@ class GalaxyCLI(CLI): cmd_token = GalaxyToken(token=context.CLIARGS['api_key']) validate_certs = context.CLIARGS['resolved_validate_certs'] + default_server_timeout = context.CLIARGS['timeout'] if context.CLIARGS['timeout'] is not None else C.GALAXY_SERVER_TIMEOUT if cmd_server: # Cmd args take precedence over the config entry but fist check if the arg was a name and use that config # entry, otherwise create a new API entry for the server specified. @@ -708,6 +713,7 @@ class GalaxyCLI(CLI): self.galaxy, 'cmd_arg', cmd_server, token=cmd_token, priority=len(config_servers) + 1, validate_certs=validate_certs, + timeout=default_server_timeout, **galaxy_options )) else: @@ -719,6 +725,7 @@ class GalaxyCLI(CLI): self.galaxy, 'default', C.GALAXY_SERVER, token=cmd_token, priority=0, validate_certs=validate_certs, + timeout=default_server_timeout, **galaxy_options )) diff --git a/lib/ansible/config/base.yml b/lib/ansible/config/base.yml index 5049fa344a4..97d1b432423 100644 --- a/lib/ansible/config/base.yml +++ b/lib/ansible/config/base.yml @@ -1342,6 +1342,15 @@ GALAXY_IGNORE_CERTS: ini: - {key: ignore_certs, section: galaxy} type: boolean +GALAXY_SERVER_TIMEOUT: + name: Default timeout to use for API calls + description: + - The default timeout for Galaxy API calls. Galaxy servers that don't configure a specific timeout will fall back to this value. + env: [{name: ANSIBLE_GALAXY_SERVER_TIMEOUT}] + default: 60 + ini: + - {key: server_timeout, section: galaxy} + type: int GALAXY_ROLE_SKELETON: name: Galaxy role skeleton directory description: Role skeleton directory to use as a template for the ``init`` action in ``ansible-galaxy``/``ansible-galaxy role``, same as ``--role-skeleton``. diff --git a/test/units/galaxy/test_collection.py b/test/units/galaxy/test_collection.py index 54b54ada56c..6f0622a7b8e 100644 --- a/test/units/galaxy/test_collection.py +++ b/test/units/galaxy/test_collection.py @@ -20,6 +20,7 @@ from unittest.mock import MagicMock, mock_open, patch import ansible.constants as C from ansible import context +from ansible.cli import galaxy from ansible.cli.galaxy import GalaxyCLI from ansible.errors import AnsibleError from ansible.galaxy import api, collection, token @@ -383,6 +384,55 @@ def test_validate_certs_server_config(ignore_certs_cfg, ignore_certs_cli, expect assert galaxy_cli.api_servers[2].validate_certs is expected_server3_validate_certs +@pytest.mark.parametrize( + ["timeout_cli", "timeout_cfg", "timeout_fallback", "expected_timeout"], + [ + (None, None, None, 60), + (None, None, 10, 10), + (None, 20, 10, 20), + (30, 20, 10, 30), + ] +) +def test_timeout_server_config(timeout_cli, timeout_cfg, timeout_fallback, expected_timeout, monkeypatch): + cli_args = [ + 'ansible-galaxy', + 'collection', + 'install', + 'namespace.collection:1.0.0', + ] + if timeout_cli is not None: + cli_args.extend(["--timeout", f"{timeout_cli}"]) + + cfg_lines = ["[galaxy]", "server_list=server1"] + if timeout_fallback is not None: + cfg_lines.append(f"server_timeout={timeout_fallback}") + + # fix default in server config since C.GALAXY_SERVER_TIMEOUT was already evaluated + server_additional = galaxy.SERVER_ADDITIONAL.copy() + server_additional['timeout']['default'] = timeout_fallback + monkeypatch.setattr(galaxy, 'SERVER_ADDITIONAL', server_additional) + + cfg_lines.extend(["[galaxy_server.server1]", "url=https://galaxy.ansible.com/api/"]) + if timeout_cfg is not None: + cfg_lines.append(f"timeout={timeout_cfg}") + + monkeypatch.setattr(C, 'GALAXY_SERVER_LIST', ['server1']) + + with tempfile.NamedTemporaryFile(suffix='.cfg') as tmp_file: + tmp_file.write(to_bytes('\n'.join(cfg_lines), errors='surrogate_or_strict')) + tmp_file.flush() + + monkeypatch.setattr(C.config, '_config_file', tmp_file.name) + C.config._parse_config_file() + + galaxy_cli = GalaxyCLI(args=cli_args) + mock_execute_install = MagicMock() + monkeypatch.setattr(galaxy_cli, '_execute_install_collection', mock_execute_install) + galaxy_cli.run() + + assert galaxy_cli.api_servers[0].timeout == expected_timeout + + def test_build_collection_no_galaxy_yaml(): fake_path = u'/fake/ÅÑŚÌβŁÈ/path' expected = to_native("The collection galaxy.yml path '%s/galaxy.yml' does not exist." % fake_path)