Galaxy server config updates (#77106)

* ansible-galaxy configurable timeouts

  - also fixed issues with precedence,
    so --ignore-certs now overrides config
  - made galaxy_timeout generic setting,
    if set, it becomes default for server configs,
    but now specific servers can override
  - updated tests or added notes (some tests ignore/override precedence)

Co-authored-by: Sloane Hertel <19572925+s-hertel@users.noreply.github.com>
pull/77712/head
Brian Coca 4 years ago committed by GitHub
parent e481b35e23
commit fa35aa4865
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,5 @@
minor_changes:
- ansible-galaxy now supports a user defined timeout, instead of existing hardcoded 60s (now the default).
bugfixes:
- ansible-galaxy --ignore-certs now has proper precedence over configuration
- GALAXY_IGNORE_CERTS reworked to allow each server entry to override

@ -61,6 +61,7 @@ from ansible.utils.plugin_docs import get_versioned_doclink
display = Display() display = Display()
urlparse = six.moves.urllib.parse.urlparse urlparse = six.moves.urllib.parse.urlparse
# config definition by position: name, required, type
SERVER_DEF = [ SERVER_DEF = [
('url', True, 'str'), ('url', True, 'str'),
('username', False, 'str'), ('username', False, 'str'),
@ -70,8 +71,21 @@ SERVER_DEF = [
('v3', False, 'bool'), ('v3', False, 'bool'),
('validate_certs', False, 'bool'), ('validate_certs', False, 'bool'),
('client_id', False, 'str'), ('client_id', False, 'str'),
('timeout', False, 'int'),
] ]
# config definition fields
SERVER_ADDITIONAL = {
'v3': {'default': 'False'},
'validate_certs': {'default': True, 'cli': [{'name': 'validate_certs'}]},
'timeout': {'default': '60', 'cli': [{'name': 'timeout'}]},
'token': {'default': None},
}
# override default if the generic is set
if C.GALAXY_IGNORE_CERTS is not None:
SERVER_ADDITIONAL['validate_certs'].update({'default': not C.GALAXY_IGNORE_CERTS})
def with_collection_artifacts_manager(wrapped_method): def with_collection_artifacts_manager(wrapped_method):
"""Inject an artifacts manager if not passed explicitly. """Inject an artifacts manager if not passed explicitly.
@ -84,7 +98,7 @@ def with_collection_artifacts_manager(wrapped_method):
if 'artifacts_manager' in kwargs: if 'artifacts_manager' in kwargs:
return wrapped_method(*args, **kwargs) return wrapped_method(*args, **kwargs)
artifacts_manager_kwargs = {'validate_certs': not context.CLIARGS['ignore_certs']} artifacts_manager_kwargs = {'validate_certs': context.CLIARGS['validate_certs']}
keyring = context.CLIARGS.get('keyring', None) keyring = context.CLIARGS.get('keyring', None)
if keyring is not None: if keyring is not None:
@ -200,8 +214,10 @@ class GalaxyCLI(CLI):
common.add_argument('--token', '--api-key', dest='api_key', common.add_argument('--token', '--api-key', dest='api_key',
help='The Ansible Galaxy API key which can be found at ' help='The Ansible Galaxy API key which can be found at '
'https://galaxy.ansible.com/me/preferences.') 'https://galaxy.ansible.com/me/preferences.')
common.add_argument('-c', '--ignore-certs', action='store_true', dest='ignore_certs', common.add_argument('-c', '--ignore-certs', action='store_true', dest='ignore_certs', help='Ignore SSL certificate validation errors.', default=None)
default=C.GALAXY_IGNORE_CERTS, help='Ignore SSL certificate validation errors.') 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) opt_help.add_verbosity_options(common)
force = opt_help.argparse.ArgumentParser(add_help=False) force = opt_help.argparse.ArgumentParser(add_help=False)
@ -530,6 +546,10 @@ class GalaxyCLI(CLI):
def post_process_args(self, options): def post_process_args(self, options):
options = super(GalaxyCLI, self).post_process_args(options) options = super(GalaxyCLI, self).post_process_args(options)
# ensure we have 'usable' cli option
setattr(options, 'validate_certs', (None if options.ignore_certs is None else not options.ignore_certs))
display.verbosity = options.verbosity display.verbosity = options.verbosity
return options return options
@ -540,7 +560,7 @@ class GalaxyCLI(CLI):
self.galaxy = Galaxy() self.galaxy = Galaxy()
def server_config_def(section, key, required, option_type): def server_config_def(section, key, required, option_type):
return { config_def = {
'description': 'The %s of the %s Galaxy server' % (key, section), 'description': 'The %s of the %s Galaxy server' % (key, section),
'ini': [ 'ini': [
{ {
@ -554,10 +574,13 @@ class GalaxyCLI(CLI):
'required': required, 'required': required,
'type': option_type, 'type': option_type,
} }
if key in SERVER_ADDITIONAL:
config_def.update(SERVER_ADDITIONAL[key])
return config_def
validate_certs_fallback = not context.CLIARGS['ignore_certs']
galaxy_options = {} galaxy_options = {}
for optional_key in ['clear_response_cache', 'no_cache']: for optional_key in ['clear_response_cache', 'no_cache', 'timeout']:
if optional_key in context.CLIARGS: if optional_key in context.CLIARGS:
galaxy_options[optional_key] = context.CLIARGS[optional_key] galaxy_options[optional_key] = context.CLIARGS[optional_key]
@ -566,25 +589,25 @@ class GalaxyCLI(CLI):
# Need to filter out empty strings or non truthy values as an empty server list env var is equal to ['']. # Need to filter out empty strings or non truthy values as an empty server list env var is equal to [''].
server_list = [s for s in C.GALAXY_SERVER_LIST or [] if s] server_list = [s for s in C.GALAXY_SERVER_LIST or [] if s]
for server_priority, server_key in enumerate(server_list, start=1): for server_priority, server_key in enumerate(server_list, start=1):
# Abuse the 'plugin config' by making 'galaxy_server' a type of plugin
# Config definitions are looked up dynamically based on the C.GALAXY_SERVER_LIST entry. We look up the # Config definitions are looked up dynamically based on the C.GALAXY_SERVER_LIST entry. We look up the
# section [galaxy_server.<server>] for the values url, username, password, and token. # section [galaxy_server.<server>] for the values url, username, password, and token.
config_dict = dict((k, server_config_def(server_key, k, req, ensure_type)) for k, req, ensure_type in SERVER_DEF) config_dict = dict((k, server_config_def(server_key, k, req, ensure_type)) for k, req, ensure_type in SERVER_DEF)
defs = AnsibleLoader(yaml_dump(config_dict)).get_single_data() defs = AnsibleLoader(yaml_dump(config_dict)).get_single_data()
C.config.initialize_plugin_configuration_definitions('galaxy_server', server_key, defs) C.config.initialize_plugin_configuration_definitions('galaxy_server', server_key, defs)
# resolve the config created options above with existing config and user options
server_options = C.config.get_plugin_options('galaxy_server', server_key) server_options = C.config.get_plugin_options('galaxy_server', server_key)
# auth_url is used to create the token, but not directly by GalaxyAPI, so # auth_url is used to create the token, but not directly by GalaxyAPI, so
# it doesn't need to be passed as kwarg to GalaxyApi # it doesn't need to be passed as kwarg to GalaxyApi, same for others we pop here
auth_url = server_options.pop('auth_url', None) auth_url = server_options.pop('auth_url')
client_id = server_options.pop('client_id', None) client_id = server_options.pop('client_id')
token_val = server_options['token'] or NoTokenSentinel token_val = server_options['token'] or NoTokenSentinel
username = server_options['username'] username = server_options['username']
available_api_versions = None v3 = server_options.pop('v3')
v3 = server_options.pop('v3', None)
validate_certs = server_options['validate_certs'] validate_certs = server_options['validate_certs']
if validate_certs is None:
validate_certs = validate_certs_fallback
server_options['validate_certs'] = validate_certs
if v3: if v3:
# This allows a user to explicitly indicate the server uses the /v3 API # This allows a user to explicitly indicate the server uses the /v3 API
# This was added for testing against pulp_ansible and I'm not sure it has # This was added for testing against pulp_ansible and I'm not sure it has
@ -596,8 +619,7 @@ class GalaxyCLI(CLI):
server_options['token'] = None server_options['token'] = None
if username: if username:
server_options['token'] = BasicAuthToken(username, server_options['token'] = BasicAuthToken(username, server_options['password'])
server_options['password'])
else: else:
if token_val: if token_val:
if auth_url: if auth_url:
@ -618,6 +640,10 @@ class GalaxyCLI(CLI):
cmd_server = context.CLIARGS['api_server'] cmd_server = context.CLIARGS['api_server']
cmd_token = GalaxyToken(token=context.CLIARGS['api_key']) cmd_token = GalaxyToken(token=context.CLIARGS['api_key'])
# resolve validate_certs
v_config_default = True if C.GALAXY_IGNORE_CERTS is None else not C.GALAXY_IGNORE_CERTS
validate_certs = v_config_default if context.CLIARGS['validate_certs'] is None else context.CLIARGS['validate_certs']
if cmd_server: if cmd_server:
# Cmd args take precedence over the config entry but fist check if the arg was a name and use that config # 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. # entry, otherwise create a new API entry for the server specified.
@ -628,7 +654,7 @@ class GalaxyCLI(CLI):
self.api_servers.append(GalaxyAPI( self.api_servers.append(GalaxyAPI(
self.galaxy, 'cmd_arg', cmd_server, token=cmd_token, self.galaxy, 'cmd_arg', cmd_server, token=cmd_token,
priority=len(config_servers) + 1, priority=len(config_servers) + 1,
validate_certs=validate_certs_fallback, validate_certs=validate_certs,
**galaxy_options **galaxy_options
)) ))
else: else:
@ -639,7 +665,7 @@ class GalaxyCLI(CLI):
self.api_servers.append(GalaxyAPI( self.api_servers.append(GalaxyAPI(
self.galaxy, 'default', C.GALAXY_SERVER, token=cmd_token, self.galaxy, 'default', C.GALAXY_SERVER, token=cmd_token,
priority=0, priority=0,
validate_certs=validate_certs_fallback, validate_certs=validate_certs,
**galaxy_options **galaxy_options
)) ))

@ -1330,7 +1330,6 @@ FACTS_MODULES:
- name: ansible_facts_modules - name: ansible_facts_modules
GALAXY_IGNORE_CERTS: GALAXY_IGNORE_CERTS:
name: Galaxy validate certs name: Galaxy validate certs
default: False
description: description:
- If set to yes, ansible-galaxy will not validate TLS certificates. - If set to yes, ansible-galaxy will not validate TLS certificates.
This can be useful for testing against a server with a self-signed certificate. This can be useful for testing against a server with a self-signed certificate.

@ -269,6 +269,7 @@ class GalaxyAPI:
self.timeout = timeout self.timeout = timeout
self._available_api_versions = available_api_versions or {} self._available_api_versions = available_api_versions or {}
self._priority = priority self._priority = priority
self._server_timeout = timeout
b_cache_dir = to_bytes(C.GALAXY_CACHE_DIR, errors='surrogate_or_strict') b_cache_dir = to_bytes(C.GALAXY_CACHE_DIR, errors='surrogate_or_strict')
makedirs_safe(b_cache_dir, mode=0o700) makedirs_safe(b_cache_dir, mode=0o700)
@ -378,7 +379,7 @@ class GalaxyAPI:
try: try:
display.vvvv("Calling Galaxy at %s" % url) display.vvvv("Calling Galaxy at %s" % url)
resp = open_url(to_native(url), data=args, validate_certs=self.validate_certs, headers=headers, resp = open_url(to_native(url), data=args, validate_certs=self.validate_certs, headers=headers,
method=method, timeout=self.timeout, http_agent=user_agent(), follow_redirects='safe') method=method, timeout=self._server_timeout, http_agent=user_agent(), follow_redirects='safe')
except HTTPError as e: except HTTPError as e:
raise GalaxyError(e, error_context_msg) raise GalaxyError(e, error_context_msg)
except Exception as e: except Exception as e:
@ -436,7 +437,14 @@ class GalaxyAPI:
""" """
url = _urljoin(self.api_server, self.available_api_versions['v1'], "tokens") + '/' url = _urljoin(self.api_server, self.available_api_versions['v1'], "tokens") + '/'
args = urlencode({"github_token": github_token}) args = urlencode({"github_token": github_token})
resp = open_url(url, data=args, validate_certs=self.validate_certs, method="POST", http_agent=user_agent(), timeout=self.timeout)
try:
resp = open_url(url, data=args, validate_certs=self.validate_certs, method="POST", http_agent=user_agent(), timeout=self._server_timeout)
except HTTPError as e:
raise GalaxyError(e, 'Attempting to authenticate to galaxy')
except Exception as e:
raise AnsibleError('Unable to authenticate to galaxy: %s' % to_native(e), orig_exc=e)
data = json.loads(to_text(resp.read(), errors='surrogate_or_strict')) data = json.loads(to_text(resp.read(), errors='surrogate_or_strict'))
return data return data

@ -206,7 +206,7 @@ class ConcreteArtifactsManager:
validate_certs=self._validate_certs, validate_certs=self._validate_certs,
timeout=self.timeout timeout=self.timeout
) )
except URLError as err: except Exception as err:
raise_from( raise_from(
AnsibleError( AnsibleError(
'Failed to download collection tar ' 'Failed to download collection tar '

@ -363,6 +363,8 @@ def test_validate_certs_with_server_url(global_ignore_certs, monkeypatch):
@pytest.mark.parametrize('global_ignore_certs', [True, False]) @pytest.mark.parametrize('global_ignore_certs', [True, False])
def test_validate_certs_with_server_config(global_ignore_certs, server_config, monkeypatch): def test_validate_certs_with_server_config(global_ignore_certs, server_config, monkeypatch):
# test sidesteps real resolution and forces the server config to override the cli option
get_plugin_options = MagicMock(side_effect=server_config) get_plugin_options = MagicMock(side_effect=server_config)
monkeypatch.setattr(C.config, 'get_plugin_options', get_plugin_options) monkeypatch.setattr(C.config, 'get_plugin_options', get_plugin_options)
@ -380,9 +382,10 @@ def test_validate_certs_with_server_config(global_ignore_certs, server_config, m
monkeypatch.setattr(galaxy_cli, '_execute_install_collection', mock_execute_install) monkeypatch.setattr(galaxy_cli, '_execute_install_collection', mock_execute_install)
galaxy_cli.run() galaxy_cli.run()
assert galaxy_cli.api_servers[0].validate_certs is False # server cfg, so should match def above, if not specified so it should use default (true)
assert galaxy_cli.api_servers[1].validate_certs is True assert galaxy_cli.api_servers[0].validate_certs is server_config[0].get('validate_certs', True)
assert galaxy_cli.api_servers[2].validate_certs is not global_ignore_certs assert galaxy_cli.api_servers[1].validate_certs is server_config[1].get('validate_certs', True)
assert galaxy_cli.api_servers[2].validate_certs is server_config[2].get('validate_certs', True)
def test_build_collection_no_galaxy_yaml(): def test_build_collection_no_galaxy_yaml():

Loading…
Cancel
Save