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.
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:
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:
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:
- Exact names before partial matches.
- Plugins that aren't redirects, then redirects, then ones that are removed or lead nowhere.
ansible.builtinandeda.builtin.- Collections present in alai's cache, for example one installed in a separate project.
- Collections in the
ansiblepackage, which bundles collections the Ansible community has vetted. - Other collections in the same namespaces as the
ansiblepackage's collections. - Everything else, with collections that look like copies of a
collection in the
ansiblepackage 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. - 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:
✗means you can't use it.⚠means you shouldn't use it.↑or a yellow↪means you can use it once something is upgraded or installed.- A cyan
↪means you can use it under another name. ✓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.builtinandeda.builtinit 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.