Skip to content

Changelog

Version [Unreleased] Latest

Added

  • Run copy as an action plugin: stat the destination, then file or a staged copy, sending nothing when the file already matches.
  • Run package as an action plugin: pick the module from the host’s package manager, gathered with a filtered setup when it is not already a fact.
  • Run service as an action plugin: pick the module from the host’s init system (use:, then a fact, then a filtered setup), falling back to the service module for a name none of the known backends carries rather than failing the task.
  • Run template as an action plugin: render the file on the controller once, then hand it to the same path copy uses.
  • Run unarchive as an action plugin: extract a controller archive or one already on the host, skipping the task when creates: already exists.
  • Add the Jinja filters, tests, methods and lookups the template engine was missing: b64decode, b64encode, comment, difference, intersect, union, flatten, from_yaml, to_yaml, to_nice_yaml, to_uuid, type_debug, quote and regex_escape; the changed, failed, succeeded, skipped and version tests; a handful of Python methods on strings and mappings, through minijinja-contrib; and the first_found and template lookups.
  • Run reboot as an action plugin: shut the host down, then reconnect until it answers with a new boot id and its test command succeeds.
  • Run with_first_found and with_fileglob loops, set ansible_run_tags and ansible_skip_tags from --tags and --skip-tags, and retry a task backed by an action plugin under until, running its whole sequence of sub-tasks again on each attempt. retries: R is now up to R + 1 runs, as in the reference. (#216)
  • Run fetch as an action plugin, writing a host’s file onto the controller under dest and nowhere else, refusing a path that would land outside it. (#215)
  • Run dnf as an action plugin, picking the dnf or dnf5 module from the host’s package manager. (#213)
  • Run modules from installed collections, such as ansible.posix.sysctl or community.general.ufw, on the warm Python path. The controller’s ansible-core says what each name is, and a missing collection, a collection’s action plugin or a module that cannot be built is refused before the first connection. Volant never installs a collection. (#211)
  • Add the fileglob lookup and the ansible.utils.ipwrap filter, and answer every filter and test under its ansible.builtin.* name too. (#210)
  • An install script: curl -fsSL https://volant.sh/install.sh | sh installs the newest release, controller and agents together, after checking its checksum.
  • The release controller carries its agents. cargo binstall volant, the shell installer and the release archives give a volant that runs local and SSH tasks with nothing else installed. On first use the agents are extracted to ~/.cache/volant/agents/<version>, or under $XDG_CACHE_HOME.

Changed

  • Agents are looked up in VOLANT_AGENT_DIR first, then among the agents built into the controller, then beside the executable.
  • Release archives no longer contain the agents. They are still published on their own, in the volant-agent-* archives.
  • A copy, template or unarchive task whose src was named by a managed host is now rejected before the file is looked up, rather than sent the way the reference sends it: a host that controls a command’s output or a fact could otherwise have any file the operator can read copied to it.
  • The documentation site moves to volant.sh and is rebuilt with sections, search and diagrams. It adds pages on installation, compatibility, the command line, configuration, inventories and exit codes.

Fixed

  • A controller started through a symbolic link finds its agents next to the file the link points at. On macOS it used to look in the directory of the link.
  • A file staged for a module belongs to the connection that sent it. Two links to one host no longer consume each other’s files, and a staged file left by a cancelled batch, a lost link or a killed agent is cleaned up. (#191)
  • A vars file holding only --- and a comment loads as an empty mapping, a templated ignore_errors is rendered when the task runs, ansible_search_path is set for every task and role_path for a role’s, and Python module results get stdout_lines and stderr_lines. (#196)
  • Tasks brought in by include_tasks or include_role inherit the tags and other keywords of the play, role and blocks around the statement. Under --tags, a tagged role’s included files used to run nothing while the run exited 0. The Python union also carries the modules of every file of the roles in play, and a failed module’s exception no longer shows as a raw object. (#199)
  • A task’s own vars: built from a lookup that needs the role’s search path, such as lookup('template'), now resolve. (#200)
  • A loop whose every item was skipped prints the task’s own skipping: line after the item lines, as ansible-core does. (#202)
  • A notify naming no handler in a dynamically included file ends the run with exit code 1, as ansible-core does. fatal: lines no longer show "failed": true, module warnings are printed as [WARNING]: lines, and an interrupted pause ends the run without counting a failure. (#204)