Repository: ansible-collections/community.general

Issue:
community.general.linode - document requirement that inventory file end in <something>linode.yml
### Summary

Looking at the docs for community.plugin.linode, there is NO mention that the inventory file must end in <something>linode.yml otherwise it won't work.  Looking at the examples in the doc, there's no mention of this in the examples section, so when trying to use the minimal example in a file called 'foo.yml' which is called with: 

'ansible-inventory -i foo.yml --list'

you get back a useless error message.  

foo.yml
```
plugin: community.general.linode
 access_token:  qwertyuioplkjhgfdsdazxcvbnm123
```

just gives you a warning like this:

```
$ ansible-inventory -i inventory.yml --list
[DEPRECATION WARNING]: DEFAULT_GATHER_SUBSET option, the module_defaults keyword is a more generic
version and can apply to all calls to the M(ansible.builtin.gather_facts) or
M(ansible.builtin.setup) actions, use module_defaults instead. This feature will be removed from
ansible-core in version 2.18. Deprecation warnings can be disabled by setting
deprecation_warnings=False in ansible.cfg.
[WARNING]:  * Failed to parse /home/stoffj/work/Ansible/playbooks/tais-lweb-
linode/hacking/inventory.yml with auto plugin: inventory source
'/home/stoffj/work/Ansible/playbooks/tais-lweb-linode/hacking/inventory.yml' could not be verified
by inventory plugin 'community.general.linode'
[WARNING]:  * Failed to parse /home/stoffj/work/Ansible/playbooks/tais-lweb-
linode/hacking/inventory.yml with yaml plugin: Plugin configuration YAML file, not YAML inventory
[WARNING]:  * Failed to parse /home/stoffj/work/Ansible/playbooks/tais-lweb-
linode/hacking/inventory.yml with ini plugin: Invalid host pattern 'plugin:' supplied, ending in
':' is not allowed, this character is reserved to provide a port.
[WARNING]: Unable to parse /home/stoffj/work/Ansible/playbooks/tais-lweb-
linode/hacking/inventory.yml as an inventory source
[WARNING]: No inventory was parsed, only implicit localhost is available
{
    "_meta": {
        "hostvars": {}
    },
    "all": {
        "children": [
            "ungrouped"
        ]
    }
}
```

### Issue Type

Documentation Report

### Component Name

community.general.linode

### Ansible Version

```console (paste below)
$ ansible --version
ansible [core 2.14.4]
  config file = /home/stoffj/.ansible.cfg
  configured module search path = ['/home/stoffj/work/Ansible/my_modules']
  ansible python module location = /home/stoffj/.local/lib/python3.10/site-packages/ansible
  ansible collection location = /home/stoffj/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/bin/ansible
  python version = 3.10.6 (main, Mar 10 2023, 10:55:28) [GCC 11.3.0] (/usr/bin/python3)
  jinja version = 3.0.3
  libyaml = True
```


### Community.general Version

```console (paste below)
$ ansible-galaxy collection list community.general
[DEPRECATION WARNING]: DEFAULT_GATHER_SUBSET option, the module_defaults keyword is a more generic
version and can apply to all calls to the M(ansible.builtin.gather_facts) or
M(ansible.builtin.setup) actions, use module_defaults instead. This feature will be removed from
ansible-core in version 2.18. Deprecation warnings can be disabled by setting
deprecation_warnings=False in ansible.cfg.

# /usr/lib/python3/dist-packages/ansible_collections
Collection        Version
----------------- -------
community.general 1.3.6

# /home/stoffj/.ansible/collections/ansible_collections
Collection        Version
----------------- -------
community.general 6.5.0

# /home/stoffj/.local/lib/python3.10/site-packages/ansible_collections
Collection        Version
----------------- -------
community.general 6.5.0

```


### Configuration

```console (paste below)
$ ansible-config dump --only-changed
[DEPRECATION WARNING]: DEFAULT_GATHER_SUBSET option, the module_defaults keyword is a more generic
version and can apply to all calls to the M(ansible.builtin.gather_facts) or
M(ansible.builtin.setup) actions, use module_defaults instead. This feature will be removed from
ansible-core in version 2.18. Deprecation warnings can be disabled by setting
deprecation_warnings=False in ansible.cfg.
ANSIBLE_NOCOLOR(/home/stoffj/.ansible.cfg) = True
CONFIG_FILE() = /home/stoffj/.ansible.cfg
DEFAULT_ASK_PASS(/home/stoffj/.ansible.cfg) = False
DEFAULT_FORKS(/home/stoffj/.ansible.cfg) = 5
DEFAULT_GATHERING(/home/stoffj/.ansible.cfg) = implicit
DEFAULT_GATHER_SUBSET(/home/stoffj/.ansible.cfg) = ['all']
DEFAULT_HOST_LIST(/home/stoffj/.ansible.cfg) = ['/home/stoffj/work/Ansible/etc']
DEFAULT_JINJA2_EXTENSIONS(/home/stoffj/.ansible.cfg) = jinja2.ext.do,jinja2.ext.i18n
DEFAULT_MODULE_PATH(/home/stoffj/.ansible.cfg) = ['/home/stoffj/work/Ansible/my_modules']
DEFAULT_MODULE_UTILS_PATH(/home/stoffj/.ansible.cfg) = ['/home/stoffj/work/Ansible/my_module_utils']
DEFAULT_POLL_INTERVAL(/home/stoffj/.ansible.cfg) = 15
DEFAULT_REMOTE_USER(/home/stoffj/.ansible.cfg) = root
DEFAULT_ROLES_PATH(/home/stoffj/.ansible.cfg) = ['/etc/ansible/roles', '/home/stoffj/work/Ansible/roles']
DEFAULT_TRANSPORT(/home/stoffj/.ansible.cfg) = smart
INTERPRETER_PYTHON(/home/stoffj/.ansible.cfg) = auto_silent

```


### OS / Environment

PRETTY_NAME="Ubuntu 22.04.2 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.2 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy


### Steps to Reproduce

<!--- Paste example playbooks or commands between quotes below -->
```yaml (paste below)
plugin: community.general.linode
access_token: qwertyuiopasdfghjklzxcvbnm1234567890
```


### Expected Results

I expected a list of linode's to be generated, but instead I got useless errors only because the NAME of the inventory file used did not end in '''linode.yml''' for some reason.  

### Actual Results

```console (paste below)
[DEPRECATION WARNING]: DEFAULT_GATHER_SUBSET option, the module_defaults keyword is a more generic
version and can apply to all calls to the M(ansible.builtin.gather_facts) or
M(ansible.builtin.setup) actions, use module_defaults instead. This feature will be removed from
ansible-core in version 2.18. Deprecation warnings can be disabled by setting
deprecation_warnings=False in ansible.cfg.
[WARNING]:  * Failed to parse /home/stoffj/work/Ansible/playbooks/tais-lweb-
linode/hacking/inventory.yml with auto plugin: inventory source
'/home/stoffj/work/Ansible/playbooks/tais-lweb-linode/hacking/inventory.yml' could not be verified
by inventory plugin 'community.general.linode'
[WARNING]:  * Failed to parse /home/stoffj/work/Ansible/playbooks/tais-lweb-
linode/hacking/inventory.yml with yaml plugin: Plugin configuration YAML file, not YAML inventory
[WARNING]:  * Failed to parse /home/stoffj/work/Ansible/playbooks/tais-lweb-
linode/hacking/inventory.yml with ini plugin: Invalid host pattern 'plugin:' supplied, ending in
':' is not allowed, this character is reserved to provide a port.
[WARNING]: Unable to parse /home/stoffj/work/Ansible/playbooks/tais-lweb-
linode/hacking/inventory.yml as an inventory source
[WARNING]: No inventory was parsed, only implicit localhost is available
{
    "_meta": {
        "hostvars": {}
    },
    "all": {
        "children": [
            "ungrouped"
        ]
    }
}

```


### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:
Files identified in the description:
* [`plugins/inventory/linode.py`](https://github.com/ansible-collections/community.general/blob/main/plugins/inventory/linode.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 @Charliekenney23 @InTheCloudDan @LBGarber @decentral1se @displague @rmcintosh
[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: notify --->
I think the requirement should stay (many inventory plugins have this requirement), but instead the documentation should be updated to mention this.
>>>>> "Felix" == Felix Fontein ***@***.***> writes:

> I think the requirement should stay (many inventory plugins have
> this requirement), but instead the documentation should be updated
> to mention this.

Can you show my some other plugins with this requirement?  And what is
the thinking behind this?  The inventory/linode.py is the only one
using:

It's doing this function, which seems *really* stupid:

    def verify_file(self, path):
        """Verify the Linode configuration file."""
        if super(InventoryModule, self).verify_file(path):
            endings = ('linode.yaml', 'linode.yml')
            if any((path.endswith(ending) for ending in endings)):
                return True
        return False


What is the purpose here?  What is the thinking?  Why is the error
message so useless?  Heck, just the function documentaion is useless,
there's no error string returned at all here.  But I also admit I'm
not a strong python programmer either.


>>>>> "John" == John Stoffel ***@***.***> writes:

>>>>> "Felix" == Felix Fontein ***@***.***> writes:
>> I think the requirement should stay (many inventory plugins have
>> this requirement), but instead the documentation should be updated
>> to mention this.

> Can you show my some other plugins with this requirement?  And what is
> the thinking behind this?  The inventory/linode.py is the only one
> using:

> It's doing this function, which seems *really* stupid:

>     def verify_file(self, path):
>         """Verify the Linode configuration file."""
>         if super(InventoryModule, self).verify_file(path):
>             endings = ('linode.yaml', 'linode.yml')
>             if any((path.endswith(ending) for ending in endings)):
>                 return True
>         return False


> What is the purpose here?  What is the thinking?  Why is the error
> message so useless?  Heck, just the function documentaion is useless,
> there's no error string returned at all here.  But I also admit I'm
> not a strong python programmer either.

Ok, so here's another plugin that does what I consider to be closer to
the right thing, which is the inventory/lxd.py file.  It's
verify_file() function actually gives you useful information!!!


    def verify_file(self, path):
        """Check the config

        Return true/false if the config-file is valid for this plugin

        Args:
            str(path): path to the config
        Kwargs:
            None
        Raises:
            None
        Returns:
            bool(valid): is valid"""
        valid = False
        if super(InventoryModule, self).verify_file(path):
            if path.endswith(('lxd.yaml', 'lxd.yml')):
                valid = True
            else:
                self.display.vvv('Inventory source not ending in
		"lxd.yaml" or "lxd.yml"')
        return valid


I still question the entire reason behind this type of check, but this
at least is what people should be reporting when they fail a check,
with a useful error message that *clearly* spells out the arbitrary
failure of the check.  

John


Resolving patch:
diff --git a/plugins/inventory/linode.py b/plugins/inventory/linode.py
--- a/plugins/inventory/linode.py
+++ b/plugins/inventory/linode.py
@@ -271,12 +271,25 @@ def populate(self):
                 strict=strict)
 
     def verify_file(self, path):
-        """Verify the Linode configuration file."""
+        """Verify the Linode configuration file.
+
+        Return true/false if the config-file is valid for this plugin
+
+        Args:
+            str(path): path to the config
+        Kwargs:
+            None
+        Raises:
+            None
+        Returns:
+            bool(valid): is valid config file"""
+        valid = False
         if super(InventoryModule, self).verify_file(path):
-            endings = ('linode.yaml', 'linode.yml')
-            if any((path.endswith(ending) for ending in endings)):
-                return True
-        return False
+            if path.endswith(("linode.yaml", "linode.yml")):
+                valid = True
+            else:
+                self.display.vvv('Inventory source not ending in "linode.yaml" or "linode.yml"')
+        return valid
 
     def parse(self, inventory, loader, path, cache=True):
         """Dynamically parse Linode the cloud inventory."""


Test patch:
diff --git a/tests/unit/plugins/inventory/test_linode.py b/tests/unit/plugins/inventory/test_linode.py
--- a/tests/unit/plugins/inventory/test_linode.py
+++ b/tests/unit/plugins/inventory/test_linode.py
@@ -37,11 +37,25 @@ def test_missing_access_token_lookup(inventory):
         assert 'Could not retrieve Linode access token' in error_message
 
 
-def test_verify_file(tmp_path, inventory):
+def test_verify_file_yml(tmp_path, inventory):
     file = tmp_path / "foobar.linode.yml"
     file.touch()
     assert inventory.verify_file(str(file)) is True
 
 
+def test_verify_file_yaml(tmp_path, inventory):
+    file = tmp_path / "foobar.linode.yaml"
+    file.touch()
+    assert inventory.verify_file(str(file)) is True
+
+
+def test_verify_file_bad_config_yml(inventory):
+    assert inventory.verify_file("foobar.linode.yml") is False
+
+
+def test_verify_file_bad_config_yaml(inventory):
+    assert inventory.verify_file("foobar.linode.yaml") is False
+
+
 def test_verify_file_bad_config(inventory):
-    assert inventory.verify_file('foobar.linode.yml') is False
+    assert inventory.verify_file("foobar.wrongcloud.yml") is False