We have an internal collection with roles, one of which lists available updates, and another that actually performs the updates, excluding necessary packages. I am trying to tweak the role that lists updates so that it doesnβt list the packages that are excluded in the actual update. I was unable to find a combination of Ansible functions/filters to accomplish this, so I have resorted to trying to write a module within this collection that helps with some string formatting. However, the role cannot find the module, even though they are within the same collection.
Below you can find my directory structure, role contents, and module contents:
# Working directory
βββ CHANGELOG.md
βββ README.md
βββ galaxy.yml
βββ meta
β βββ runtime.yml
βββ plugins
β βββ modules
β βββ __init__.py
β βββ add_string.py
βββ renovate.json
βββ roles
βββ list_linux_updates
β βββ handlers
β β βββ main.yml
β βββ meta
β β βββ argument_specs.yml
β β βββ main.yml
β βββ tasks
β βββ main.yml
βββ update_linux_packages
βββ defaults
β βββ main.yml
βββ meta
β βββ argument_specs.yml
β βββ main.yml
βββ tasks
βββ main.yml
# list_linux_updates role
---
- name: List updates
block:
- name: Updates for RedHat/SUSE and derived distributions
when: ansible_facts.pkg_mgr == 'dnf'
block:
- name: Prepare submitted packages to be excluded
company.update_roles.add_string:
in_list: list_linux_updates_packages_exclude
in_string: "--exclude"
prepend: true
register: list_linux_updates_packages_exclude_prepped
- name: Store submitted packages to be excluded
ansible.builtin.set_fact:
list_linux_updates_packages_exclude_joined: "{{ list_linux_updates_packages_exclude_prepped.output | join(' ') }}"
- name: Gather available package updates for RedHat/SUSE systems
ansible.builtin.command:
cmd: "dnf check-update --quiet {{ list_linux_updates_packages_exclude_joined }}"
register: list_linux_updates_updatable_packages_raw_RH
changed_when: list_linux_updates_updatable_packages_raw_RH.rc in [0, 100]
failed_when: list_linux_updates_updatable_packages_raw_RH.rc not in [0, 100]
notify:
- Write RedHat packages to variable
- name: Updates for Debian/Ubuntu and derived distributions
when: ansible_facts.pkg_mgr == 'apt'
block:
- name: Gather available package updates for Debian/Ubuntu systems
ansible.builtin.command:
cmd: apt-get upgrade --dry-run
register: list_linux_updates_updatable_packages_raw_DB
changed_when: list_linux_updates_updatable_packages_raw_DB.rc == 0
notify:
- Write Debian packages to variable
# Module
#!/usr/bin/python
# Created using the official module in a collection example:
# https://docs.ansible.com/projects/ansible/latest/dev_guide/developing_modules_general.html#creating-a-module-in-a-collection
DOCUMENTATION = r'''
---
module: add_string
author: department_X
version_added: "1.0.0"
short_description: Prepends or appends a string to each string item in a list.
description:
- Prepends or appends a string to each string item in a list.
- It makes no changes on any host, and is purely needed for some formatting of text.
options:
in_list:
description: This is a list of strings to which the string must be added.
required: true
type: list
in_string:
description: This is the string to add to each element in the list.
required: true
type: str
prepend:
description: Prepends string to each element. If false, it will append instead.
required: false
type: bool
default: true
'''
EXAMPLES = r'''
# Base usage
- name: Add "added" to every object in the list
company.update_roles.add_string:
in_list: ['ring', 'around', 'the', 'rosie']
in_string: 'added'
register: prepend_text
- name: Print output
ansible.builtin.debug:
var: prepend_text.output
# returns: ['added ring','added around','added the','added rosie']
# Append instead of prepend
- name: Add "added" to every object in the list
company.update_roles.add_string:
in_list: ['ring', 'around', 'the', 'rosie']
in_string: 'added'
prepend: false
- name: Print output
ansible.builtin.debug:
var: append_text.output
# returns: ['ring added','around added','the added','rosie added']
'''
RETURN = r'''
# These are examples of possible return values, and in general should use other names for return values.
input:
description: The original name param that was passed in.
type: list
returned: always
sample: ['ring','around','the','rosie']
output:
description: The output message that the test module generates.
type: list
returned: always
sample: ['added ring','added around','added the','added rosie']
'''
from ansible.module_utils.basic import AnsibleModule
def run_module():
# define available arguments/parameters a user can pass to the module
module_args = dict(
in_list=dict(type='list', required=True),
in_string=dict(type='str', required=True),
prepend=dict(type='bool', required=False, default=True)
)
result = dict(
changed=False,
input=[],
output=[]
)
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True
)
if module.check_mode:
module.exit_json(**result)
result['input'] = module.params['in_list']
result['output'] = add_string(module.params['in_list'], module.params['in_string'], module.params['prepend'])
if module.params['in_list'] != result['output']:
result['changed'] = True
module.exit_json(**result)
def add_string(in_list: list, in_string: str, prepend: bool):
if in_list == []:
return []
else:
out_list = []
for item in in_list:
if prepend:
out_list.append(str(item).strip() + " " + in_string.strip())
else:
out_list.append(in_string.strip() + " " + str(item).strip())
return out_list
def main():
run_module()
if __name__ == '__main__':
main()
I have tried to test it via command line, but it yields this error:
ansible localhost -m company.update_roles.add_string -a "in_list=['one','two','three'] in_string=word" --playbook-dir=$PWD
# result:
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: Error loading plugin 'company.update_roles.add_string': No module named 'ansible_collections.company'
[ERROR]: Task failed: Cannot resolve 'company.update_roles.add_string' to an action or module. Does anyone here have an inkling of what I might be missing?
Task failed.
Origin: <adhoc 'company.update_roles.add_string' task>
{'action': 'company.update_roles.add_string', 'args': {'in_list': "['one','two','three']", 'in_string': 'word'}, [...]
<<< caused by >>>
Cannot resolve 'company.update_roles.add_string' to an action or module.
Origin: <CLI option '-m'>
company.update_roles.add_string
localhost | FAILED! => {
"changed": false,
"msg": "Task failed: Cannot resolve 'company.update_roles.add_string' to an action or module."
}
Which is pretty similar to the error I get when running ansible-lint:
$ ansible-lint
WARNING Unable to load module company.update_roles.add_string at roles/list_linux_updates/tasks/main.yml:7 for options validation
WARNING Invalid value (None)for resolved_fqcn attribute of company.update_roles.add_string module.
WARNING Listing 1 violation(s) that are fatal
# Rule Violation Summary
1 syntax-check profile:min tags:core,unskippable
Failed: 1 failure(s), 0 warning(s) on 27 files.
syntax-check[unknown-module]: couldn't resolve module/action 'company.update_roles.add_string'. This often indicates a misspelling, missing collection, or incorrect module path.
roles/list_linux_updates/tasks/main.yml:7:11
Iβm sure that there are plenty of things wrong with the module itself, but so long as I cannot test it, I wonβt know and learn. I also have a bit of a feeling that this may not be the intended use of modules, or even the best way to tackle the issue at hand, but itβs the only one that I could think of that has proven somewhat fruitful. Nevertheless, I do want to know how I can use modules within the same collection.