diff options
Diffstat (limited to 'lib/ansible/galaxy/dependency_resolution/dataclasses.py')
-rw-r--r-- | lib/ansible/galaxy/dependency_resolution/dataclasses.py | 66 |
1 files changed, 55 insertions, 11 deletions
diff --git a/lib/ansible/galaxy/dependency_resolution/dataclasses.py b/lib/ansible/galaxy/dependency_resolution/dataclasses.py index 35b65054..7e8fb57a 100644 --- a/lib/ansible/galaxy/dependency_resolution/dataclasses.py +++ b/lib/ansible/galaxy/dependency_resolution/dataclasses.py @@ -29,7 +29,8 @@ if t.TYPE_CHECKING: from ansible.errors import AnsibleError, AnsibleAssertionError from ansible.galaxy.api import GalaxyAPI -from ansible.module_utils._text import to_bytes, to_native, to_text +from ansible.galaxy.collection import HAS_PACKAGING, PkgReq +from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text from ansible.module_utils.common.arg_spec import ArgumentSpecValidator from ansible.utils.collection_loader import AnsibleCollectionRef from ansible.utils.display import Display @@ -215,10 +216,15 @@ class _ComputedReqKindsMixin: return cls.from_dir_path_implicit(dir_path) @classmethod - def from_dir_path(cls, dir_path, art_mgr): + def from_dir_path( # type: ignore[misc] + cls, # type: t.Type[Collection] + dir_path, # type: bytes + art_mgr, # type: ConcreteArtifactsManager + ): # type: (...) -> Collection """Make collection from an directory with metadata.""" - b_dir_path = to_bytes(dir_path, errors='surrogate_or_strict') - if not _is_collection_dir(b_dir_path): + if dir_path.endswith(to_bytes(os.path.sep)): + dir_path = dir_path.rstrip(to_bytes(os.path.sep)) + if not _is_collection_dir(dir_path): display.warning( u"Collection at '{path!s}' does not have a {manifest_json!s} " u'file, nor has it {galaxy_yml!s}: cannot detect version.'. @@ -267,6 +273,8 @@ class _ComputedReqKindsMixin: regardless of whether any of known metadata files are present. """ # There is no metadata, but it isn't required for a functional collection. Determine the namespace.name from the path. + if dir_path.endswith(to_bytes(os.path.sep)): + dir_path = dir_path.rstrip(to_bytes(os.path.sep)) u_dir_path = to_text(dir_path, errors='surrogate_or_strict') path_list = u_dir_path.split(os.path.sep) req_name = '.'.join(path_list[-2:]) @@ -275,13 +283,25 @@ class _ComputedReqKindsMixin: @classmethod def from_string(cls, collection_input, artifacts_manager, supplemental_signatures): req = {} - if _is_concrete_artifact_pointer(collection_input): - # Arg is a file path or URL to a collection + if _is_concrete_artifact_pointer(collection_input) or AnsibleCollectionRef.is_valid_collection_name(collection_input): + # Arg is a file path or URL to a collection, or just a collection req['name'] = collection_input - else: + elif ':' in collection_input: req['name'], _sep, req['version'] = collection_input.partition(':') if not req['version']: del req['version'] + else: + if not HAS_PACKAGING: + raise AnsibleError("Failed to import packaging, check that a supported version is installed") + try: + pkg_req = PkgReq(collection_input) + except Exception as e: + # packaging doesn't know what this is, let it fly, better errors happen in from_requirement_dict + req['name'] = collection_input + else: + req['name'] = pkg_req.name + if pkg_req.specifier: + req['version'] = to_text(pkg_req.specifier) req['signatures'] = supplemental_signatures return cls.from_requirement_dict(req, artifacts_manager) @@ -414,6 +434,9 @@ class _ComputedReqKindsMixin: format(not_url=req_source.api_server), ) + if req_type == 'dir' and req_source.endswith(os.path.sep): + req_source = req_source.rstrip(os.path.sep) + tmp_inst_req = cls(req_name, req_version, req_source, req_type, req_signature_sources) if req_type not in {'galaxy', 'subdirs'} and req_name is None: @@ -440,8 +463,8 @@ class _ComputedReqKindsMixin: def __unicode__(self): if self.fqcn is None: return ( - f'{self.type} collection from a Git repo' if self.is_scm - else f'{self.type} collection from a namespace' + u'"virtual collection Git repo"' if self.is_scm + else u'"virtual collection namespace"' ) return ( @@ -481,14 +504,14 @@ class _ComputedReqKindsMixin: @property def namespace(self): if self.is_virtual: - raise TypeError(f'{self.type} collections do not have a namespace') + raise TypeError('Virtual collections do not have a namespace') return self._get_separate_ns_n_name()[0] @property def name(self): if self.is_virtual: - raise TypeError(f'{self.type} collections do not have a name') + raise TypeError('Virtual collections do not have a name') return self._get_separate_ns_n_name()[-1] @@ -542,6 +565,27 @@ class _ComputedReqKindsMixin: return not self.is_concrete_artifact @property + def is_pinned(self): + """Indicate if the version set is considered pinned. + + This essentially computes whether the version field of the current + requirement explicitly requests a specific version and not an allowed + version range. + + It is then used to help the resolvelib-based dependency resolver judge + whether it's acceptable to consider a pre-release candidate version + despite pre-release installs not being requested by the end-user + explicitly. + + See https://github.com/ansible/ansible/pull/81606 for extra context. + """ + version_string = self.ver[0] + return version_string.isdigit() or not ( + version_string == '*' or + version_string.startswith(('<', '>', '!=')) + ) + + @property def source_info(self): return self._source_info |