Reuse code from modules in other collections

Preclaimer: I’m having a general module development issue. To describe my specific usecase I’m talking about an existing Rundeck-module below, but I’d say my question applies to other modules as well:

I’m using the community.general.rundeck_acl_policy module but found that it is lacking a parameter that I need. I’ll report a feature request within the GitHub Issues (along with a PR), but for the time being I’d like to have something available without having to fork the community-collection or similar.

I just started working with module-development and thought it should be easy to add a new wrapper-module that adds the missing option and reuses most of the original module. Sort of just a wrapper. Using Pythons monkey-patching this is no problem. I came up with the following and added it to my local collection:

File: my_namespace/my_collection/plugins/modules/rundeck_acl_policy_ext.py:

#!/usr/bin/python
# -*- coding: utf-8 -*-

from __future__ import absolute_import, division, print_function
__metaclass__ = type

DOCUMENTATION = "..."
EXAMPLES = "..."
RETURN = "..."

import ansible_collections.community.general.plugins.modules.rundeck_acl_policy as rundeck_acl_policy
import ansible_collections.community.general.plugins.module_utils.rundeck as rundeck

def api_argument_spec():
    spec = rundeck.api_argument_spec()
    spec.update(dict( # This parameter is needed but missing from `url_argument_spec`
        follow_redirects=dict(type='str', default='safe', choices=['all', 'no', 'none', 'safe', 'urllib2', 'yes']),
    ))
    return spec

def main():
    # monkeypatch our version of api_argument_spec so it is called instead of the original one:
    rundeck_acl_policy.api_argument_spec = api_argument_spec
    rundeck_acl_policy.main()

if __name__ == '__main__':
    main()

Now when including the task into my playbook I get the following error:

fatal: [host01]: FAILED! => {"msg": "Unexpected failure during module execution: CollectionModuleUtilLocator can only locate below ansible_collections.(ns).(coll).plugins.module_utils, got ('ansible_collections', 'community', 'general', 'plugins', 'modules', 'rundeck_acl_policy')", "stdout": ""}

So essentially, as far as I understand, it’s not possible to import a module from another collection. I guess this is a security feature?

I would like to patch the original module and add the missing option. Is it possible to do this without simply copying it over? Can I configure CollectionModuleUtilLocator in a way that would help me? Are there similar modules out there?

I found this page in the docs, but besides

You can copy other people’s modules and plugins.

There seems to be no info about my usecase. Perhaps this is something that could be extended when we have an answer to my question. I’ll see that I open an issue for that as well as soon as I have gained more knowledge.

In general you can import code from other collections. But please note that modules are special. Modules can generally only import code from module_utils, and not from anywhere else inside the ansible Python top-level namespace. (Imports from completely other places are allowed, but should better be guarded, see the import sanity test and https://docs.ansible.com/ansible/devel/dev_guide/testing/sanity/import.html.)

So you can import code from ansible-core’s module_utils, your own collection’s module_utils, and other collection’s module_utils, but not from anywhere else.

Other plugins do not have this restriction.

(The reason is that modules are usually executed on a remote target, so Ansible has to copy everything over to the remote target that’s needed by the module. Modules were always only allowed to use their own code and code from module_utils, even before collections were a thing, for that reason.)

1 Like

Thanks for the quick answer. These guards make sense, obviously. Bummer that my quick-hack won’t work though :slight_smile:

I’ll provide a PR to solve this in the original collection anyways, so eventually it gets solved. Perhaps I can change the original code to use module_utils a bit more so future changes are easier to realize.