This commit is contained in:
parent
65fd331cb5
commit
67d9cc45bd
5 changed files with 181 additions and 17 deletions
|
@ -39,7 +39,14 @@ options:
|
||||||
version:
|
version:
|
||||||
description:
|
description:
|
||||||
- The maven version coordinate
|
- The maven version coordinate
|
||||||
default: latest
|
- Mutually exclusive with I(version_by_spec).
|
||||||
|
version_by_spec:
|
||||||
|
description:
|
||||||
|
- The maven dependency version ranges.
|
||||||
|
- See supported version ranges on U(https://cwiki.apache.org/confluence/display/MAVENOLD/Dependency+Mediation+and+Conflict+Resolution)
|
||||||
|
- The range type "(,1.0],[1.2,)" and "(,1.1),(1.1,)" is not supported.
|
||||||
|
- Mutually exclusive with I(version).
|
||||||
|
version_added: "2.10"
|
||||||
classifier:
|
classifier:
|
||||||
description:
|
description:
|
||||||
- The maven classifier coordinate
|
- The maven classifier coordinate
|
||||||
|
@ -90,7 +97,8 @@ options:
|
||||||
keep_name:
|
keep_name:
|
||||||
description:
|
description:
|
||||||
- If C(yes), the downloaded artifact's name is preserved, i.e the version number remains part of it.
|
- If C(yes), the downloaded artifact's name is preserved, i.e the version number remains part of it.
|
||||||
- This option only has effect when C(dest) is a directory and C(version) is set to C(latest).
|
- This option only has effect when C(dest) is a directory and C(version) is set to C(latest) or C(version_by_spec)
|
||||||
|
is defined.
|
||||||
type: bool
|
type: bool
|
||||||
default: 'no'
|
default: 'no'
|
||||||
version_added: "2.4"
|
version_added: "2.4"
|
||||||
|
@ -158,6 +166,13 @@ EXAMPLES = '''
|
||||||
artifact_id: junit
|
artifact_id: junit
|
||||||
dest: /tmp/junit-latest.jar
|
dest: /tmp/junit-latest.jar
|
||||||
repository_url: "file://{{ lookup('env','HOME') }}/.m2/repository"
|
repository_url: "file://{{ lookup('env','HOME') }}/.m2/repository"
|
||||||
|
|
||||||
|
# Download the latest version between 3.8 and 4.0 (exclusive) of the JUnit framework artifact from Maven Central
|
||||||
|
- maven_artifact:
|
||||||
|
group_id: junit
|
||||||
|
artifact_id: junit
|
||||||
|
version_by_spec: "[3.8,4.0)"
|
||||||
|
dest: /tmp/
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
|
@ -168,6 +183,9 @@ import io
|
||||||
import tempfile
|
import tempfile
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
from ansible.module_utils.ansible_release import __version__ as ansible_version
|
||||||
|
from re import match
|
||||||
|
|
||||||
LXML_ETREE_IMP_ERR = None
|
LXML_ETREE_IMP_ERR = None
|
||||||
try:
|
try:
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
@ -184,6 +202,15 @@ except ImportError:
|
||||||
BOTO_IMP_ERR = traceback.format_exc()
|
BOTO_IMP_ERR = traceback.format_exc()
|
||||||
HAS_BOTO = False
|
HAS_BOTO = False
|
||||||
|
|
||||||
|
SEMANTIC_VERSION_IMP_ERR = None
|
||||||
|
try:
|
||||||
|
from semantic_version import Version, Spec
|
||||||
|
HAS_SEMANTIC_VERSION = True
|
||||||
|
except ImportError:
|
||||||
|
SEMANTIC_VERSION_IMP_ERR = traceback.format_exc()
|
||||||
|
HAS_SEMANTIC_VERSION = False
|
||||||
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||||
from ansible.module_utils.six.moves.urllib.parse import urlparse
|
from ansible.module_utils.six.moves.urllib.parse import urlparse
|
||||||
from ansible.module_utils.urls import fetch_url
|
from ansible.module_utils.urls import fetch_url
|
||||||
|
@ -224,7 +251,7 @@ def adjust_recursive_directory_permissions(pre_existing_dir, new_directory_list,
|
||||||
|
|
||||||
|
|
||||||
class Artifact(object):
|
class Artifact(object):
|
||||||
def __init__(self, group_id, artifact_id, version, classifier='', extension='jar'):
|
def __init__(self, group_id, artifact_id, version, version_by_spec, classifier='', extension='jar'):
|
||||||
if not group_id:
|
if not group_id:
|
||||||
raise ValueError("group_id must be set")
|
raise ValueError("group_id must be set")
|
||||||
if not artifact_id:
|
if not artifact_id:
|
||||||
|
@ -233,6 +260,7 @@ class Artifact(object):
|
||||||
self.group_id = group_id
|
self.group_id = group_id
|
||||||
self.artifact_id = artifact_id
|
self.artifact_id = artifact_id
|
||||||
self.version = version
|
self.version = version
|
||||||
|
self.version_by_spec = version_by_spec
|
||||||
self.classifier = classifier
|
self.classifier = classifier
|
||||||
|
|
||||||
if not extension:
|
if not extension:
|
||||||
|
@ -290,17 +318,62 @@ class Artifact(object):
|
||||||
|
|
||||||
|
|
||||||
class MavenDownloader:
|
class MavenDownloader:
|
||||||
def __init__(self, module, base="http://repo1.maven.org/maven2", local=False, headers=None):
|
def __init__(self, module, base, local=False, headers=None):
|
||||||
self.module = module
|
self.module = module
|
||||||
if base.endswith("/"):
|
if base.endswith("/"):
|
||||||
base = base.rstrip("/")
|
base = base.rstrip("/")
|
||||||
self.base = base
|
self.base = base
|
||||||
self.local = local
|
self.local = local
|
||||||
self.headers = headers
|
self.headers = headers
|
||||||
self.user_agent = "Ansible {0} maven_artifact".format(self.module.ansible_version)
|
self.user_agent = "Ansible {0} maven_artifact".format(ansible_version)
|
||||||
self.latest_version_found = None
|
self.latest_version_found = None
|
||||||
self.metadata_file_name = "maven-metadata-local.xml" if local else "maven-metadata.xml"
|
self.metadata_file_name = "maven-metadata-local.xml" if local else "maven-metadata.xml"
|
||||||
|
|
||||||
|
def find_version_by_spec(self, artifact):
|
||||||
|
path = "/%s/%s" % (artifact.path(False), self.metadata_file_name)
|
||||||
|
content = self._getContent(self.base + path, "Failed to retrieve the maven metadata file: " + path)
|
||||||
|
xml = etree.fromstring(content)
|
||||||
|
original_versions = xml.xpath("/metadata/versioning/versions/version/text()")
|
||||||
|
versions = []
|
||||||
|
for version in original_versions:
|
||||||
|
try:
|
||||||
|
versions.append(Version.coerce(version))
|
||||||
|
except ValueError:
|
||||||
|
# This means that version string is not a valid semantic versioning
|
||||||
|
pass
|
||||||
|
|
||||||
|
parse_versions_syntax = {
|
||||||
|
# example -> (,1.0]
|
||||||
|
r"^\(,(?P<upper_bound>[0-9.]*)]$": "<={upper_bound}",
|
||||||
|
# example -> 1.0
|
||||||
|
r"^(?P<version>[0-9.]*)$": "~={version}",
|
||||||
|
# example -> [1.0]
|
||||||
|
r"^\[(?P<version>[0-9.]*)\]$": "=={version}",
|
||||||
|
# example -> [1.2, 1.3]
|
||||||
|
r"^\[(?P<lower_bound>[0-9.]*),\s*(?P<upper_bound>[0-9.]*)\]$": ">={lower_bound},<={upper_bound}",
|
||||||
|
# example -> [1.2, 1.3)
|
||||||
|
r"^\[(?P<lower_bound>[0-9.]*),\s*(?P<upper_bound>[0-9.]+)\)$": ">={lower_bound},<{upper_bound}",
|
||||||
|
# example -> [1.5,)
|
||||||
|
r"^\[(?P<lower_bound>[0-9.]*),\)$": ">={lower_bound}",
|
||||||
|
}
|
||||||
|
|
||||||
|
for regex, spec_format in parse_versions_syntax.items():
|
||||||
|
regex_result = match(regex, artifact.version_by_spec)
|
||||||
|
if regex_result:
|
||||||
|
spec = Spec(spec_format.format(**regex_result.groupdict()))
|
||||||
|
selected_version = spec.select(versions)
|
||||||
|
|
||||||
|
if not selected_version:
|
||||||
|
raise ValueError("No version found with this spec version: {0}".format(artifact.version_by_spec))
|
||||||
|
|
||||||
|
# To deal when repos on maven don't have patch number on first build (e.g. 3.8 instead of 3.8.0)
|
||||||
|
if str(selected_version) not in original_versions:
|
||||||
|
selected_version.patch = None
|
||||||
|
|
||||||
|
return str(selected_version)
|
||||||
|
|
||||||
|
raise ValueError("The spec version {0} is not supported! ".format(artifact.version_by_spec))
|
||||||
|
|
||||||
def find_latest_version_available(self, artifact):
|
def find_latest_version_available(self, artifact):
|
||||||
if self.latest_version_found:
|
if self.latest_version_found:
|
||||||
return self.latest_version_found
|
return self.latest_version_found
|
||||||
|
@ -313,6 +386,9 @@ class MavenDownloader:
|
||||||
return v[0]
|
return v[0]
|
||||||
|
|
||||||
def find_uri_for_artifact(self, artifact):
|
def find_uri_for_artifact(self, artifact):
|
||||||
|
if artifact.version_by_spec:
|
||||||
|
artifact.version = self.find_version_by_spec(artifact)
|
||||||
|
|
||||||
if artifact.version == "latest":
|
if artifact.version == "latest":
|
||||||
artifact.version = self.find_latest_version_available(artifact)
|
artifact.version = self.find_latest_version_available(artifact)
|
||||||
|
|
||||||
|
@ -390,8 +466,8 @@ class MavenDownloader:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def download(self, tmpdir, artifact, verify_download, filename=None):
|
def download(self, tmpdir, artifact, verify_download, filename=None):
|
||||||
if not artifact.version or artifact.version == "latest":
|
if (not artifact.version and not artifact.version_by_spec) or artifact.version == "latest":
|
||||||
artifact = Artifact(artifact.group_id, artifact.artifact_id, self.find_latest_version_available(artifact),
|
artifact = Artifact(artifact.group_id, artifact.artifact_id, self.find_latest_version_available(artifact), None,
|
||||||
artifact.classifier, artifact.extension)
|
artifact.classifier, artifact.extension)
|
||||||
url = self.find_uri_for_artifact(artifact)
|
url = self.find_uri_for_artifact(artifact)
|
||||||
tempfd, tempname = tempfile.mkstemp(dir=tmpdir)
|
tempfd, tempname = tempfile.mkstemp(dir=tmpdir)
|
||||||
|
@ -464,10 +540,11 @@ def main():
|
||||||
argument_spec=dict(
|
argument_spec=dict(
|
||||||
group_id=dict(required=True),
|
group_id=dict(required=True),
|
||||||
artifact_id=dict(required=True),
|
artifact_id=dict(required=True),
|
||||||
version=dict(default="latest"),
|
version=dict(default=None),
|
||||||
|
version_by_spec=dict(default=None),
|
||||||
classifier=dict(default=''),
|
classifier=dict(default=''),
|
||||||
extension=dict(default='jar'),
|
extension=dict(default='jar'),
|
||||||
repository_url=dict(default=None),
|
repository_url=dict(default='http://repo1.maven.org/maven2'),
|
||||||
username=dict(default=None, aliases=['aws_secret_key']),
|
username=dict(default=None, aliases=['aws_secret_key']),
|
||||||
password=dict(default=None, no_log=True, aliases=['aws_secret_access_key']),
|
password=dict(default=None, no_log=True, aliases=['aws_secret_access_key']),
|
||||||
headers=dict(type='dict'),
|
headers=dict(type='dict'),
|
||||||
|
@ -478,12 +555,16 @@ def main():
|
||||||
keep_name=dict(required=False, default=False, type='bool'),
|
keep_name=dict(required=False, default=False, type='bool'),
|
||||||
verify_checksum=dict(required=False, default='download', choices=['never', 'download', 'change', 'always'])
|
verify_checksum=dict(required=False, default='download', choices=['never', 'download', 'change', 'always'])
|
||||||
),
|
),
|
||||||
add_file_common_args=True
|
add_file_common_args=True,
|
||||||
|
mutually_exclusive=([('version', 'version_by_spec')])
|
||||||
)
|
)
|
||||||
|
|
||||||
if not HAS_LXML_ETREE:
|
if not HAS_LXML_ETREE:
|
||||||
module.fail_json(msg=missing_required_lib('lxml'), exception=LXML_ETREE_IMP_ERR)
|
module.fail_json(msg=missing_required_lib('lxml'), exception=LXML_ETREE_IMP_ERR)
|
||||||
|
|
||||||
|
if module.params['version_by_spec'] and not HAS_SEMANTIC_VERSION:
|
||||||
|
module.fail_json(msg=missing_required_lib('semantic_version'), exception=SEMANTIC_VERSION_IMP_ERR)
|
||||||
|
|
||||||
repository_url = module.params["repository_url"]
|
repository_url = module.params["repository_url"]
|
||||||
if not repository_url:
|
if not repository_url:
|
||||||
repository_url = "http://repo1.maven.org/maven2"
|
repository_url = "http://repo1.maven.org/maven2"
|
||||||
|
@ -501,6 +582,7 @@ def main():
|
||||||
group_id = module.params["group_id"]
|
group_id = module.params["group_id"]
|
||||||
artifact_id = module.params["artifact_id"]
|
artifact_id = module.params["artifact_id"]
|
||||||
version = module.params["version"]
|
version = module.params["version"]
|
||||||
|
version_by_spec = module.params["version_by_spec"]
|
||||||
classifier = module.params["classifier"]
|
classifier = module.params["classifier"]
|
||||||
extension = module.params["extension"]
|
extension = module.params["extension"]
|
||||||
headers = module.params['headers']
|
headers = module.params['headers']
|
||||||
|
@ -514,8 +596,11 @@ def main():
|
||||||
|
|
||||||
downloader = MavenDownloader(module, repository_url, local, headers)
|
downloader = MavenDownloader(module, repository_url, local, headers)
|
||||||
|
|
||||||
|
if not version_by_spec and not version:
|
||||||
|
version = "latest"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
artifact = Artifact(group_id, artifact_id, version, classifier, extension)
|
artifact = Artifact(group_id, artifact_id, version, version_by_spec, classifier, extension)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
module.fail_json(msg=e.args[0])
|
module.fail_json(msg=e.args[0])
|
||||||
|
|
||||||
|
@ -537,13 +622,19 @@ def main():
|
||||||
|
|
||||||
if os.path.isdir(b_dest):
|
if os.path.isdir(b_dest):
|
||||||
version_part = version
|
version_part = version
|
||||||
if keep_name and version == 'latest':
|
if version == 'latest':
|
||||||
version_part = downloader.find_latest_version_available(artifact)
|
version_part = downloader.find_latest_version_available(artifact)
|
||||||
|
elif version_by_spec:
|
||||||
|
version_part = downloader.find_version_by_spec(artifact)
|
||||||
|
|
||||||
|
filename = "{artifact_id}{version_part}{classifier}.{extension}".format(
|
||||||
|
artifact_id=artifact_id,
|
||||||
|
version_part="-{0}".format(version_part) if keep_name else "",
|
||||||
|
classifier="-{0}".format(classifier) if classifier else "",
|
||||||
|
extension=extension
|
||||||
|
)
|
||||||
|
dest = posixpath.join(dest, filename)
|
||||||
|
|
||||||
if classifier:
|
|
||||||
dest = posixpath.join(dest, "%s-%s-%s.%s" % (artifact_id, version_part, classifier, extension))
|
|
||||||
else:
|
|
||||||
dest = posixpath.join(dest, "%s-%s.%s" % (artifact_id, version_part, extension))
|
|
||||||
b_dest = to_bytes(dest, errors='surrogate_or_strict')
|
b_dest = to_bytes(dest, errors='surrogate_or_strict')
|
||||||
|
|
||||||
if os.path.lexists(b_dest) and ((not verify_change) or not downloader.is_invalid_md5(dest, downloader.find_uri_for_artifact(artifact))):
|
if os.path.lexists(b_dest) and ((not verify_change) or not downloader.is_invalid_md5(dest, downloader.find_uri_for_artifact(artifact))):
|
||||||
|
|
|
@ -45,3 +45,4 @@ mccabe == 0.6.1
|
||||||
pylint == 2.3.1
|
pylint == 2.3.1
|
||||||
typed-ast == 1.4.0 # 1.4.0 is required to compile on Python 3.8
|
typed-ast == 1.4.0 # 1.4.0 is required to compile on Python 3.8
|
||||||
wrapt == 1.11.1
|
wrapt == 1.11.1
|
||||||
|
semantic_version == 2.6.0 # newer versions are not supported on Python 2.6
|
||||||
|
|
|
@ -5,3 +5,6 @@ pytest
|
||||||
pytest-mock
|
pytest-mock
|
||||||
pytest-xdist
|
pytest-xdist
|
||||||
pyyaml
|
pyyaml
|
||||||
|
|
||||||
|
# requirement for maven_artifact
|
||||||
|
semantic_version
|
||||||
|
|
|
@ -4889,7 +4889,6 @@ lib/ansible/modules/packaging/language/easy_install.py validate-modules:doc-defa
|
||||||
lib/ansible/modules/packaging/language/easy_install.py validate-modules:parameter-type-not-in-doc
|
lib/ansible/modules/packaging/language/easy_install.py validate-modules:parameter-type-not-in-doc
|
||||||
lib/ansible/modules/packaging/language/easy_install.py validate-modules:doc-missing-type
|
lib/ansible/modules/packaging/language/easy_install.py validate-modules:doc-missing-type
|
||||||
lib/ansible/modules/packaging/language/gem.py validate-modules:parameter-type-not-in-doc
|
lib/ansible/modules/packaging/language/gem.py validate-modules:parameter-type-not-in-doc
|
||||||
lib/ansible/modules/packaging/language/maven_artifact.py validate-modules:doc-default-does-not-match-spec
|
|
||||||
lib/ansible/modules/packaging/language/maven_artifact.py validate-modules:parameter-type-not-in-doc
|
lib/ansible/modules/packaging/language/maven_artifact.py validate-modules:parameter-type-not-in-doc
|
||||||
lib/ansible/modules/packaging/language/maven_artifact.py validate-modules:doc-missing-type
|
lib/ansible/modules/packaging/language/maven_artifact.py validate-modules:doc-missing-type
|
||||||
lib/ansible/modules/packaging/language/pear.py validate-modules:undocumented-parameter
|
lib/ansible/modules/packaging/language/pear.py validate-modules:undocumented-parameter
|
||||||
|
|
70
test/units/modules/packaging/language/test_maven_artifact.py
Normal file
70
test/units/modules/packaging/language/test_maven_artifact.py
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
# Copyright (c) 2017 Ansible Project
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from ansible.modules.packaging.language import maven_artifact
|
||||||
|
from ansible.module_utils import basic
|
||||||
|
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.usefixtures('patch_ansible_module')
|
||||||
|
|
||||||
|
maven_metadata_example = b"""<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<metadata>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<versioning>
|
||||||
|
<latest>4.13-beta-2</latest>
|
||||||
|
<release>4.13-beta-2</release>
|
||||||
|
<versions>
|
||||||
|
<version>3.7</version>
|
||||||
|
<version>3.8</version>
|
||||||
|
<version>3.8.1</version>
|
||||||
|
<version>3.8.2</version>
|
||||||
|
<version>4.0</version>
|
||||||
|
<version>4.1</version>
|
||||||
|
<version>4.2</version>
|
||||||
|
<version>4.3</version>
|
||||||
|
<version>4.3.1</version>
|
||||||
|
<version>4.4</version>
|
||||||
|
<version>4.5</version>
|
||||||
|
<version>4.6</version>
|
||||||
|
<version>4.7</version>
|
||||||
|
<version>4.8</version>
|
||||||
|
<version>4.8.1</version>
|
||||||
|
<version>4.8.2</version>
|
||||||
|
<version>4.9</version>
|
||||||
|
<version>4.10</version>
|
||||||
|
<version>4.11-beta-1</version>
|
||||||
|
<version>4.11</version>
|
||||||
|
<version>4.12-beta-1</version>
|
||||||
|
<version>4.12-beta-2</version>
|
||||||
|
<version>4.12-beta-3</version>
|
||||||
|
<version>4.12</version>
|
||||||
|
<version>4.13-beta-1</version>
|
||||||
|
<version>4.13-beta-2</version>
|
||||||
|
</versions>
|
||||||
|
<lastUpdated>20190202141051</lastUpdated>
|
||||||
|
</versioning>
|
||||||
|
</metadata>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('patch_ansible_module, version_by_spec, version_choosed', [
|
||||||
|
(None, "(,3.9]", "3.8.2"),
|
||||||
|
(None, "3.0", "3.8.2"),
|
||||||
|
(None, "[3.7]", "3.7"),
|
||||||
|
(None, "[4.10, 4.12]", "4.12"),
|
||||||
|
(None, "[4.10, 4.12)", "4.11"),
|
||||||
|
(None, "[2.0,)", "4.13-beta-2"),
|
||||||
|
])
|
||||||
|
def test_find_version_by_spec(mocker, version_by_spec, version_choosed):
|
||||||
|
_getContent = mocker.patch('ansible.modules.packaging.language.maven_artifact.MavenDownloader._getContent')
|
||||||
|
_getContent.return_value = maven_metadata_example
|
||||||
|
|
||||||
|
artifact = maven_artifact.Artifact("junit", "junit", None, version_by_spec, "jar")
|
||||||
|
mvn_downloader = maven_artifact.MavenDownloader(basic.AnsibleModule, "http://repo1.maven.org/maven2")
|
||||||
|
|
||||||
|
assert mvn_downloader.find_version_by_spec(artifact) == version_choosed
|
Loading…
Reference in a new issue