google.cloud/plugins
Vi P 307839b810
Remove plugin option from inventory config for compatibility with passthrough plugins
One niche but useful feature of Ansible inventory plugins is the ability to call one plugin from another. For example, you can write a custom plugin (`my.plugin.multi_cloud`) that acts as a sort of normalization interface between project inventories deployed on multiple cloud platforms. This makes it feasible to create a single set of Ansible Playbooks that can act on hosts across cloud platforms because they can expect managed node (inventory host) data to be consistent across those cloud platform vendors.

For example, `my.plugin.multi_cloud` inventory plugin might look something like the following code, which simply dynamically loads one or another underlying (upstream) vendor'ed inventory plugins based on the value of the shell environment variable called `CLOUD_PLATFORM`:

```python
#!/usr/bin/env python3

from ansible.errors import AnsibleError, AnsibleParserError
from ansible.plugins.inventory import BaseInventoryPlugin
from ansible.plugins.loader import inventory_loader
import os

class InventoryModule(BaseInventoryPlugin):

    def parse(self, inventory, loader, path, cache=True):
        super(InventoryModule, self).parse(inventory, loader, path)

        # Map the cloud platform to the appropriate vendor's inventory plugin.
        cloud_platform = os.environ.get('CLOUD_PLATFORM', '').lower()
        if cloud_platform == 'aws':
            plugin_fqcn = 'amazon.aws.aws_ec2'
        elif cloud_platform == 'gcp':
            plugin_fqcn = 'google.cloud.gcp_compute'
        else:
            raise AnsibleParserError(
                    f"Error: Unrecognized or unset cloud platform '{cloud_platform}'. "
                    f"Set ENTROPY_CLOUD_PLATFORM to 'aws' or 'gcp'.\n"
            )

        if not inventory_loader.has_plugin(plugin_fqcn):
            raise AnsibleParserError(f"Error: '{plugin_fqcn}' inventory plugin not found.\n")

        # Load the requested plugin.
        self.inventory_plugin = inventory_loader.get(plugin_fqcn)
```

In the above example, we see code loading either this `google.cloud.gcp_compute` inventory plugin or Amazon's `amazon.aws.aws_ec2` inventory plugin.

Later, we can invoke the underlying (upstream) plugin in a passthrough manner like this:

```python
        # Passthrough to the underlying plugin.
        if self.inventory_plugin.verify_file(path):
            self.inventory_plugin.parse(inventory, loader, path)
        else:
            raise AnsibleParserError(f"Error: Inventory configuration file '{path}' failed verification by plugin '{plugin_fqcn}'")
```

However, in order for this to work, we must supply the underlying plugin with knowledge of the fact that we are calling it from a different actual plugin. This is facilitated via Ansible's built in `_redirected_names` class property. Before calling the underlying plugin's `parse()` method, we must first do:

```python
        self.inventory_plugin._redirected_names.append(InventoryModule.NAME)
```

Now the underlying plugin will be permitted to run because the underlying plugin is informing Ansible that one of the names it is permitted to use is this "redirected" (aliased) name of the calling plugin. We have effectively monkey-patched the plugin during runtime, which is exactly what we want.

Unfortunately, for _this_ `google.cloud.gcp_compute` inventory plugin, that's not enough, because of the fact that the `plugin` option in its configuration file is also checked and compared against this same name. That's something that, for example, the `amazon.aws.aws_ec2` inventory plugin _doesn't_ do, and for good reason: enforcing this check with hardcoded options breaks the built-in functionality of the Ansible module loading alias features.

That's why I'm suggesting we remove this option. It isn't needed for the `auto` inventory plugin to load the plugin correctly, nor does it ever really need to be checked once this plugin is actually running; it's already running! But its presence _does_ break existing Ansible features, and makes the above use case of a pass-through plugin, for example, infeasible.

Thanks for considering this proposal.
2025-03-14 23:12:41 -04:00
..
doc_fragments Fix doc_fragments/gcp.py (#587) 2023-07-22 00:31:54 +00:00
filter Add a basic DOCUMENTATION string for gcp_kms_filters.py 2024-08-22 16:04:26 -07:00
inventory Remove plugin option from inventory config for compatibility with passthrough plugins 2025-03-14 23:12:41 -04:00
lookup Fix lint errors in the gcp_secret_manager lookup plugin 2024-11-04 10:28:23 -08:00
module_utils chore: removed unused imports 2023-11-20 11:17:25 -08:00
modules Fix doc lint errors in gcp_pubsub_subscription 2025-01-10 14:52:33 -08:00
README.md galaxy.yml commit 2019-09-03 13:28:41 -07:00

Collections Plugins Directory

This directory can be used to ship various plugins inside an Ansible collection. Each plugin is placed in a folder that is named after the type of plugin it is in. It can also include the module_utils and modules directory that would contain module utils and modules respectively.

Here is an example directory of the majority of plugins currently supported by Ansible;

└── plugins
    ├── action
    ├── become
    ├── cache
    ├── callback
    ├── cliconf
    ├── connection
    ├── filter
    ├── httpapi
    ├── inventory
    ├── lookup
    ├── module_utils
    ├── modules
    ├── netconf
    ├── shell
    ├── strategy
    ├── terminal
    ├── test
    └── vars

A full list of plugin types can be found at Working With Plugins.