Repository: ansible/ansible

Issue:
Module maven_artifact can't download non unique snapshot-artifact
##### SUMMARY
Module maven_artifact cannot download a snapshot artifact that is not unique. Non unique Snapshot artifacts do not contain the timestamp field in the maven-metadata.xml

##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
maven_artifact

##### ANSIBLE VERSION
```
ansible 2.5.3 (detached HEAD 778cb5bd8b) last updated 2018/05/24 14:24:47 (GMT +000)
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /opt/ansible/lib/ansible
  executable location = /opt/ansible/bin/ansible
  python version = 2.7.13 (default, Jan 31 2018, 00:17:36) [GCC 4.8.5 20150623 (Red Hat 4.8.5-11)]
```

##### CONFIGURATION
ANSIBLE_PIPELINING(/etc/ansible/ansible.cfg) = True
ANSIBLE_SSH_ARGS(/etc/ansible/ansible.cfg) = -C -o ControlMaster=auto -o ControlPersist=60s -o UserKnownHostsFile=/dev/n
DEFAULT_CALLBACK_WHITELIST(/etc/ansible/ansible.cfg) = ['timer', 'mail']
DEFAULT_FORKS(/etc/ansible/ansible.cfg) = 20
DEFAULT_GATHERING(/etc/ansible/ansible.cfg) = smart
DEFAULT_LOG_PATH(/etc/ansible/ansible.cfg) = /var/log/ansible.log
DEFAULT_PRIVATE_KEY_FILE(/etc/ansible/ansible.cfg) = /var/lib/rundeck/.ssh/ec2_user_id_rsa
DEFAULT_REMOTE_USER(/etc/ansible/ansible.cfg) = ec2-user
DEFAULT_SCP_IF_SSH(/etc/ansible/ansible.cfg) = smart
DISPLAY_SKIPPED_HOSTS(/etc/ansible/ansible.cfg) = True
HOST_KEY_CHECKING(/etc/ansible/ansible.cfg) = False
RETRY_FILES_ENABLED(/etc/ansible/ansible.cfg) = False

##### OS / ENVIRONMENT
N/A

##### STEPS TO REPRODUCE
Deploy an artifact with -SNAPSHOT in version string into a non unique repository and try to download it via the maven_artifact module.

##### EXPECTED RESULTS
I expect that the artifact is downloaded onto the server in the dest directory

##### ACTUAL RESULTS
```
TASK [install-modules : get module installation files from artifactory] ***
--
task path: /opt/rundeck-data/d0614942-24f2-4453-aee9-b66a9e7f9914/18233/roles/install-modules/tasks/install-module.yml:12
Using module file /opt/ansible/lib/ansible/modules/packaging/language/maven_artifact.py
<my-server.my-serverfarm.net> ESTABLISH SSH CONNECTION FOR USER: ec2-user
<my-server.my-serverfarm.net> SSH: EXEC ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o 'IdentityFile="/var/lib/rundeck/.ssh/ec2_user_id_rsa"' -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=ec2-user -o ConnectTimeout=10 -o ControlPath=/var/lib/rundeck/.ansible/cp/5342204d28 my-server.my-serverfarm.net '/bin/sh -c '"'"'sudo -H -S -n -u root /bin/sh -c '"'"'"'"'"'"'"'"'echo BECOME-SUCCESS-sypixboiktxznzxvgvjfmmezrqkzjsxy; /usr/bin/python'"'"'"'"'"'"'"'"' && sleep 0'"'"''
Escalation succeeded
<my-server.my-serverfarm.net> (1, '', 'OpenSSH_7.4p1, OpenSSL 1.0.2k-fips  26 Jan 2017\r\ndebug1: Reading configuration data /etc/ssh/ssh_config\r\ndebug1: /etc/ssh/ssh_config line 58: Applying options for *\r\ndebug1: auto-mux: Trying existing master\r\ndebug2: fd 3 setting O_NONBLOCK\r\ndebug2: mux_client_hello_exchange: master version 4\r\ndebug3: mux_client_forwards: request forwardings: 0 local, 0 remote\r\ndebug3: mux_client_request_session: entering\r\ndebug3: mux_client_request_alive: entering\r\ndebug3: mux_client_request_alive: done pid = 6689\r\ndebug3: mux_client_request_session: session request sent\r\ndebug1: mux_client_request_session: master session id: 2\r\nTraceback (most recent call last):\n  File "/tmp/ansible_5JSeNG/ansible_module_maven_artifact.py", line 507, in <module>\n    main()\n  File "/tmp/ansible_5JSeNG/ansible_module_maven_artifact.py", line 489, in main\n    if downloader.download(artifact, b_dest):\n  File "/tmp/ansible_5JSeNG/ansible_module_maven_artifact.py", line 347, in download\n    url = self.find_uri_for_artifact(artifact)\n  File "/tmp/ansible_5JSeNG/ansible_module_maven_artifact.py", line 295, in find_uri_for_artifact\n    timestamp = xml.xpath("/metadata/versioning/snapshot/timestamp/text()")[0]\nIndexError: list index out of range\ndebug3: mux_client_read_packet: read header failed: Broken pipe\r\ndebug2: Received exit status from master 1\r\n')
fatal: [my-server.my-serverfarm.net]: FAILED! => {
"changed": false,
"module_stderr": "OpenSSH_7.4p1, OpenSSL 1.0.2k-fips  26 Jan 2017\r\ndebug1: Reading configuration data /etc/ssh/ssh_config\r\ndebug1: /etc/ssh/ssh_config line 58: Applying options for *\r\ndebug1: auto-mux: Trying existing master\r\ndebug2: fd 3 setting O_NONBLOCK\r\ndebug2: mux_client_hello_exchange: master version 4\r\ndebug3: mux_client_forwards: request forwardings: 0 local, 0 remote\r\ndebug3: mux_client_request_session: entering\r\ndebug3: mux_client_request_alive: entering\r\ndebug3: mux_client_request_alive: done pid = 6689\r\ndebug3: mux_client_request_session: session request sent\r\ndebug1: mux_client_request_session: master session id: 2\r\nTraceback (most recent call last):\n  File \"/tmp/ansible_5JSeNG/ansible_module_maven_artifact.py\", line 507, in <module>\n    main()\n  File \"/tmp/ansible_5JSeNG/ansible_module_maven_artifact.py\", line 489, in main\n    if downloader.download(artifact, b_dest):\n  File \"/tmp/ansible_5JSeNG/ansible_module_maven_artifact.py\", line 347, in download\n    url = self.find_uri_for_artifact(artifact)\n  File \"/tmp/ansible_5JSeNG/ansible_module_maven_artifact.py\", line 295, in find_uri_for_artifact\n    timestamp = xml.xpath(\"/metadata/versioning/snapshot/timestamp/text()\")[0]\nIndexError: list index out of range\ndebug3: mux_client_read_packet: read header failed: Broken pipe\r\ndebug2: Received exit status from master 1\r\n",
"module_stdout": "",
"msg": "MODULE FAILURE",
"rc": 1
}
```



Discussion:
Files identified in the description:
* [lib/ansible/modules/packaging/language/maven_artifact.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/packaging/language/maven_artifact.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @tumbl3w33d @turb
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Resolving patch:
diff --git a/lib/ansible/modules/packaging/language/maven_artifact.py b/lib/ansible/modules/packaging/language/maven_artifact.py
--- a/lib/ansible/modules/packaging/language/maven_artifact.py
+++ b/lib/ansible/modules/packaging/language/maven_artifact.py
@@ -304,8 +304,7 @@ def find_uri_for_artifact(self, artifact):
             path = "/%s/%s" % (artifact.path(), self.metadata_file_name)
             content = self._getContent(self.base + path, "Failed to retrieve the maven metadata file: " + path)
             xml = etree.fromstring(content)
-            timestamp = xml.xpath("/metadata/versioning/snapshot/timestamp/text()")[0]
-            buildNumber = xml.xpath("/metadata/versioning/snapshot/buildNumber/text()")[0]
+
             for snapshotArtifact in xml.xpath("/metadata/versioning/snapshotVersions/snapshotVersion"):
                 classifier = snapshotArtifact.xpath("classifier/text()")
                 artifact_classifier = classifier[0] if classifier else ''
@@ -313,7 +312,11 @@ def find_uri_for_artifact(self, artifact):
                 artifact_extension = extension[0] if extension else ''
                 if artifact_classifier == artifact.classifier and artifact_extension == artifact.extension:
                     return self._uri_for_artifact(artifact, snapshotArtifact.xpath("value/text()")[0])
-            return self._uri_for_artifact(artifact, artifact.version.replace("SNAPSHOT", timestamp + "-" + buildNumber))
+            timestamp_xmlpath = xml.xpath("/metadata/versioning/snapshot/timestamp/text()")
+            if timestamp_xmlpath:
+                timestamp = timestamp_xmlpath[0]
+                build_number = xml.xpath("/metadata/versioning/snapshot/buildNumber/text()")[0]
+                return self._uri_for_artifact(artifact, artifact.version.replace("SNAPSHOT", timestamp + "-" + build_number))
 
         return self._uri_for_artifact(artifact, artifact.version)
 


Test patch: