Skip to content

Finding plugins

alai find answers "which collection has this?" for a module, plugin, role or playbook, and "where did it go?" for one that was moved or removed. alai find searches a list of every plugin in every collection on the index, so it finds collections you haven't installed and lists the ones you have first.

Finding plugins

What to search for

Query Finds Example
A word Every plugin whose name contains it, exact names first alai find user finds user, win_user and user_info
A glob, with *, ? or [...] Short names that match it, or with namespace.collection.* the plugins in that collection alai find '*_info', alai find 'community.general.*'
A full name (FQCN) That plugin's details alai find community.general.docker_container

A glob is matched against full names once it has a namespace and a collection in front, and against short names otherwise. For example, 'mgdis.ovh.*' finds every plugin in mgdis.ovh, including ones in subdirectories such as mgdis.ovh.cloud.block_storage.block_storage. To search every collection in a namespace, give both parts, as in alai find 'community.*.*'. 'community.*' on its own matches short names, so it finds only plugins in a community/ subdirectory. Matching short names is also how to search a subdirectory, as in alai find 'cloud.*'.

-x/--exact matches whole names only, so alai find -x user leaves out win_user. -t/--type limits the search to the types given, using the names ansible-doc -t takes (module, filter, lookup, callback…), plus role and playbook.

When nothing matches, alai suggests similar names and exits with code 1. For a full name it suggests full names, such as ansible.windows.win_disk_info for ansible.legacy.win_disk_info.

Quoting globs

Put a glob in single quotes, as in alai find '*_info' rather than alai find *_info. Without quotes, the shell expands *, ? and [...] before alai sees them. Single quotes keep everything as typed. A word or a full name with no glob characters does not need to be quoted.

Here docker_container_info is a file in the current directory, so the unquoted *_info becomes docker_container_info before alai runs, and alai searches for that one name:

Why globs need quotes

Examples

Search by part of a name, or the whole name with -x, or only what's installed with --installed:

Searching by part of a name, or the whole name

Narrow it to a type, or several:

Searching one type, or several

Quoted globs match short names, or full names when they have dots:

Searching with globs

Given a full name, alai find shows that plugin's details, such as whether it's installed and where it redirects:

An old name that no longer leads anywhere, and an ansible.legacy name

When piping to other tools such as jq, --color and --output-format control how the output looks:

$ alai find user --color always | less -R
$ alai find user --output-format json | jq -r '.plugins[] | select(.matched) | .fqcn'
$ alai find -x ipmath --output-format json \
    | jq -r '.collections | to_entries[] | select(.value.installed) | .key'

When piped, alai lists every match rather than the first 20, and --color always keeps the colors.

What's listed first

Matches are in two sections. Installed has the collections in the project's collections directory, plus ansible.builtin and eda.builtin, which come with ansible-core and ansible-rulebook. Not installed has everything else on the index. Within each section, matches are listed in this order:

  1. Exact names before partial matches.
  2. Plugins that aren't redirects, then redirects, then ones that are removed or lead nowhere.
  3. ansible.builtin and eda.builtin.
  4. Collections present in alai's cache, for example one installed in a separate project.
  5. Collections in the ansible package, which bundles collections the Ansible community has vetted.
  6. Other collections in the same namespaces as the ansible package's collections.
  7. Everything else, with collections that look like copies of a collection in the ansible package last. Their names are gray, and a plugin's details say which collection it seems to copy. The copy check is a guess based on at least half their plugins having the same names, so it only ranks them lower and never hides them.
  8. Modules before other types, then by collection and name.

The order puts the matches you're most likely to want first.

On a terminal, only the first 20 matches are listed, installed ones first. When you need more, --limit N changes the limit and --limit 0 lists them all. When the output goes to a pipe or a file, such as alai find user | less or | grep, there is no limit unless --limit is given. To list only the plugins that are installed, use --installed.

Partial matches leave out removed plugins and aliases that point to a plugin with the same name. Use -a/--all to list them too.

What the markers mean

Each line starts with a marker:

Marker Means
✓ In an installed collection
· In a collection that isn't installed
↑ Added in a newer version of the collection than the one installed. Upgrade it to use it
↪ Redirects to the plugin shown after →. The name still works, as Ansible loads that plugin instead
↪ Installed, but redirects through a collection that isn't, so Ansible can't load it until that's installed too
⚠ Deprecated, or redirects through a deprecated name. Its details say when it's due to be removed, and usually what to use instead
✗ Removed, or its redirect leads nowhere Ansible can load

When more than one applies, the marker shows the one that matters most for using the plugin, in this order:

  1. ✗ means you can't use it.
  2. ⚠ means you shouldn't use it.
  3. ↑ or a yellow ↪ means you can use it once something is upgraded or installed.
  4. A cyan ↪ means you can use it under another name.
  5. ✓ or · means you can use it.

When a plugin isn't simply usable, its line says why in place of the description. For example, a redirect shows the plugin it ends at, with anything that stands in the way in brackets:

↪ module    ansible.builtin.nmcli               → community.general.nmcli
↪ module    ansible.builtin.docker_container    → community.docker.docker_container (not installed)
⚠ module    awx.awx.tower_project               → awx.awx.project (deprecated)
↑ module    ansible.builtin.disk_info           → community.general.disk_info (needs 3.0.0, local 2.5.0)
✗ module    ansible.builtin.win_domain_user     → community.windows.win_domain_user (removed)

Otherwise the description is a short reason why it is not a normal target, such as needs 3.9.1 (local 2.8.0), deprecated, removed in 13.0.0 or redirect loop. local is the version installed in the project. A plugin's details say when it moved or was renamed, and show each step of a redirect.

alai find renders Ansible's documentation markup as best it can. For example, C(lxc) is shown in cyan, and U() as a link.

It depends on your terminal

How much of this you see depends on the terminal. Italics and links (OSC 8 hyperlinks) aren't supported everywhere, and a terminal that doesn't support one shows the plain text instead. Some terminals only mark a link while you hold Ctrl (Cmd on macOS) and hover over it, as Ghostty does. Inside tmux, links only reach the terminal once tmux is told the terminal supports them. Add set -as terminal-features ",xterm-ghostty:hyperlinks" to tmux's config, with your terminal's TERM or * in place of xterm-ghostty, then detach and reattach.

A plugin's details

Searching for a full name shows the details of that plugin, such as:

  • Installed: Whether the plugin is installed for the project. For ansible.builtin and eda.builtin it means Ansible sees the plugin wherever ansible-core or ansible-rulebook is. alai doesn't check that either is in your Python environment, and the line says so.
  • Versions: The versions the plugin is present in.
  • Removal: When a deprecated plugin is planned to be removed, if its collection provides the version or date.
  • Route: The plugin, then each name it redirects to. The plugin it ends at has its description under it. A step's deprecation or removal message from its collection is shown under that step, as Ansible warns at every step it passes.
  • Hint: Extra information on how to install or use the plugin.

A plugin's details and where its redirects lead

A redirect can lead to another that redirects again, and alai shows the whole chain so it's clear how Ansible resolves the plugin name.

For scripts

With --output-format json, the output holds the facts alai knows about each plugin, rather than the human-friendly conclusions the text output shows, and the caller decides how to interpret and present them. The JSON output has no limit unless --limit is given. The following is the JSON output of alai find ansible.builtin.docker_container --output-format json:

{
  "v": 1,
  "plugins": [
    {
      "type": "module",
      "name": "docker_container",
      "collection": "ansible.builtin",
      "since": null,
      "until": null,
      "short_description": null,
      "routing": {
        "redirect": "community.docker.docker_container"
      },
      "fqcn": "ansible.builtin.docker_container",
      "index": null,
      "matched": true
    },
    {
      "type": "module",
      "name": "docker_container",
      "collection": "community.docker",
      "since": null,
      "until": null,
      "short_description": "manage Docker containers",
      "routing": null,
      "fqcn": "community.docker.docker_container",
      "index": null,
      "matched": false
    }
  ],
  "collections": {
    "ansible.builtin": {
      "installed": true,
      "version": null,
      "path": null,
      "source": null,
      "release": {
        "python_index": "https://pypi.org/simple/",
        "package": "ansible-core",
        "version": "2.21.5"
      }
    },
    "community.docker": {
      "installed": true,
      "version": "5.4.0",
      "path": "/home/me/project/collections/ansible_collections/community/docker",
      "source": {
        "index": "https://index.alai.sh/"
      },
      "release": null
    }
  },
  "omitted": 0
}

In this case community.docker.docker_container was read from the collection installed in the project, so its index is null. ansible.builtin.docker_container's is null too, as it was read from the ansible-core release in its collection's release. The community.docker entry in collections still has a source, as that's where the installed collection came from, not where its plugins were read from.

If an installed collection doesn't have a plugin, but a newer version on the index does, that plugin's entry comes from the index and has its index set. With ansible.windows 3.8.0 installed, alai find 'ansible.windows.win_d[is]*' --output-format json lists win_dsc from the installed files and win_disk_info from the index, as win_disk_info was only added in 3.9.1:

{
  "v": 1,
  "plugins": [
    {
      "type": "module",
      "name": "win_dsc",
      "collection": "ansible.windows",
      "since": null,
      "until": null,
      "short_description": "Invokes a PowerShell DSC configuration",
      "routing": null,
      "fqcn": "ansible.windows.win_dsc",
      "index": null,
      "matched": true
    },
    {
      "type": "module",
      "name": "win_disk_info",
      "collection": "ansible.windows",
      "since": "3.9.1",
      "until": null,
      "short_description": "Gather disk, partition, and volume information on Windows",
      "routing": null,
      "fqcn": "ansible.windows.win_disk_info",
      "index": "https://index.alai.sh/",
      "matched": true
    }
  ],
  "collections": {
    "ansible.windows": {
      "installed": true,
      "version": "3.8.0",
      "path": "/home/me/project/collections/ansible_collections/ansible/windows",
      "source": {
        "index": "https://index.alai.sh/"
      },
      "release": null
    }
  },
  "omitted": 0
}

The ansible.windows entry in collections still describes the installed collection. The index set on win_disk_info is what says it isn't in the installed version and was read from the index.

Field
v The format's version, which changes if a field is removed or changes meaning
plugins The same matches the text output lists, in the same order, then every plugin their redirects lead to that alai found, each listed once. Like the text output, it leaves out removed plugins and aliases unless they're named exactly or -a/--all is given
collections Every collection the plugins are in or redirect to, by name
omitted How many matches --limit left out

Every plugin and collection has all of its fields, null where one doesn't apply.

Each collection has:

Field
installed Whether it's installed. ansible.builtin and eda.builtin always are
version The installed version, from its MANIFEST.json, or its galaxy.yml if it has none. null if it isn't installed or neither can be read, and for ansible.builtin and eda.builtin
path The installed collection's directory. null if it isn't installed, and for ansible.builtin and eda.builtin
source Where the installed collection came from, as the lock writes it, such as {"index": "https://index.alai.sh/"} or {"editable": "../my.tools"}. null unless alai knows, from the lock or from installing it
release For ansible.builtin and eda.builtin, the release their plugins were read from: its python_index, package and version. null for any other

Each plugin has:

Field
type Its type, as -t takes it, such as module, filter or role
name Its short name
collection The collection it's in, or ansible.builtin for ansible-core's own and eda.builtin for ansible-rulebook's
since The lowest version of its collection that has it. For ansible.builtin and eda.builtin, it's the ansible-core or ansible-rulebook version it was added in. null if unknown, so always for one read from an installed collection
until The version of its collection it was removed in. null unless it was removed, so always for one read from an installed collection
short_description One line from its documentation, with any Ansible markup such as C(...) as written. null if it has none
routing Its entry in its collection's meta/runtime.yml plugin_routing (redirect, deprecation, tombstone), as the collection wrote it. null if it has none
fqcn Its full name
index The URL of the index whose list it was read from, or null. When null, the plugin was read from the installed collection's files, or for ansible.builtin and eda.builtin, from the release
matched Whether it matched the search, rather than being listed because a match redirects to it

Everything but fqcn, index and matched comes from the index or the installed collection, and for ansible.builtin and eda.builtin from the release. An installed collection's plugins are read from its files, but a plugin only a newer version has comes from the index and has index set.

The JSON doesn't say whether a plugin can be used, as the details' Installed line does. alai works that out from the JSON facts by following each routing.redirect as ansible-core does and checking each step's since and until against the installed version of its collection.

Where the list comes from

The index publishes the list, plugins.jsonl, alongside its collections. alai downloads it once and then only again when it changes. plugins.jsonl holds each plugin's name, type, collection, short description, the versions that have it and its collection's routing for it from meta/runtime.yml. With several indexes, alai searches each one's list and uses the first index's entry when two have the same plugin. An index without a list is skipped, and if none has one, alai find says so.

Installed collections

alai also reads the plugins and routing of each installed collection from its files, without running anything in it, as the version installed is the one Ansible uses. Reading the files means alai also searches collections that aren't on any index, such as a source checkout or the collection you're developing. Routes are followed as the installed version's meta/runtime.yml has them, and the index still says which plugins a newer version adds. alai keeps what it read in the cache for a collection with a MANIFEST.json, so only the first search after installing it reads its files. A collection without one, such as a checkout, is read on every search. If you edit an installed collection's files by hand, --refresh reads them again.

When you only want what's installed, --no-index searches the installed collections with ansible.builtin and eda.builtin and leaves out every index's list. Nothing is then listed as not installed, and nothing says a newer version has a plugin. --installed still uses the index's lists, so it does say when a newer version has a plugin.

ansible-core's own plugins

ansible.builtin holds ansible-core's own plugins and eda.builtin holds ansible-rulebook's event sources and filters, and neither is a collection on an index. alai reads them from each project's latest release on PyPI, or on the python_index set in its place, and ignores any lines an index has for either name. The first alai find downloads each sdist (ansible-core's is about 5 MB) and reads its plugins and routing without running anything in it. alai keeps the result in the cache, so later runs only ask the index whether there's a new release. Both count as installed, as each is part of the tool that runs its content. When offline or the index can't be reached, alai uses the last release it read. Without one, alai warns that they aren't searchable until a run online reads them.