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

Merged
merged 28 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
102 changes: 85 additions & 17 deletions lib/ansible/cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from ansible import constants as C
from ansible.cli.arguments import option_helpers as opt_help
from ansible.config.manager import ConfigManager, Setting
from ansible.errors import AnsibleError, AnsibleOptionsError
from ansible.errors import AnsibleError, AnsibleOptionsError, AnsibleRequiredOptionError
from ansible.module_utils.common.text.converters import to_native, to_text, to_bytes
from ansible.module_utils.common.json import json_dump
from ansible.module_utils.six import string_types
Expand All @@ -35,6 +35,9 @@
display = Display()


_IGNORE_CHANGED = frozenset({'_terms', '_input'})
bcoca marked this conversation as resolved.
Show resolved Hide resolved


def yaml_dump(data, default_flow_style=False, default_style=None):
return yaml.dump(data, Dumper=AnsibleDumper, default_flow_style=default_flow_style, default_style=default_style)

Expand Down Expand Up @@ -149,6 +152,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 +269,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] = self.config.get_configuration_definitions('galaxy_server', server)

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

Expand Down Expand Up @@ -445,13 +458,13 @@ def _render_settings(self, config):

entries = []
for setting in sorted(config):
changed = (config[setting].origin not in ('default', 'REQUIRED'))
changed = (config[setting].origin not in ('default', 'REQUIRED') and setting not in _IGNORE_CHANGED)

if context.CLIARGS['format'] == 'display':
if isinstance(config[setting], Setting):
# proceed normally
value = config[setting].value
if config[setting].origin == 'default':
if config[setting].origin == 'default' or setting in _IGNORE_CHANGED:
color = 'green'
value = self.config.template_default(value, get_constants())
elif config[setting].origin == 'REQUIRED':
Expand All @@ -468,6 +481,8 @@ def _render_settings(self, config):
else:
entry = {}
for key in config[setting]._fields:
if key == 'type':
continue
entry[key] = getattr(config[setting], key)

if not context.CLIARGS['only_changed'] or changed:
Expand All @@ -476,7 +491,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 @@ -528,12 +546,9 @@ def _get_plugin_configs(self, ptype, plugins):
for setting in config_entries[finalname].keys():
try:
v, o = C.config.get_config_value_and_origin(setting, cfile=self.config_file, plugin_type=ptype, plugin_name=name, variables=get_constants())
except AnsibleError as e:
if to_text(e).startswith('No setting was provided for required configuration'):
v = None
o = 'REQUIRED'
else:
raise e
except AnsibleRequiredOptionError:
v = None
o = 'REQUIRED'

if v is None and o is None:
# not all cases will be error
Expand All @@ -553,17 +568,60 @@ 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 = {}
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 s_config[setting].get('required', False):
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(server)
output.append(f'\n{server}\n{equals}')
output.extend(self._render_settings(server_config))
else:
output.append({server: 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
server_config_list = self._get_galaxy_server_configs()
if context.CLIARGS['format'] == 'display':
output.append('\nGALAXY_SERVERS:\n')
output.extend(server_config_list)
else:
configs = {}
for server_config in server_config_list:
server = list(server_config.keys())[0]
server_reduced_config = server_config.pop(server)
configs[server] = server_reduced_config
output.append({'GALAXY_SERVERS': 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 +634,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 +653,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 +670,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
Original file line number Diff line number Diff line change
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