Repository: ansible-collections/community.general

Issue:
Maven module doesn't find artifacts given timestamped snapshot version
<!--- Verify first that your issue is not already reported on GitHub -->
<!--- Also test if the latest release and devel branch are affected too -->
<!--- Complete *all* sections as described, this form is processed automatically -->

##### SUMMARY
When specifying a timestamped snapshot version, maven_artifact fails to retrieve the artifact from a maven repository, whereas maven can resolve the artifact via dependency:get ala:

mvn dependency:get -DremoteRepositories=https://myrepository.mydomain.local/artifactory/libs-snapshot-local -DartifactId=myartifact -DgroupId=com.mydomain -Dpackaging=jar -Dversion=1.0.0-20200527.193024-1

The error returned for the equivalent from ansible is:

TASK [Download action for maven repo that requires No basic auth] **************
fatal: [localhost]: FAILED! => {"changed": false, "msg": "Failed to download artifact com.mydomain:myartifact:1.0.0-20200527.193024-1 because of HTTP Error 404: Not Foundfor URL http://myrepository.mydomain.local/artifactory/libs-snapshot/com/mydomain/myartifact/1.0.0-20200527.193024-1/myartifact-1.0.0-20200527.193024-1.jar"}

NOTE: that the path to the artifact contains the time stamped snapshot version 1.0.0-20200527.193024-1 where it should use the snapshot version string 1.0.0-SNAPSHOT

##### ISSUE TYPE
- Bug Report

##### COMPONENT NAME
maven_artifact

##### ANSIBLE VERSION
```
ansible 2.8.4
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/cygdrive/c/Users/jweber/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3.7/site-packages/ansible
  executable location = /usr/bin/ansible
  python version = 3.7.7 (default, Apr 10 2020, 07:59:19) [GCC 9.3.0]
```
NOTE: this is also evident in the most recent version github

##### CONFIGURATION
```

```

##### OS / ENVIRONMENT
Windows 10/Cygwin CYGWIN_NT-10.0 jweber2-lt 3.1.6(0.340/5/3) 2020-07-09 08:20 x86_64 Cygwin

##### STEPS TO REPRODUCE
ansible-playbook the-playbook-described-below.yml

```
- name: packages
  hosts: localhost
  gather_facts: false
  
  vars:
    maven:
        #NOTE: this snapshot version string will work, loads the latest SNAPSHOT version of the artifact
        #version: "1.0.0-SNAPSHOT" 
        #NOTE: this version string will fail
        version: "1.0.0-20200527.193024-1"
        artifact: "myartifact"
        extension: "jar"
  
  tasks:
  - name: Download action for maven repo that requires No basic auth
    maven_artifact:
      group_id: "com.mydomain"
      artifact_id: "{{maven.artifact}}"
      version: "{{maven.version}}"
      extension: "{{maven.extension}}"
      repository_url: "http://myrepository.mydomain.local/artifactory/libs-snapshot"
      dest: /tmp/{{maven.artifact}}-{{maven.version}}.{{maven.extension}}
      mode: 0640
      validate_certs: false
      verify_checksum: always
```

##### EXPECTED RESULTS
Expect the artifact file /tmp/myartifact-1.0.0-20200527.193024-1.jar to exist and match the version stored in the artifact repository.


##### ACTUAL RESULTS

<!--- Paste verbatim command output between quotes -->
```
ansible-playbook 2.8.4
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/cygdrive/c/Users/jweber/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3.7/site-packages/ansible
  executable location = /usr/bin/ansible-playbook
  python version = 3.7.7 (default, Apr 10 2020, 07:59:19) [GCC 9.3.0]
Using /etc/ansible/ansible.cfg as config file
host_list declined parsing /etc/ansible/hosts as it did not pass it's verify_file() method
script declined parsing /etc/ansible/hosts as it did not pass it's verify_file() method
auto declined parsing /etc/ansible/hosts as it did not pass it's verify_file() method
Parsed /etc/ansible/hosts inventory source with ini plugin

PLAYBOOK: maventest-with-timestamp.yml *****************************************
1 plays in /cygdrive/c/Temp/maventest-with-timestamp.yml

PLAY [packages] ****************************************************************
META: ran handlers

TASK [Download action for maven repo that requires No basic auth] **************
task path: /cygdrive/c/Temp/maventest-with-timestamp.yml:13
<127.0.0.1> ESTABLISH LOCAL CONNECTION FOR USER: jweber
<127.0.0.1> EXEC /bin/sh -c 'echo ~jweber && sleep 0'
<127.0.0.1> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo /home/jweber/.ansible/tmp/ansible-tmp-1596042534.8783953-25711094796568 `" && echo ansible-tmp-1596042534.8783953-25711094796568="` echo /home/jweber/.ansible/tmp/ansible-tmp-1596042534.8783953-25711094796568 `" ) && sleep 0'
Using module file /usr/lib/python3.7/site-packages/ansible/modules/packaging/language/maven_artifact.py
<127.0.0.1> PUT /cygdrive/c/Users/jweber/.ansible/tmp/ansible-local-3894zz6jhgxk/tmpi9ddtths TO /home/jweber/.ansible/tmp/ansible-tmp-1596042534.8783953-25711094796568/AnsiballZ_maven_artifact.py
<127.0.0.1> EXEC /bin/sh -c 'chmod u+x /home/jweber/.ansible/tmp/ansible-tmp-1596042534.8783953-25711094796568/ /home/jweber/.ansible/tmp/ansible-tmp-1596042534.8783953-25711094796568/AnsiballZ_maven_artifact.py && sleep 0'
<127.0.0.1> EXEC /bin/sh -c '/usr/bin/python3.7 /home/jweber/.ansible/tmp/ansible-tmp-1596042534.8783953-25711094796568/AnsiballZ_maven_artifact.py && sleep 0'
<127.0.0.1> EXEC /bin/sh -c 'rm -f -r /home/jweber/.ansible/tmp/ansible-tmp-1596042534.8783953-25711094796568/ > /dev/null 2>&1 && sleep 0'
The full traceback is:
  File "/tmp/ansible_maven_artifact_payload_b6m6maxd/__main__.py", line 554, in main
    download_error = downloader.download(module.tmpdir, artifact, verify_download, b_dest)
  File "/tmp/ansible_maven_artifact_payload_b6m6maxd/__main__.py", line 420, in download
    raise e
  File "/tmp/ansible_maven_artifact_payload_b6m6maxd/__main__.py", line 408, in download
    response = self._request(url, "Failed to download artifact " + str(artifact))
  File "/tmp/ansible_maven_artifact_payload_b6m6maxd/__main__.py", line 389, in _request
    raise ValueError(failmsg + " because of " + info['msg'] + "for URL " + url_to_use)

fatal: [localhost]: FAILED! => {
    "changed": false,
    "invocation": {
        "module_args": {
            "artifact_id": "myartifact",
            "attributes": null,
            "backup": null,
            "classifier": "",
            "content": null,
            "delimiter": null,
            "dest": "/tmp/myartifact-1.0.0-20200527.193024-1.jar",
            "directory_mode": null,
            "extension": "jar",
            "follow": false,
            "force": null,
            "group": null,
            "group_id": "com.mydomain",
            "headers": null,
            "http_agent": null,
            "keep_name": false,
            "mode": 416,
            "owner": null,
            "password": null,
            "regexp": null,
            "remote_src": null,
            "repository_url": "http://myrepository.mydomain.local/artifactory/libs-snapshot",
            "selevel": null,
            "serole": null,
            "setype": null,
            "seuser": null,
            "src": null,
            "state": "present",
            "timeout": 10,
            "unsafe_writes": null,
            "url_password": null,
            "url_username": null,
            "username": null,
            "validate_certs": false,
            "verify_checksum": "always",
            "version": "1.0.0-20200527.193024-1"
        }
    },
    "msg": "Failed to download artifact myartifact:1.0.0-20200527.193024-1 because of HTTP Error 404: Not Foundfor URL http://myrepository.mydomain.local/artifactory/libs-snapshot/com/mydomain/myartifact/1.0.0-20200527.193024-1/myartifact-1.0.0-20200527.193024-1.jar"
}

PLAY RECAP *********************************************************************
localhost                  : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0


```



Discussion:
Files identified in the description:
* [`plugins/modules/packaging/language/maven_artifact.py`](https://github.com/ansible-collections/community.general/blob/master/plugins/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 --->
I have a fix for this issue that I'd like to propose. I don't seem to have access to push a branch to this repository to create a pull request. Happy to provide the details once I understand the best way to provide them.
You need to fork the repository and push a branch into your fork, and then create a pull request from your branch for this repository. That's the standard GitHub workflow.

Resolving patch:
diff --git a/plugins/modules/packaging/language/maven_artifact.py b/plugins/modules/packaging/language/maven_artifact.py
--- a/plugins/modules/packaging/language/maven_artifact.py
+++ b/plugins/modules/packaging/language/maven_artifact.py
@@ -215,6 +215,7 @@
 import io
 import tempfile
 import traceback
+import re
 
 from ansible.module_utils.ansible_release import __version__ as ansible_version
 from re import match
@@ -307,7 +308,11 @@ def is_snapshot(self):
     def path(self, with_version=True):
         base = posixpath.join(self.group_id.replace(".", "/"), self.artifact_id)
         if with_version and self.version:
-            base = posixpath.join(base, self.version)
+            timestamp_version_match = re.match("^(.*-)?([0-9]{8}\\.[0-9]{6}-[0-9]+)$", self.version)
+            if timestamp_version_match:
+                base = posixpath.join(base, timestamp_version_match.group(1) + "SNAPSHOT")
+            else:
+                base = posixpath.join(base, self.version)
         return base
 
     def _generate_filename(self):


Test patch: