Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

config, integrate dynamic galaxy servers #83129

Open
wants to merge 20 commits into
base: devel
Choose a base branch
from
73 changes: 65 additions & 8 deletions lib/ansible/cli/config.py
Expand Up @@ -149,6 +149,10 @@ def run(self):

super(ConfigCLI, self).run()

# initialize each galaxy server's options from known listed servers
self._galaxy_servers = [s for s in C.GALAXY_SERVER_LIST or [] if s] # clean list, reused later here
C.config.load_galaxy_server_defs(self._galaxy_servers)

if context.CLIARGS['config_file']:
self.config_file = unfrackpath(context.CLIARGS['config_file'], follow=False)
b_config = to_bytes(self.config_file)
Expand Down Expand Up @@ -262,11 +266,17 @@ def _list_entries_from_args(self):
'''
build a dict with the list requested configs
'''

config_entries = {}
if context.CLIARGS['type'] in ('base', 'all'):
# this dumps main/common configs
config_entries = self.config.get_configuration_definitions(ignore_private=True)

# for base and all, we include galaxy servers
config_entries['GALAXY_SERVERS'] = {}
for server in self._galaxy_servers:
config_entries['GALAXY_SERVERS'][server.upper()] = self.config.get_configuration_definitions('galaxy_server', server)
bcoca marked this conversation as resolved.
Show resolved Hide resolved

if context.CLIARGS['type'] != 'base':
config_entries['PLUGINS'] = {}
bcoca marked this conversation as resolved.
Show resolved Hide resolved

Expand Down Expand Up @@ -476,7 +486,10 @@ def _render_settings(self, config):
return entries

def _get_global_configs(self):
config = self.config.get_configuration_definitions(ignore_private=True).copy()

# Add base
config = self.config.get_configuration_definitions(ignore_private=True)
# convert to settings
for setting in config.keys():
v, o = C.config.get_config_value_and_origin(setting, cfile=self.config_file, variables=get_constants())
config[setting] = Setting(setting, v, o, None)
Expand Down Expand Up @@ -553,17 +566,51 @@ def _get_plugin_configs(self, ptype, plugins):

return output

def _get_galaxy_server_configs(self):

output = []
# add galaxy servers
for server in self._galaxy_servers:
server_config = {}
name = f'GALAXY_SERVER.{server.upper()}'
s_config = self.config.get_configuration_definitions('galaxy_server', server)
for setting in s_config.keys():
try:
v, o = C.config.get_config_value_and_origin(setting, plugin_type='galaxy_server', plugin_name=server, cfile=self.config_file)
except AnsibleError as e:
if to_text(e).startswith('No setting was provided for required configuration'):
v = None
o = 'REQUIRED'
else:
raise e
if v is None and o is None:
# not all cases will be error
o = 'REQUIRED'
server_config[setting] = Setting(setting, v, o, None)
if context.CLIARGS['format'] == 'display':
if not context.CLIARGS['only_changed'] or server_config:
equals = '=' * len(name)
output.append(f'\n{name}\n{equals}')
output.extend(self._render_settings(server_config))
else:
output.append({name: server_config})

return output

def execute_dump(self):
'''
Shows the current settings, merges ansible.cfg if specified
'''
if context.CLIARGS['type'] == 'base':
# deal with base
output = self._get_global_configs()
elif context.CLIARGS['type'] == 'all':
output = []
if context.CLIARGS['type'] in ('base', 'all'):
# deal with base
output = self._get_global_configs()
# deal with plugins

# add galaxy servers
output.extend(self._get_galaxy_server_configs())

if context.CLIARGS['type'] == 'all':
# add all plugins
for ptype in C.CONFIGURABLE_PLUGINS:
plugin_list = self._get_plugin_configs(ptype, context.CLIARGS['args'])
if context.CLIARGS['format'] == 'display':
Expand All @@ -576,8 +623,9 @@ def execute_dump(self):
else:
pname = '%s_PLUGINS' % ptype.upper()
output.append({pname: plugin_list})
else:
# deal with plugins

elif context.CLIARGS['type'] != 'base':
# deal with specific plugin
output = self._get_plugin_configs(context.CLIARGS['type'], context.CLIARGS['args'])

if context.CLIARGS['format'] == 'display':
Expand All @@ -594,6 +642,7 @@ def execute_validate(self):
found = False
config_entries = self._list_entries_from_args()
plugin_types = config_entries.pop('PLUGINS', None)
galaxy_servers = config_entries.pop('GALAXY_SERVERS', None)

if context.CLIARGS['format'] == 'ini':
if C.CONFIG_FILE is not None:
Expand All @@ -610,6 +659,14 @@ def execute_validate(self):
sections[s].update(plugin_sections[s])
else:
sections[s] = plugin_sections[s]
if galaxy_servers:
for server in galaxy_servers:
server_sections = _get_ini_entries(galaxy_servers[server])
for s in server_sections:
if s in sections:
sections[s].update(server_sections[s])
else:
sections[s] = server_sections[s]
if sections:
p = C.config._parsers[C.CONFIG_FILE]
for s in p.sections():
Expand Down
52 changes: 3 additions & 49 deletions lib/ansible/cli/galaxy.py
Expand Up @@ -55,7 +55,6 @@
from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
from ansible.module_utils import six
from ansible.parsing.dataloader import DataLoader
from ansible.parsing.yaml.loader import AnsibleLoader
from ansible.playbook.role.requirement import RoleRequirement
from ansible.template import Templar
from ansible.utils.collection_loader import AnsibleCollectionConfig
Expand All @@ -66,27 +65,6 @@
display = Display()
urlparse = six.moves.urllib.parse.urlparse

# config definition by position: name, required, type
SERVER_DEF = [
('url', True, 'str'),
('username', False, 'str'),
('password', False, 'str'),
('token', False, 'str'),
('auth_url', False, 'str'),
('api_version', False, 'int'),
('validate_certs', False, 'bool'),
('client_id', False, 'str'),
('timeout', False, 'int'),
]

# config definition fields
SERVER_ADDITIONAL = {
'api_version': {'default': None, 'choices': [2, 3]},
'validate_certs': {'cli': [{'name': 'validate_certs'}]},
'timeout': {'default': C.GALAXY_SERVER_TIMEOUT, 'cli': [{'name': 'timeout'}]},
'token': {'default': None},
}


def with_collection_artifacts_manager(wrapped_method):
"""Inject an artifacts manager if not passed explicitly.
Expand Down Expand Up @@ -618,45 +596,21 @@ def run(self):

self.galaxy = Galaxy()

def server_config_def(section, key, required, option_type):
config_def = {
'description': 'The %s of the %s Galaxy server' % (key, section),
'ini': [
{
'section': 'galaxy_server.%s' % section,
'key': key,
}
],
'env': [
{'name': 'ANSIBLE_GALAXY_SERVER_%s_%s' % (section.upper(), key.upper())},
],
'required': required,
'type': option_type,
}
if key in SERVER_ADDITIONAL:
config_def.update(SERVER_ADDITIONAL[key])

return config_def
# dynamically add per server config depending on declared servers
C.config.load_galaxy_server_defs(C.GALAXY_SERVER_LIST)

galaxy_options = {}
for optional_key in ['clear_response_cache', 'no_cache']:
if optional_key in context.CLIARGS:
galaxy_options[optional_key] = context.CLIARGS[optional_key]

config_servers = []

# 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]
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
# 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)
defs = AnsibleLoader(yaml_dump(config_dict)).get_single_data()
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(plugin_type='galaxy_server', name=server_key)

# 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, same for others we pop here
Expand Down
65 changes: 60 additions & 5 deletions lib/ansible/config/manager.py
Expand Up @@ -29,6 +29,26 @@

INTERNAL_DEFS = {'lookup': ('_terms',)}

GALAXY_SERVER_DEF = [
('url', True, 'str'),
('username', False, 'str'),
('password', False, 'str'),
('token', False, 'str'),
('auth_url', False, 'str'),
('api_version', False, 'int'),
('validate_certs', False, 'bool'),
('client_id', False, 'str'),
('timeout', False, 'int'),
]

# config definition fields
GALAXY_SERVER_ADDITIONAL = {
'api_version': {'default': None, 'choices': [2, 3]},
'validate_certs': {'cli': [{'name': 'validate_certs'}]},
'timeout': {'cli': [{'name': 'timeout'}]},
'token': {'default': None},
}


def _get_entry(plugin_type, plugin_name, config):
''' construct entry for requested config '''
Expand Down Expand Up @@ -302,6 +322,42 @@ def __init__(self, conf_file=None, defs_file=None):
# ensure we always have config def entry
self._base_defs['CONFIG_FILE'] = {'default': None, 'type': 'path'}

def load_galaxy_server_defs(self, server_list):

def server_config_def(section, key, required, option_type):
config_def = {
'description': 'The %s of the %s Galaxy server' % (key, section),
'ini': [
{
'section': 'galaxy_server.%s' % section,
'key': key,
}
],
'env': [
{'name': 'ANSIBLE_GALAXY_SERVER_%s_%s' % (section.upper(), key.upper())},
],
'required': required,
'type': option_type,
}
if key in GALAXY_SERVER_ADDITIONAL:
config_def.update(GALAXY_SERVER_ADDITIONAL[key])
# ensure we always have a default timeout
if key == 'timeout' and 'default' not in config_def:
config_def['default'] = self.get_config_value('GALAXY_SERVER_TIMEOUT')

return config_def

if server_list:
for server_key in server_list:
if not server_key:
# To filter out empty strings or non truthy values as an empty server list env var is equal to [''].
continue

# 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.
defs = dict((k, server_config_def(server_key, k, req, value_type)) for k, req, value_type in GALAXY_SERVER_DEF)
self.initialize_plugin_configuration_definitions('galaxy_server', server_key, defs)

def template_default(self, value, variables):
if isinstance(value, string_types) and (value.startswith('{{') and value.endswith('}}')) and variables is not None:
# template default values if possible
Expand Down Expand Up @@ -357,7 +413,7 @@ def _find_yaml_config_files(self):
def get_plugin_options(self, plugin_type, name, keys=None, variables=None, direct=None):

options = {}
defs = self.get_configuration_definitions(plugin_type, name)
defs = self.get_configuration_definitions(plugin_type=plugin_type, name=name)
for option in defs:
options[option] = self.get_config_value(option, plugin_type=plugin_type, plugin_name=name, keys=keys, variables=variables, direct=direct)

Expand All @@ -366,7 +422,7 @@ def get_plugin_options(self, plugin_type, name, keys=None, variables=None, direc
def get_plugin_vars(self, plugin_type, name):

pvars = []
for pdef in self.get_configuration_definitions(plugin_type, name).values():
for pdef in self.get_configuration_definitions(plugin_type=plugin_type, name=name).values():
if 'vars' in pdef and pdef['vars']:
for var_entry in pdef['vars']:
pvars.append(var_entry['name'])
Expand All @@ -375,7 +431,7 @@ def get_plugin_vars(self, plugin_type, name):
def get_plugin_options_from_var(self, plugin_type, name, variable):

options = []
for option_name, pdef in self.get_configuration_definitions(plugin_type, name).items():
for option_name, pdef in self.get_configuration_definitions(plugin_type=plugin_type, name=name).items():
if 'vars' in pdef and pdef['vars']:
for var_entry in pdef['vars']:
if variable == var_entry['name']:
Expand Down Expand Up @@ -417,7 +473,6 @@ def get_configuration_definitions(self, plugin_type=None, name=None, ignore_priv
for cdef in list(ret.keys()):
if cdef.startswith('_'):
del ret[cdef]

return ret

def _loop_entries(self, container, entry_list):
Expand Down Expand Up @@ -472,7 +527,7 @@ def get_config_value_and_origin(self, config, cfile=None, plugin_type=None, plug
origin = None
origin_ftype = None

defs = self.get_configuration_definitions(plugin_type, plugin_name)
defs = self.get_configuration_definitions(plugin_type=plugin_type, name=plugin_name)
if config in defs:

aliases = defs[config].get('aliases', [])
Expand Down
21 changes: 21 additions & 0 deletions test/integration/targets/ansible-config/files/galaxy_server.ini
@@ -0,0 +1,21 @@
[galaxy]
server_list = my_org_hub, release_galaxy, test_galaxy, my_galaxy_ng

[galaxy_server.my_org_hub]
url=https://automation.my_org/
username=my_user
password=my_pass

[galaxy_server.release_galaxy]
url=https://galaxy.ansible.com/
token=my_token

[galaxy_server.test_galaxy]
url=https://galaxy-dev.ansible.com/
token=my_test_token

[galaxy_server.my_galaxy_ng]
url=http://my_galaxy_ng:8000/api/automation-hub/
auth_url=http://my_keycloak:8080/auth/realms/myco/protocol/openid-connect/token
client_id=galaxy-ng
token=my_keycloak_access_token