diff options
-rw-r--r-- | changelogs/fragments/ansible-galaxy-install-delay-initial-api-call.yml | 2 | ||||
-rwxr-xr-x | lib/ansible/cli/galaxy.py | 62 | ||||
-rw-r--r-- | lib/ansible/galaxy/role.py | 9 | ||||
-rw-r--r-- | test/integration/targets/ansible-galaxy-role/tasks/main.yml | 3 | ||||
-rwxr-xr-x | test/integration/targets/ansible-galaxy/runme.sh | 6 |
5 files changed, 57 insertions, 25 deletions
diff --git a/changelogs/fragments/ansible-galaxy-install-delay-initial-api-call.yml b/changelogs/fragments/ansible-galaxy-install-delay-initial-api-call.yml new file mode 100644 index 0000000000..10e8eb8926 --- /dev/null +++ b/changelogs/fragments/ansible-galaxy-install-delay-initial-api-call.yml @@ -0,0 +1,2 @@ +bugfixes: + - ansible-galaxy - make initial call to Galaxy server on-demand only when installing, getting info about, and listing roles. diff --git a/lib/ansible/cli/galaxy.py b/lib/ansible/cli/galaxy.py index 5acaa6e46a..c08d2ec60f 100755 --- a/lib/ansible/cli/galaxy.py +++ b/lib/ansible/cli/galaxy.py @@ -17,7 +17,9 @@ import shutil import sys import textwrap import time +import typing as t +from dataclasses import dataclass from yaml.error import YAMLError import ansible.constants as C @@ -156,6 +158,30 @@ def validate_signature_count(value): return value +@dataclass +class RoleDistributionServer: + _api: t.Union[GalaxyAPI, None] + api_servers: t.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): '''command to manage Ansible roles in shared repositories, the default of which is Ansible Galaxy *https://galaxy.ansible.com*.''' @@ -186,7 +212,7 @@ class GalaxyCLI(CLI): self.api_servers = [] self.galaxy = None - self._api = None + self.lazy_role_api = None super(GalaxyCLI, self).__init__(args) def init_parser(self): @@ -649,25 +675,15 @@ class GalaxyCLI(CLI): **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']() @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 + return self.lazy_role_api.api def _get_default_collection_path(self): return C.COLLECTIONS_PATHS[0] @@ -728,7 +744,7 @@ class GalaxyCLI(CLI): display.vvv("found role %s in yaml file" % to_text(role)) if "name" not in role and "src" not in 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: b_include_path = to_bytes(requirement["include"], errors="surrogate_or_strict") if not os.path.isfile(b_include_path): @@ -737,7 +753,7 @@ class GalaxyCLI(CLI): with open(b_include_path, 'rb') as f_include: 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 yaml_load(f_include))] except Exception as e: raise AnsibleError("Unable to load data from include requirements file: %s %s" @@ -1152,7 +1168,7 @@ class GalaxyCLI(CLI): for role in context.CLIARGS['args']: 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 if install_info: @@ -1294,7 +1310,7 @@ class GalaxyCLI(CLI): # (and their dependencies, unless the user doesn't want us to). for rname in context.CLIARGS['args']: role = RoleRequirement.role_yaml_parse(rname.strip()) - role_requirements.append(GalaxyRole(self.galaxy, self.api, **role)) + role_requirements.append(GalaxyRole(self.galaxy, self.lazy_role_api, **role)) if not role_requirements and not collection_requirements: display.display("Skipping install, no requirements found") @@ -1403,7 +1419,7 @@ class GalaxyCLI(CLI): display.debug('Installing dep %s' % dep) dep_req = RoleRequirement() 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: # we know we can skip this, as it's not going to # be found on galaxy.ansible.com @@ -1487,7 +1503,7 @@ class GalaxyCLI(CLI): if role_name: # show the requested role, if it exists - gr = GalaxyRole(self.galaxy, self.api, role_name, path=os.path.join(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): role_found = True display.display('# %s' % os.path.dirname(gr.path)) @@ -1506,7 +1522,7 @@ class GalaxyCLI(CLI): display.display('# %s' % role_path) path_files = os.listdir(role_path) 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: _display_role(gr) diff --git a/lib/ansible/galaxy/role.py b/lib/ansible/galaxy/role.py index 058174c931..391394cfc9 100644 --- a/lib/ansible/galaxy/role.py +++ b/lib/ansible/galaxy/role.py @@ -32,6 +32,7 @@ from shutil import rmtree from ansible import context from ansible.errors import AnsibleError +from ansible.galaxy.api import GalaxyAPI from ansible.galaxy.user_agent import user_agent from ansible.module_utils._text import to_native, to_text from ansible.module_utils.common.yaml import yaml_dump, yaml_load @@ -60,7 +61,7 @@ class GalaxyRole(object): display.debug('Validate TLS certificates: %s' % self._validate_certs) self.galaxy = galaxy - self.api = api + self._api = api self.name = name self.version = version @@ -101,6 +102,12 @@ class GalaxyRole(object): return self.name == other.name @property + def api(self): + if not isinstance(self._api, GalaxyAPI): + return self._api.api + return self._api + + @property def metadata(self): """ Returns role metadata diff --git a/test/integration/targets/ansible-galaxy-role/tasks/main.yml b/test/integration/targets/ansible-galaxy-role/tasks/main.yml index e49e4e2958..03d0b3c243 100644 --- a/test/integration/targets/ansible-galaxy-role/tasks/main.yml +++ b/test/integration/targets/ansible-galaxy-role/tasks/main.yml @@ -1,3 +1,6 @@ +- name: Install role from Galaxy (should not fail with AttributeError) + command: ansible-galaxy role install ansible.nope -vvvv --ignore-errors + - name: Archive directories file: state: directory diff --git a/test/integration/targets/ansible-galaxy/runme.sh b/test/integration/targets/ansible-galaxy/runme.sh index 4bb459588a..0df42aa82b 100755 --- a/test/integration/targets/ansible-galaxy/runme.sh +++ b/test/integration/targets/ansible-galaxy/runme.sh @@ -103,7 +103,11 @@ f_ansible_galaxy_status "install of local git repo" mkdir -p "${galaxy_testdir}" pushd "${galaxy_testdir}" - ansible-galaxy install git+file:///"${galaxy_local_test_role_git_repo}" "$@" + # minimum verbosity is hardcoded to include calls to Galaxy + ansible-galaxy install git+file:///"${galaxy_local_test_role_git_repo}" "$@" -vvvv 2>&1 | tee out.txt + + # Test no initial call is made to Galaxy + grep out.txt -e "https://galaxy.ansible.com" && cat out.txt && exit 1 # Test that the role was installed to the expected directory [[ -d "${HOME}/.ansible/roles/${galaxy_local_test_role}" ]] |