galaxy.py (ansible-2.14.0) | : | galaxy.py (ansible-2.14.1rc1) | ||
---|---|---|---|---|
skipping to change at line 20 | skipping to change at line 20 | |||
# ansible.cli needs to be imported first, to ensure the source bin/* scripts run that code first | # ansible.cli needs to be imported first, to ensure the source bin/* scripts run that code first | |||
from ansible.cli import CLI | from ansible.cli import CLI | |||
import json | import json | |||
import os.path | import os.path | |||
import re | import re | |||
import shutil | import shutil | |||
import sys | import sys | |||
import textwrap | import textwrap | |||
import time | import time | |||
import typing as t | ||||
from dataclasses import dataclass | ||||
from yaml.error import YAMLError | from yaml.error import YAMLError | |||
import ansible.constants as C | import ansible.constants as C | |||
from ansible import context | from ansible import context | |||
from ansible.cli.arguments import option_helpers as opt_help | from ansible.cli.arguments import option_helpers as opt_help | |||
from ansible.errors import AnsibleError, AnsibleOptionsError | from ansible.errors import AnsibleError, AnsibleOptionsError | |||
from ansible.galaxy import Galaxy, get_collections_galaxy_meta_info | from ansible.galaxy import Galaxy, get_collections_galaxy_meta_info | |||
from ansible.galaxy.api import GalaxyAPI | from ansible.galaxy.api import GalaxyAPI | |||
from ansible.galaxy.collection import ( | from ansible.galaxy.collection import ( | |||
build_collection, | build_collection, | |||
skipping to change at line 166 | skipping to change at line 168 | |||
return fqcn_length, version_length | return fqcn_length, version_length | |||
def validate_signature_count(value): | def validate_signature_count(value): | |||
match = re.match(SIGNATURE_COUNT_RE, value) | match = re.match(SIGNATURE_COUNT_RE, value) | |||
if match is None: | if match is None: | |||
raise ValueError(f"{value} is not a valid signature count value") | raise ValueError(f"{value} is not a valid signature count value") | |||
return value | return value | |||
@dataclass | ||||
class RoleDistributionServer: | ||||
_api: t.Union[GalaxyAPI, None] | ||||
api_servers: list[GalaxyAPI] | ||||
@property | ||||
def api(self): | ||||
if self._api: | ||||
return self._api | ||||
for server in self.api_servers: | ||||
try: | ||||
if u'v1' in server.available_api_versions: | ||||
self._api = server | ||||
break | ||||
except Exception: | ||||
continue | ||||
if not self._api: | ||||
self._api = self.api_servers[0] | ||||
return self._api | ||||
class GalaxyCLI(CLI): | class GalaxyCLI(CLI): | |||
'''command to manage Ansible roles in shared repositories, the default of wh ich is Ansible Galaxy *https://galaxy.ansible.com*.''' | '''command to manage Ansible roles in shared repositories, the default of wh ich is Ansible Galaxy *https://galaxy.ansible.com*.''' | |||
name = 'ansible-galaxy' | name = 'ansible-galaxy' | |||
SKIP_INFO_KEYS = ("name", "description", "readme_html", "related", "summary_ fields", "average_aw_composite", "average_aw_score", "url") | SKIP_INFO_KEYS = ("name", "description", "readme_html", "related", "summary_ fields", "average_aw_composite", "average_aw_score", "url") | |||
def __init__(self, args): | def __init__(self, args): | |||
self._raw_args = args | self._raw_args = args | |||
self._implicit_role = False | self._implicit_role = False | |||
skipping to change at line 194 | skipping to change at line 219 | |||
if args[1:3] == ['role', 'login']: | if args[1:3] == ['role', 'login']: | |||
display.error( | display.error( | |||
"The login command was removed in late 2020. An API key is n ow required to publish roles or collections " | "The login command was removed in late 2020. An API key is n ow required to publish roles or collections " | |||
"to Galaxy. The key can be found at https://galaxy.ansible.c om/me/preferences, and passed to the " | "to Galaxy. The key can be found at https://galaxy.ansible.c om/me/preferences, and passed to the " | |||
"ansible-galaxy CLI via a file at {0} or (insecurely) via th e `--token` " | "ansible-galaxy CLI via a file at {0} or (insecurely) via th e `--token` " | |||
"command-line argument.".format(to_text(C.GALAXY_TOKEN_PATH) )) | "command-line argument.".format(to_text(C.GALAXY_TOKEN_PATH) )) | |||
sys.exit(1) | sys.exit(1) | |||
self.api_servers = [] | self.api_servers = [] | |||
self.galaxy = None | self.galaxy = None | |||
self._api = None | self.lazy_role_api = None | |||
super(GalaxyCLI, self).__init__(args) | super(GalaxyCLI, self).__init__(args) | |||
def init_parser(self): | def init_parser(self): | |||
''' create an options parser for bin/ansible ''' | ''' create an options parser for bin/ansible ''' | |||
super(GalaxyCLI, self).init_parser( | super(GalaxyCLI, self).init_parser( | |||
desc="Perform various Role and Collection related operations.", | desc="Perform various Role and Collection related operations.", | |||
) | ) | |||
# Common arguments that apply to more than 1 action | # Common arguments that apply to more than 1 action | |||
skipping to change at line 674 | skipping to change at line 699 | |||
# Default to C.GALAXY_SERVER if no servers were defined | # Default to C.GALAXY_SERVER if no servers were defined | |||
if len(self.api_servers) == 0: | if len(self.api_servers) == 0: | |||
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, | validate_certs=validate_certs, | |||
**galaxy_options | **galaxy_options | |||
)) | )) | |||
# checks api versions once a GalaxyRole makes an api call | ||||
# self.api can be used to evaluate the best server immediately | ||||
self.lazy_role_api = RoleDistributionServer(None, self.api_servers) | ||||
return context.CLIARGS['func']() | return context.CLIARGS['func']() | |||
@property | @property | |||
def api(self): | def api(self): | |||
if self._api: | return self.lazy_role_api.api | |||
return self._api | ||||
for server in self.api_servers: | ||||
try: | ||||
if u'v1' in server.available_api_versions: | ||||
self._api = server | ||||
break | ||||
except Exception: | ||||
continue | ||||
if not self._api: | ||||
self._api = self.api_servers[0] | ||||
return self._api | ||||
def _get_default_collection_path(self): | def _get_default_collection_path(self): | |||
return C.COLLECTIONS_PATHS[0] | return C.COLLECTIONS_PATHS[0] | |||
def _parse_requirements_file(self, requirements_file, allow_old_format=True, artifacts_manager=None, validate_signature_options=True): | def _parse_requirements_file(self, requirements_file, allow_old_format=True, artifacts_manager=None, validate_signature_options=True): | |||
""" | """ | |||
Parses an Ansible requirement.yml file and returns all the roles and/or collections defined in it. There are 2 | Parses an Ansible requirement.yml file and returns all the roles and/or collections defined in it. There are 2 | |||
requirements file format: | requirements file format: | |||
# v1 (roles only) | # v1 (roles only) | |||
skipping to change at line 753 | skipping to change at line 768 | |||
if file_requirements is None: | if file_requirements is None: | |||
raise AnsibleError("No requirements found in file '%s'" % to_native( requirements_file)) | raise AnsibleError("No requirements found in file '%s'" % to_native( requirements_file)) | |||
def parse_role_req(requirement): | def parse_role_req(requirement): | |||
if "include" not in requirement: | if "include" not in requirement: | |||
role = RoleRequirement.role_yaml_parse(requirement) | role = RoleRequirement.role_yaml_parse(requirement) | |||
display.vvv("found role %s in yaml file" % to_text(role)) | display.vvv("found role %s in yaml file" % to_text(role)) | |||
if "name" not in role and "src" not in role: | if "name" not in role and "src" not in role: | |||
raise AnsibleError("Must specify name or src for role") | raise AnsibleError("Must specify name or src for role") | |||
return [GalaxyRole(self.galaxy, self.api, **role)] | return [GalaxyRole(self.galaxy, self.lazy_role_api, **role)] | |||
else: | else: | |||
b_include_path = to_bytes(requirement["include"], errors="surrog ate_or_strict") | b_include_path = to_bytes(requirement["include"], errors="surrog ate_or_strict") | |||
if not os.path.isfile(b_include_path): | if not os.path.isfile(b_include_path): | |||
raise AnsibleError("Failed to find include requirements file '%s' in '%s'" | raise AnsibleError("Failed to find include requirements file '%s' in '%s'" | |||
% (to_native(b_include_path), to_native(r equirements_file))) | % (to_native(b_include_path), to_native(r equirements_file))) | |||
with open(b_include_path, 'rb') as f_include: | with open(b_include_path, 'rb') as f_include: | |||
try: | try: | |||
return [GalaxyRole(self.galaxy, self.api, **r) for r in | return [GalaxyRole(self.galaxy, self.lazy_role_api, **r) for r in | |||
(RoleRequirement.role_yaml_parse(i) for i in yam l_load(f_include))] | (RoleRequirement.role_yaml_parse(i) for i in yam l_load(f_include))] | |||
except Exception as e: | except Exception as e: | |||
raise AnsibleError("Unable to load data from include req uirements file: %s %s" | raise AnsibleError("Unable to load data from include req uirements file: %s %s" | |||
% (to_native(requirements_file), to_n ative(e))) | % (to_native(requirements_file), to_n ative(e))) | |||
if isinstance(file_requirements, list): | if isinstance(file_requirements, list): | |||
# Older format that contains only roles | # Older format that contains only roles | |||
if not allow_old_format: | if not allow_old_format: | |||
raise AnsibleError("Expecting requirements file to be a dict wit h the key 'collections' that contains " | raise AnsibleError("Expecting requirements file to be a dict wit h the key 'collections' that contains " | |||
"a list of collections to install") | "a list of collections to install") | |||
skipping to change at line 1178 | skipping to change at line 1193 | |||
""" | """ | |||
prints out detailed information about an installed role as well as info available from the galaxy API. | prints out detailed information about an installed role as well as info available from the galaxy API. | |||
""" | """ | |||
roles_path = context.CLIARGS['roles_path'] | roles_path = context.CLIARGS['roles_path'] | |||
data = '' | data = '' | |||
for role in context.CLIARGS['args']: | for role in context.CLIARGS['args']: | |||
role_info = {'path': roles_path} | role_info = {'path': roles_path} | |||
gr = GalaxyRole(self.galaxy, self.api, role) | gr = GalaxyRole(self.galaxy, self.lazy_role_api, role) | |||
install_info = gr.install_info | install_info = gr.install_info | |||
if install_info: | if install_info: | |||
if 'version' in install_info: | if 'version' in install_info: | |||
install_info['installed_version'] = install_info['version'] | install_info['installed_version'] = install_info['version'] | |||
del install_info['version'] | del install_info['version'] | |||
role_info.update(install_info) | role_info.update(install_info) | |||
if not context.CLIARGS['offline']: | if not context.CLIARGS['offline']: | |||
remote_data = None | remote_data = None | |||
skipping to change at line 1323 | skipping to change at line 1338 | |||
display_func = display.warning if self._implicit_role else d isplay.vvv | display_func = display.warning if self._implicit_role else d isplay.vvv | |||
display_func(two_type_warning.format('collection')) | display_func(two_type_warning.format('collection')) | |||
else: | else: | |||
collection_path = self._get_default_collection_path() | collection_path = self._get_default_collection_path() | |||
collection_requirements = requirements['collections'] | collection_requirements = requirements['collections'] | |||
else: | else: | |||
# roles were specified directly, so we'll just go out grab them | # roles were specified directly, so we'll just go out grab them | |||
# (and their dependencies, unless the user doesn't want us to). | # (and their dependencies, unless the user doesn't want us to). | |||
for rname in context.CLIARGS['args']: | for rname in context.CLIARGS['args']: | |||
role = RoleRequirement.role_yaml_parse(rname.strip()) | role = RoleRequirement.role_yaml_parse(rname.strip()) | |||
role_requirements.append(GalaxyRole(self.galaxy, self.api, * *role)) | role_requirements.append(GalaxyRole(self.galaxy, self.lazy_r ole_api, **role)) | |||
if not role_requirements and not collection_requirements: | if not role_requirements and not collection_requirements: | |||
display.display("Skipping install, no requirements found") | display.display("Skipping install, no requirements found") | |||
return | return | |||
if role_requirements: | if role_requirements: | |||
display.display("Starting galaxy role install process") | display.display("Starting galaxy role install process") | |||
self._execute_install_role(role_requirements) | self._execute_install_role(role_requirements) | |||
if collection_requirements: | if collection_requirements: | |||
skipping to change at line 1434 | skipping to change at line 1449 | |||
if not no_deps and installed: | if not no_deps and installed: | |||
if not role.metadata: | if not role.metadata: | |||
# NOTE: the meta file is also required for installing the ro le, not just dependencies | # NOTE: the meta file is also required for installing the ro le, not just dependencies | |||
display.warning("Meta file %s is empty. Skipping dependencie s." % role.path) | display.warning("Meta file %s is empty. Skipping dependencie s." % role.path) | |||
else: | else: | |||
role_dependencies = role.metadata_dependencies + role.requir ements | role_dependencies = role.metadata_dependencies + role.requir ements | |||
for dep in role_dependencies: | for dep in role_dependencies: | |||
display.debug('Installing dep %s' % dep) | display.debug('Installing dep %s' % dep) | |||
dep_req = RoleRequirement() | dep_req = RoleRequirement() | |||
dep_info = dep_req.role_yaml_parse(dep) | dep_info = dep_req.role_yaml_parse(dep) | |||
dep_role = GalaxyRole(self.galaxy, self.api, **dep_info) | dep_role = GalaxyRole(self.galaxy, self.lazy_role_api, * *dep_info) | |||
if '.' not in dep_role.name and '.' not in dep_role.src and dep_role.scm is None: | if '.' not in dep_role.name and '.' not in dep_role.src and dep_role.scm is None: | |||
# we know we can skip this, as it's not going to | # we know we can skip this, as it's not going to | |||
# be found on galaxy.ansible.com | # be found on galaxy.ansible.com | |||
continue | continue | |||
if dep_role.install_info is None: | if dep_role.install_info is None: | |||
if dep_role not in requirements: | if dep_role not in requirements: | |||
display.display('- adding dependency: %s' % to_t ext(dep_role)) | display.display('- adding dependency: %s' % to_t ext(dep_role)) | |||
requirements.append(dep_role) | requirements.append(dep_role) | |||
else: | else: | |||
display.display('- dependency %s already pending installation.' % dep_role.name) | display.display('- dependency %s already pending installation.' % dep_role.name) | |||
skipping to change at line 1518 | skipping to change at line 1533 | |||
for path in roles_search_paths: | for path in roles_search_paths: | |||
role_path = GalaxyCLI._resolve_path(path) | role_path = GalaxyCLI._resolve_path(path) | |||
if os.path.isdir(path): | if os.path.isdir(path): | |||
path_found = True | path_found = True | |||
else: | else: | |||
warnings.append("- the configured path {0} does not exist.".form at(path)) | warnings.append("- the configured path {0} does not exist.".form at(path)) | |||
continue | continue | |||
if role_name: | if role_name: | |||
# show the requested role, if it exists | # show the requested role, if it exists | |||
gr = GalaxyRole(self.galaxy, self.api, role_name, path=os.path.j oin(role_path, role_name)) | gr = GalaxyRole(self.galaxy, self.lazy_role_api, role_name, path =os.path.join(role_path, role_name)) | |||
if os.path.isdir(gr.path): | if os.path.isdir(gr.path): | |||
role_found = True | role_found = True | |||
display.display('# %s' % os.path.dirname(gr.path)) | display.display('# %s' % os.path.dirname(gr.path)) | |||
_display_role(gr) | _display_role(gr) | |||
break | break | |||
warnings.append("- the role %s was not found" % role_name) | warnings.append("- the role %s was not found" % role_name) | |||
else: | else: | |||
if not os.path.exists(role_path): | if not os.path.exists(role_path): | |||
warnings.append("- the configured path %s does not exist." % role_path) | warnings.append("- the configured path %s does not exist." % role_path) | |||
continue | continue | |||
if not os.path.isdir(role_path): | if not os.path.isdir(role_path): | |||
warnings.append("- the configured path %s, exists, but it is not a directory." % role_path) | warnings.append("- the configured path %s, exists, but it is not a directory." % role_path) | |||
continue | continue | |||
display.display('# %s' % role_path) | display.display('# %s' % role_path) | |||
path_files = os.listdir(role_path) | path_files = os.listdir(role_path) | |||
for path_file in path_files: | for path_file in path_files: | |||
gr = GalaxyRole(self.galaxy, self.api, path_file, path=path) | gr = GalaxyRole(self.galaxy, self.lazy_role_api, path_file, path=path) | |||
if gr.metadata: | if gr.metadata: | |||
_display_role(gr) | _display_role(gr) | |||
# Do not warn if the role was found in any of the search paths | # Do not warn if the role was found in any of the search paths | |||
if role_found and role_name: | if role_found and role_name: | |||
warnings = [] | warnings = [] | |||
for w in warnings: | for w in warnings: | |||
display.warning(w) | display.warning(w) | |||
End of changes. 13 change blocks. | ||||
23 lines changed or deleted | 38 lines changed or added |