Repository: ansible-collections/community.general

Issue:
community.general.archive requires lzma module when format != xz when a dest file exists
### Summary

As in summary. It used to work in ansible 2.10

### Issue Type

Bug Report

### Component Name

archive

### Ansible Version

```console (paste below)
$ ansible --version
ansible [core 2.13.5]
  config file = None
  configured module search path = ['/home/docker_user/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/lib/python3.8/site-packages/ansible
  ansible collection location = /home/docker_user/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/local/bin/ansible
  python version = 3.8.12 (default, May 10 2022, 23:46:40) [GCC 8.5.0 20210514 (Red Hat 8.5.0-10)]
  jinja version = 3.1.2
  libyaml = False
```


### Community.general Version

```console (paste below)
$ ansible-galaxy collection list community.general
/usr/local/lib/python3.8/site-packages/requests/__init__.py:89: RequestsDependencyWarning: urllib3 (1.26.12) or chardet (3.0.4) doesn't match a supported version!
  warnings.warn("urllib3 ({}) or chardet ({}) doesn't match a supported "

# /usr/local/lib/python3.8/site-packages/ansible_collections
Collection        Version
----------------- -------
community.general 5.7.0  
```


### Configuration

```console (paste below)
$ ansible-config dump --only-changed
```


### OS / Environment

Rocky 8 on controller node
CentOS 7 on target node

### Steps to Reproduce

<!--- Paste example playbooks or commands between quotes below -->
```yaml (paste below)
~ docker run -d centos:7 tail -f /dev/null
65f128b924c551a83d1e5a68f604559ab49f3a18bc55800138d05a767aeaf053
~ docker exec -it 65f128b924c5 mkdir /tmp/foo
~ docker exec -it 65f128b924c5 touch /tmp/bar
~ ansible all -i 65f128b924c5, -c docker -m community.general.archive -a 'dest=/tmp/bar path=/tmp/foo'
```


### Expected Results

65f128b924c5 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"archived": [],
"arcroot": "/tmp/",
"changed": true,
"dest": "/tmp/bar",
"expanded_exclude_paths": [],
"expanded_paths": [
"/tmp/foo"
],
"gid": 0,
"group": "root",
"missing": [],
"mode": "0644",
"owner": "root",
"size": 54,
"state": "file",
"uid": 0
}

### Actual Results

```console (paste below)
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: NameError: global name 'lzma' is not defined
65f128b924c5 | FAILED! => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "module_stderr": "Traceback (most recent call last):\n  File \"/root/.ansible/tmp/ansible-tmp-1666185363.709719-328-48756839893166/AnsiballZ_archive.py\", line 107, in <module>\n    _ansiballz_main()\n  File \"/root/.ansible/tmp/ansible-tmp-1666185363.709719-328-48756839893166/AnsiballZ_archive.py\", line 99, in _ansiballz_main\n    invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)\n  File \"/root/.ansible/tmp/ansible-tmp-1666185363.709719-328-48756839893166/AnsiballZ_archive.py\", line 48, in invoke_module\n    run_name='__main__', alter_sys=True)\n  File \"/usr/lib64/python2.7/runpy.py\", line 176, in run_module\n    fname, loader, pkg_name)\n  File \"/usr/lib64/python2.7/runpy.py\", line 82, in _run_module_code\n    mod_name, mod_fname, mod_loader, pkg_name)\n  File \"/usr/lib64/python2.7/runpy.py\", line 72, in _run_code\n    exec code in run_globals\n  File \"/tmp/ansible_community.general.archive_payload_GXlgjp/ansible_community.general.archive_payload.zip/ansible_collections/community/general/plugins/modules/files/archive.py\", line 662, in <module>\n  File \"/tmp/ansible_community.general.archive_payload_GXlgjp/ansible_community.general.archive_payload.zip/ansible_collections/community/general/plugins/modules/files/archive.py\", line 629, in main\n  File \"/tmp/ansible_community.general.archive_payload_GXlgjp/ansible_community.general.archive_payload.zip/ansible_collections/community/general/plugins/modules/files/archive.py\", line 604, in get_archive\n  File \"/tmp/ansible_community.general.archive_payload_GXlgjp/ansible_community.general.archive_payload.zip/ansible_collections/community/general/plugins/modules/files/archive.py\", line 535, in __init__\n  File \"/tmp/ansible_community.general.archive_payload_GXlgjp/ansible_community.general.archive_payload.zip/ansible_collections/community/general/plugins/modules/files/archive.py\", line 314, in __init__\n  File \"/tmp/ansible_community.general.archive_payload_GXlgjp/ansible_community.general.archive_payload.zip/ansible_collections/community/general/plugins/modules/files/archive.py\", line 387, in destination_checksums\n  File \"/tmp/ansible_community.general.archive_payload_GXlgjp/ansible_community.general.archive_payload.zip/ansible_collections/community/general/plugins/modules/files/archive.py\", line 588, in _get_checksums\nNameError: global name 'lzma' is not defined\n",
    "module_stdout": "",
    "msg": "MODULE FAILURE\nSee stdout/stderr for the exact error",
    "rc": 1
}
```


### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:
Files identified in the description:
* [`plugins/modules/files/archive.py`](https://github.com/ansible-collections/community.general/blob/main/plugins/modules/files/archive.py)

If these files are incorrect, 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/devel/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @bendoh
[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: notify --->
This requirement is mentioned in the documenation (https://docs.ansible.com/ansible/latest/collections/community/general/archive_module.html#notes), although not where it should be (under requirements).
I created #5392 to update the documentation.
> This requirement is mentioned in the documenation (https://docs.ansible.com/ansible/latest/collections/community/general/archive_module.html#notes), although not where it should be (under requirements).

@felixfontein the requirement says lzma is required *if* using xz format. In my scenario I don't use xz format.
Oh, sorry, I missed that part apparently. The reason for that is https://github.com/ansible-collections/community.general/blob/main/plugins/modules/files/archive.py#L588.

Resolving patch:
diff --git a/plugins/modules/files/archive.py b/plugins/modules/files/archive.py
--- a/plugins/modules/files/archive.py
+++ b/plugins/modules/files/archive.py
@@ -575,6 +575,11 @@ def py26_filter(path):
             self.file.add(path, archive_name, recursive=False, exclude=py26_filter)
 
     def _get_checksums(self, path):
+        if HAS_LZMA:
+            LZMAError = lzma.LZMAError
+        else:
+            # Just picking another exception that's also listed below
+            LZMAError = tarfile.ReadError
         try:
             if self.format == 'xz':
                 with lzma.open(_to_native_ascii(path), 'r') as f:
@@ -585,7 +590,7 @@ def _get_checksums(self, path):
                 archive = tarfile.open(_to_native_ascii(path), 'r|' + self.format)
                 checksums = set((info.name, info.chksum) for info in archive.getmembers())
                 archive.close()
-        except (lzma.LZMAError, tarfile.ReadError, tarfile.CompressionError):
+        except (LZMAError, tarfile.ReadError, tarfile.CompressionError):
             try:
                 # The python implementations of gzip, bz2, and lzma do not support restoring compressed files
                 # to their original names so only file checksum is returned


Test patch:
diff --git a/tests/integration/targets/archive/tasks/main.yml b/tests/integration/targets/archive/tasks/main.yml
--- a/tests/integration/targets/archive/tasks/main.yml
+++ b/tests/integration/targets/archive/tasks/main.yml
@@ -12,6 +12,37 @@
 # Make sure we start fresh
 
 # Test setup
+- name: prep our files
+  copy: src={{ item }} dest={{remote_tmp_dir}}/{{ item }}
+  with_items:
+    - foo.txt
+    - bar.txt
+    - empty.txt
+    - sub
+    - sub/subfile.txt
+
+# Run twice without lzma backport installed, to make sure it does not crash
+- name: Archive - pre-test - first run
+  archive:
+    path: "{{ remote_tmp_dir }}/*.txt"
+    dest: "{{ remote_tmp_dir }}/archive_pretest_1.tar"
+    format: "tar"
+  register: pretest_1
+
+- name: Archive - pre-test - second run
+  archive:
+    path: "{{ remote_tmp_dir }}/*.txt"
+    dest: "{{ remote_tmp_dir }}/archive_pretest_1.tar"
+    format: "tar"
+  register: pretest_2
+
+- name: Archive - validate pre-test
+  assert:
+    that:
+      - pretest_1 is changed
+      - pretest_2 is not changed
+
+# Install dependencies
 - name: Ensure zip is present to create test archive (yum)
   yum: name=zip state=latest
   when: ansible_facts.pkg_mgr == 'yum'
@@ -63,15 +94,6 @@
   when: ansible_python_version.split('.')[0] == '2'
   register: backports_lzma_pip
 
-- name: prep our files
-  copy: src={{ item }} dest={{remote_tmp_dir}}/{{ item }}
-  with_items:
-    - foo.txt
-    - bar.txt
-    - empty.txt
-    - sub
-    - sub/subfile.txt
-
 - name: Define formats to test
   set_fact:
     formats: