Skip to content

All versions since 0.1.0-alpha.5

0.1.0-alpha.5

Added

  • The whole ansible-core 2.19 grammar loads, and whatever this release cannot run is refused by name before the first connection. (#98)
  • Plays compile into a flat list of steps, with blocks kept as spans. (#101)
  • Roles load from the standard paths, with dependencies, argument specs and import_tasks. (#102)
  • --tags, --skip-tags and the listing commands, with output identical to Ansible’s. (#103)
  • Blocks with rescue and always, and the ansible_failed_task and ansible_failed_result facts. (#104)
  • Handlers, run at every flush point. (#105)
  • until, retries and delay, no_log, and environment. (#107)
  • serial batches, with the run stopping when a batch loses every host. (#108)
  • include_tasks, include_role and include_vars, resolved while the play runs. (#109)
  • run_once and delegate_to, with the delegated task running over the delegate’s own connection. (#115)

Changed

  • Every host reads one shared hostvars map instead of its own copy. (#116)

Fixed

  • The host that runs a run_once task is elected once for the whole batch. (#118)

0.1.0-alpha.6

Added

  • Every release archive now carries the two Linux agents, so one download is enough for a first run over SSH. (#147)
  • The documentation lists which arguments each native module reads, and the pre-flight refuses the ones it does not. (#145)

Changed

  • Hosts now meet in front of every task, as Ansible’s linear strategy promises. The previous behavior, where each host ran ahead through tasks that did not read another host’s state, is available behind the [volant] batching option. (#140)
  • Resolving variables on large inventories is faster: inventory-wide values are built once per batch and shared across hosts. (#134)

Fixed

  • A module result is data and is never rendered again as a template on the controller. (#139)
  • The connection settings come from a host’s effective variables, including group_vars, task vars, set_fact and --extra-vars, not only from the inventory. (#141)
  • creates and removes are resolved in the directory the command runs in. (#142)
  • A task’s timeout covers the child process, its output streams and the write to its standard input. (#144), (#154)
  • A remote_tmp whose ~ part is not a user name is refused. (#146)
  • Every event a task produces is shown, through the same output renderer as the rest of the run. (#143)
  • What a dynamic include brings in is checked by the pre-flight before it runs. (#155)

0.1.0-alpha.7

Security

  • A managed host can no longer set its own connection settings through the facts a module returns. Until this release, a fact module’s ansible_facts reached the host’s variables unfiltered, so a host could change the address, user and ssh arguments of its own next task, including -oProxyCommand, which runs a command on the controller. It could also pick the Python interpreter its next module ran under, switch the connection to local to move its next task onto the controller, and move ansible_remote_tmp so that a planted agent ran as root on the next become task. Volant now strips the names ansible-core’s clean_facts() strips, plus ansible_remote_tmp, which ansible-core does not use this way, and prints the same warning. (#181)

Added

  • Run ansible-core’s Python modules on managed hosts. The controller builds one content-addressed zip of the modules a run needs and their module_utils, and sends it to each host once. A Python server in the agent loads it once and forks a child per task, so running a module costs tens of milliseconds rather than starting a new interpreter for each one. The host needs a Python interpreter but not ansible-core; the controller needs ansible-core. Modules the reference runs through an action plugin, such as copy, template, service and package, are still refused before the first connection. (#158, #159, #163, #166, #171)
  • Gather facts with ansible-core’s own setup module when a play asks for them. Gathered facts are data from the host and are never rendered as templates. (#173)
  • Run assert, fail and pause on the controller. (#179)

Changed

  • A play that does not write gather_facts: false now gathers facts, so it needs ansible-core on the controller and is refused before the first connection without it. (#173)
  • Gathered facts now rank above the inventory and below the play’s, role’s and task’s own variables, as in ansible-core. A playbook that sets a variable with the same name as a gathered fact now reads its own value. (#181)
  • An escalated connection is kept for the whole play instead of being rebuilt at every task. On a thirty-five task playbook against one host this took the run from 872 ms to 226 ms per task. A run can now hold two connections per host, so a controller limited to 1024 open files reaches that limit at about 169 escalating hosts. (#178)

Fixed

  • ansible_facts[...] uses the same keys as ansible-core, without an ansible_ prefix, and a fact a module returns without the prefix keeps its name. (#181)
  • A command skipped because of creates: or removes: is reported as ok, as in ansible-core, rather than as skipped. (#181)
  • A registered result carries changed and failed the way ansible-core’s does, and a result such as rc: "2" counts as a failure as it does there. (#181)
  • The timeout: keyword applies to tasks that run on the controller. (#179)

[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)