Repository: ansible-collections/community.general

Issue:
snap: error: unknown flag `classic'
### Summary

When install neovim via my ansible playbook using the following yaml, I get an error.

### Issue Type

Bug Report

### Component Name

snap

### Ansible Version

```console (paste below)
$ ansible --version
ansible [core 2.11.2]
  config file = /usr/src/ansible.cfg
  configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3.9/site-packages/ansible
  ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/sbin/ansible
  python version = 3.9.5 (default, May 24 2021, 12:50:35) [GCC 11.1.0]
  jinja version = 3.0.1
  libyaml = True
```


### Configuration

```console (paste below)
$ ansible-config dump --only-changed
DEFAULT_HOST_LIST(/usr/src/ansible.cfg) = ['/usr/src/hosts.yml']
DEFAULT_REMOTE_USER(/usr/src/ansible.cfg) = david.volm
```


### OS / Environment

Amazon Linux 2

### Steps to Reproduce

<!--- Paste example playbooks or commands between quotes below -->
`ansible-playbook -vvv playbooks/test.yml -K`
```yaml (paste below)
---
- name: install neovim
  hosts: all
  tasks:
   - name: 'Install neovim via snap'
     become: yes
     community.general.snap:
       name: nvim
       classic: yes
```


### Expected Results

I expect the playbook to install neovim, but instead I get an error.

### Actual Results

```console (paste below)
The full traceback is:
Traceback (most recent call last):
  File "/tmp/ansible_community.general.snap_payload_MrWFp4/ansible_community.general.snap_payload.zip/ansible_collections/community/general/plugins/module_utils/mh/deco.py", line 42, in wrapper
    func(self, *args, **kwargs)
  File "/tmp/ansible_community.general.snap_payload_MrWFp4/ansible_community.general.snap_payload.zip/ansible_collections/community/general/plugins/module_utils/mh/base.py", line 60, in run
    self.__run__()
  File "/tmp/ansible_community.general.snap_payload_MrWFp4/ansible_community.general.snap_payload.zip/ansible_collections/community/general/plugins/module_utils/mh/mixins/state.py", line 36, in __run__
    return func()
  File "/tmp/ansible_community.general.snap_payload_MrWFp4/ansible_community.general.snap_payload.zip/ansible_collections/community/general/plugins/modules/snap.py", line 228, in state_present
ModuleHelperException
fatal: [an_ip_address]: FAILED! => {
    "changed": false,
    "channel": "stable",
    "classic": true,
    "cmd": "['classic', 'channel', 'state', {'actionable_snaps': ['nvim']}]",
    "cmd_args": [
        "/bin/snap",
        "--classic",
        "install",
        "nvim"
    ],
    "force_lang": "C",
    "invocation": {
        "module_args": {
            "channel": "stable",
            "classic": true,
            "name": [
                "nvim"
            ],
            "state": "present"
        }
    },
    "msg": "Ooops! Snap installation failed while executing '['classic', 'channel', 'state', {'actionable_snaps': ['nvim']}]', please examine logs and error output for more details.",
    "output": {
        "channel": "stable",
        "classic": true,
        "cmd": "['classic', 'channel', 'state', {'actionable_snaps': ['nvim']}]",
        "cmd_args": [
            "/bin/snap",
            "--classic",
            "install",
            "nvim"
        ],
        "force_lang": "C",
        "rc": 64,
        "snaps_installed": [
            "nvim"
        ],
        "stderr": "error: unknown flag `classic'\n",
        "stdout": ""
    },
    "rc": 64,
    "snaps_installed": [
        "nvim"
    ],
    "stderr": "error: unknown flag `classic'\n",
    "stderr_lines": [
        "error: unknown flag `classic'"
    ],
    "stdout": "",
    "stdout_lines": [],
    "vars": {
        "channel": "stable",
        "classic": true,
        "cmd": "['classic', 'channel', 'state', {'actionable_snaps': ['nvim']}]",
        "cmd_args": [
            "/bin/snap",
            "--classic",
            "install",
            "nvim"
        ],
        "force_lang": "C",
        "rc": 64,
        "snaps_installed": [
            "nvim"
        ],
        "stderr": "error: unknown flag `classic'\n",
        "stdout": ""
    }
}
```


### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:
Files identified in the description:
* [`plugins/modules/packaging/os/snap.py`](https://github.com/ansible-collections/community.general/blob/master/plugins/modules/packaging/os/snap.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 @angristan @vcarceler
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
I think the issue has to do with the script putting the flags in the wrong order.
```bash
$ snap --classic install nvim
error: unknown flag `classic'
```
```bash
$ snap install nvim --classic
error: access denied (try with sudo)
```
```bash
$ sudo snap install nvim --classic
(works)
```
@daxxog thanks for reporting this issue.
Does something like `sudo snap install --classic nvim` work as expected too ? (it should, imho)
CC @russoz
> @daxxog thanks for reporting this issue.
> 
> Does something like `sudo snap install --classic nvim` work as expected too ? (it should, imho)

Yes it does! Just doesn't work when it put the '--classic' flag first, which is where I think the script is going wrong.

Resolving patch:
diff --git a/plugins/modules/packaging/os/snap.py b/plugins/modules/packaging/os/snap.py
--- a/plugins/modules/packaging/os/snap.py
+++ b/plugins/modules/packaging/os/snap.py
@@ -133,10 +133,10 @@ class Snap(CmdStateModuleHelper):
     module = dict(
         argument_spec={
             'name': dict(type='list', elements='str', required=True),
-            'state': dict(type='str', required=False, default='present',
+            'state': dict(type='str', default='present',
                           choices=['absent', 'present', 'enabled', 'disabled']),
-            'classic': dict(type='bool', required=False, default=False),
-            'channel': dict(type='str', required=False, default='stable'),
+            'classic': dict(type='bool', default=False),
+            'channel': dict(type='str', default='stable'),
         },
         supports_check_mode=True,
     )
@@ -205,7 +205,7 @@ def state_present(self):
         self.vars.snaps_installed = actionable_snaps
         if self.module.check_mode:
             return
-        params = ['classic', 'channel', 'state']  # get base cmd parts
+        params = ['state', 'classic', 'channel']  # get base cmd parts
         has_one_pkg_params = bool(self.vars.classic) or self.vars.channel != 'stable'
         has_multiple_snaps = len(actionable_snaps) > 1
         if has_one_pkg_params and has_multiple_snaps:


Test patch:
diff --git a/tests/integration/targets/snap/tasks/default.yml b/tests/integration/targets/snap/tasks/default.yml
--- a/tests/integration/targets/snap/tasks/default.yml
+++ b/tests/integration/targets/snap/tasks/default.yml
@@ -10,6 +10,12 @@
     name: snapd
     state: started
 
+- name: Create link /snap
+  file:
+    src: /var/lib/snapd/snap
+    dest: /snap
+    state: link
+
 - name: Inform that snap is installed
   set_fact:
     has_snap: true
diff --git a/tests/integration/targets/snap/tasks/main.yml b/tests/integration/targets/snap/tasks/main.yml
--- a/tests/integration/targets/snap/tasks/main.yml
+++ b/tests/integration/targets/snap/tasks/main.yml
@@ -96,50 +96,50 @@
           - remove_again is not changed
           - remove_again_check is not changed
 
-#    - name: Make sure package from classic snap is not installed
-#      community.general.snap:
-#        name: nvim
-#        state: absent
-#
-#    - name: Install package from classic snap
-#      community.general.snap:
-#        name: nvim
-#        state: present
-#        classic: true
-#      register: classic_install
-#
-#    # testing classic idempotency
-#    - name: Install package from classic snap again
-#      community.general.snap:
-#        name: nvim
-#        state: present
-#        classic: true
-#      register: classic_install_again
-#
-#    - name: Assert package has been installed just once
-#      assert:
-#        that:
-#          - classic_install is changed
-#          - classic_install_again is not changed
-#
-#    # this is just testing if a package which has been installed
-#    # with true classic can be removed without setting classic to true
-#    - name: Remove package from classic snap without setting classic to true
-#      community.general.snap:
-#        name: nvim
-#        state: absent
-#      register: classic_remove_without_true_classic
-#
-#    - name: Remove package from classic snap with setting classic to true
-#      community.general.snap:
-#        name: nvim
-#        state: absent
-#        classic: true
-#      register: classic_remove_with_true_classic
-#
-#    - name: Assert package has been removed without setting classic to true
-#      assert:
-#        that:
-#          - classic_remove_without_ture_classic is changed
-#          - classic_remove_with_ture_classic is not changed
+    - name: Make sure package from classic snap is not installed
+      community.general.snap:
+        name: nvim
+        state: absent
+
+    - name: Install package from classic snap
+      community.general.snap:
+        name: nvim
+        state: present
+        classic: true
+      register: classic_install
+
+    # testing classic idempotency
+    - name: Install package from classic snap again
+      community.general.snap:
+        name: nvim
+        state: present
+        classic: true
+      register: classic_install_again
+
+    - name: Assert package has been installed just once
+      assert:
+        that:
+          - classic_install is changed
+          - classic_install_again is not changed
+
+    # this is just testing if a package which has been installed
+    # with true classic can be removed without setting classic to true
+    - name: Remove package from classic snap without setting classic to true
+      community.general.snap:
+        name: nvim
+        state: absent
+      register: classic_remove_without_true_classic
+
+    - name: Remove package from classic snap with setting classic to true
+      community.general.snap:
+        name: nvim
+        state: absent
+        classic: true
+      register: classic_remove_with_true_classic
+
+    - name: Assert package has been removed without setting classic to true
+      assert:
+        that:
+          - classic_remove_without_true_classic is changed
+          - classic_remove_with_true_classic is not changed
   when: has_snap