Compute variables per host based on group_vars

I have some groups defined for each OS under group_vars: debian and opensuse directories. For this example I will only write about debian.

I defined this file: group_vars/all/ansible_groups.yaml: Abstract groups

In this debian directory I defined Debian users and groups:
10_groups.yaml: Abstract groups
20_users.yaml: Abstract users

And then there’s this file:
30_users_and_groups.yaml with that content:

---
debian_users_and_groups: "{{ debian_users | debian_resolve_users_and_groups(debian_groups, ansible_groups, debian_gids_ids, debian_uids_ids, group_names) }}"

As you can see there’s a custom filter with the name debian_resolve_users_and_groups that takes the following group vaiables as input:

debian_users
debian_groups
ansible_groups
debian_gids_ids
debian_uids_ids
group_names

So I will get the following variables for each host:

gids_ids
uids_ids
groups
users
ansible_groups
group_names

The filter will compute the debian_users_and_groups variable in the 30_users_and_groups.yaml file. That will be computed per host. The filter is already doing exacly what I want, that’s already working :check_mark:

Next step:
I want to get rid of the filter as I think it’s not the best way to handle these variable computations. Instead I thought about using the VarsModule class under the vars_plugins directory. As I understood that’s the “correct” way to handle variables, did I understood that correctly?

So I created vars_plugins/debian_users_and_groups.py. I took the host_group_vars.py file as example:


"""Debian users and groups."""

DOCUMENTATION = """
    name: debian_users_and_groups
    short_description: Generate Debian users and groups per host
    requirements:
        - Enabled in configuration
    description:
        - Loads Debian group data from group_vars/debian/10_groups.yml and user data from group_vars/debian/20_users.yml
    required: True
    vars:
      - debian_users_and_groups
"""

import contextlib
from typing import Any
from ansible.plugins.vars import BaseVarsPlugin
from ansible.utils.vars import combine_vars
from ansible.inventory.host import Host


class VarsModule(BaseVarsPlugin):
    """Generate Debian users and groups per host."""

    REQUIRES_ENABLED = True
    is_stateless = True

    # Functions from the filter will be copied here -->

    def get_vars(self, loader, path, entities, cache=True) -> dict[Any, Any]:
        if not isinstance(entities, list):
            entities = [entities]

        data = {}
        for entity in entities:
            if isinstance(entity, Host):
                #I need to access all hostvars (and the group_vars):
                hostvars = ... # But how?

                # Then I need to get the variables from hostvars or group_vars, don't know:
                gids_ids = hostvars.get("gids_ids", {})
                uids_ids = hostvars.get("uids_ids", {})
                groups = hostvars.get("groups", {})
                users = hostvars.get("users", {})
                ansible_groups = hostvars.get("ansible_groups", {})
                group_names = hostvars.get("group_names", [])

                users_and_groups: dict[Any, Any] = {}

                # Logic from the filter will be copied here -->

                data = combine_vars(data, users_and_groups)

        return data

How can I access the already (?) computed hostvars (also containing the group_vars) for the host?

I think a custom lookup plugin would be a better solution. The vars and varnames lookups might be able to help, or serve as examples.

- name: retrieve variables based on a prefix
  debug:
    msg: "{{ q('vars', *q('varnames', 'debian_.+')) }}"

Vars plugins don’t have access to the results of other vars plugins. The Host objects only have access to inventory variables (#3 and #8 in variable precedence):

        data = {}
        for entity in entities:
            if not isinstance(entity, Host):
                continue

            group_vars = {}
            for group in entity.groups:
                group_vars = combine_vars(group_vars, group.get_vars())

            inventory_vars = combine_vars(group_vars, entity.get_vars())

Thanks @shertel, that already clears up some confusion on my side! If the vars_plugins can’t read host and group vars it’s not what I need. Your suggestion of using vars or varnames is also interesting, bu they ae already working on the task level, but my already in use filter does it job before what I like to preserve. …so… maybe I already found the solution weeks ago by simple using a filter? :grinning_face: At least that filter does its job flawlessly.

If you are looking for an alternative, a custom lookup is a good option, whereas vars plugin is not. I linked to the specific lookup plugins as implementation examples since they’re simple and variable-related, since I don’t know what your filter is doing. You may not be able to easily replace your filter with builtins, but if you can, it could simplify your content.