All versions since 0.1.0-alpha.3
0.1.0-alpha.3
This entry covers the first three pre-releases.
Added
volant playbookandvolant-playbook: run playbooks made ofcommand,shellandrawtasks, with ansible-playbook’s output, recap and exit codes.- Static INI inventories with groups, children and group variables; implicit
localhost. - The agent binary, running task batches with fail-fast and cancellation.
- Variables from inventories,
group_vars/andhost_vars/directories, playvarsandvars_files, taskvars,--extra-vars,registerandset_fact, with Ansible’s precedence. - Jinja2 templating in strict mode, with the common Ansible filters, tests and lookups, checked against ansible-core.
- Task keywords
when,loop,with_items,loop_control,register,changed_when,failed_when,timeout; controller-sideset_factanddebug. --limit,ansible.cfginventory and timeout,ansible_versionandvolant_versionvariables.- The
sshconnection: the agent is uploaded once per host, cached underremote_tmpper version, and reused by every later run. Space is checked before the write and the upload is atomic. becomethroughsudo, as a play or task keyword and asansible_become*host variables, with the escalation password read from-Kor from a variable and never written anywhere. The agent an escalated task runs is cached under the target user’s own home, so escalating to an account other thanrootworks on a host whose home directories are private.forks, bounding how many hosts a play works on at once, readable asansible_forks.- One agent link per host and target user. The host’s own link is held for the whole run across plays, with a single reconnect when a link dies between plays; an escalated link lasts as long as the batch that needs it, so
forksbounds the connections open as well as the hosts working. - The full host-pattern grammar: wildcards inside names,
!exclusions,&intersections and[N],[N:M],[N-M]subscripts. - New flags
-u,--private-key,-f,-b,--become-user,--become-methodand-K, and theansible.cfgkeysremote_user,private_key_file,host_key_checking,remote_tmp,forksand the[privilege_escalation]section. - Run-level refusals exit with the code the reference gives them: 4 for a playbook that does not parse and for a
vars_filesvalue of the wrong type, 1 for a playbook file that is not there and for a pattern that leaves no hosts, 2 for a setting that is refused. - A
--limitpattern that matches no host warns and narrows the run to what it did match, instead of narrowing it silently. - A configuration file that is there and cannot be read stops the run instead of falling back to the defaults.
Changed
rawno longer reportscmd,start,endordelta, matching Ansible.- A host whose connection type is unknown is reported unreachable; the run continues.
- Hosts that fail or become unreachable are left out of the following plays.
- A host reading another host’s variables now waits for the others to reach the same task, so
hostvarsholds what the reference would show there. ansible_play_hostsand its aliases shrink as hosts fail during a play, instead of listing the play’s original hosts.- Each playbook on the command line prints its own recap.
- Mapping keys keep the order the playbook wrote them, in results and in rendered output.
Fixed
- Interrupting a run now cancels running tasks on every host and kills their whole process group;
SIGTERMis handled like Ctrl-C. - A program killed by a signal reports the negative signal number as its return code.
- Banner widths count characters, not bytes.
- A failing loop item now reports its own message and the recap counts it as failed, not as changed; an empty loop skips with
skipped_reasonand an emptyresultslist. omitis removed from lists as well as from dictionaries, at any depth.- Integers are typed as YAML 1.1 does, so
0o755,0x1fand1_000read the way the reference reads them, and theboolfilter accepts only the spellings Ansible accepts. - An inventory name that is both a host and a group resolves to the host and warns once per run, as the reference does.
- A group listed as its own descendant no longer drops hosts from
all, which had made!web,&weband an empty pattern select the wrong set.
0.1.0-alpha.4
Added
- The full host-pattern grammar: wildcards,
!exclusions and&intersections. (#78) - The
sshconnection, with the agent uploaded once and cached on each host. (#79) - Agent connections are kept across plays, and
forkslimits how many hosts run at once. (#82) becomethroughsudo, from keywords and host variables. (#83)- A task that reads
hostvarswaits for the other hosts to reach it. (#85)
Fixed
- Loop failures, empty loops, and one recap per playbook. (#86)
- Key order,
omitin nested structures, and YAML 1.1 integers. (#87) - Escalation to any user, a bound on open connections, and exit codes that match Ansible’s. (#90)
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-tagsand the listing commands, with output identical to Ansible’s. (#103)- Blocks with
rescueandalways, and theansible_failed_taskandansible_failed_resultfacts. (#104) - Handlers, run at every flush point. (#105)
until,retriesanddelay,no_log, andenvironment. (#107)serialbatches, with the run stopping when a batch loses every host. (#108)include_tasks,include_roleandinclude_vars, resolved while the play runs. (#109)run_onceanddelegate_to, with the delegated task running over the delegate’s own connection. (#115)
Changed
- Every host reads one shared
hostvarsmap instead of its own copy. (#116)
Fixed
- The host that runs a
run_oncetask 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
linearstrategy promises. The previous behavior, where each host ran ahead through tasks that did not read another host’s state, is available behind the[volant] batchingoption. (#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, taskvars,set_factand--extra-vars, not only from the inventory. (#141) createsandremovesare 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_tmpwhose~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_factsreached 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 tolocalto move its next task onto the controller, and moveansible_remote_tmpso that a planted agent ran as root on the nextbecometask. Volant now strips the names ansible-core’sclean_facts()strips, plusansible_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 ascopy,template,serviceandpackage, are still refused before the first connection. (#158, #159, #163, #166, #171) - Gather facts with ansible-core’s own
setupmodule when a play asks for them. Gathered facts are data from the host and are never rendered as templates. (#173) - Run
assert,failandpauseon the controller. (#179)
Changed
- A play that does not write
gather_facts: falsenow 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 anansible_prefix, and a fact a module returns without the prefix keeps its name. (#181)- A command skipped because of
creates:orremoves:is reported asok, as in ansible-core, rather than as skipped. (#181) - A registered result carries
changedandfailedthe way ansible-core’s does, and a result such asrc: "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
copyas an action plugin:statthe destination, thenfileor a stagedcopy, sending nothing when the file already matches. - Run
packageas an action plugin: pick the module from the host’s package manager, gathered with a filteredsetupwhen it is not already a fact. - Run
serviceas an action plugin: pick the module from the host’s init system (use:, then a fact, then a filteredsetup), falling back to theservicemodule for a name none of the known backends carries rather than failing the task. - Run
templateas an action plugin: render the file on the controller once, then hand it to the same pathcopyuses. - Run
unarchiveas an action plugin: extract a controller archive or one already on the host, skipping the task whencreates: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,quoteandregex_escape; thechanged,failed,succeeded,skippedandversiontests; a handful of Python methods on strings and mappings, throughminijinja-contrib; and thefirst_foundandtemplatelookups. - Run
rebootas 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_foundandwith_fileglobloops, setansible_run_tagsandansible_skip_tagsfrom--tagsand--skip-tags, and retry a task backed by an action plugin underuntil, running its whole sequence of sub-tasks again on each attempt.retries: Ris now up to R + 1 runs, as in the reference. (#216) - Run
fetchas an action plugin, writing a host’s file onto the controller underdestand nowhere else, refusing a path that would land outside it. (#215) - Run
dnfas an action plugin, picking thednfordnf5module from the host’s package manager. (#213) - Run modules from installed collections, such as
ansible.posix.sysctlorcommunity.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
filegloblookup and theansible.utils.ipwrapfilter, and answer every filter and test under itsansible.builtin.*name too. (#210) - An install script:
curl -fsSL https://volant.sh/install.sh | shinstalls 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 avolantthat 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_DIRfirst, 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,templateorunarchivetask whosesrcwas 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 templatedignore_errorsis rendered when the task runs,ansible_search_pathis set for every task androle_pathfor a role’s, and Python module results getstdout_linesandstderr_lines. (#196) - Tasks brought in by
include_tasksorinclude_roleinherit 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’sexceptionno longer shows as a raw object. (#199) - A task’s own
vars:built from a lookup that needs the role’s search path, such aslookup('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
notifynaming 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 interruptedpauseends the run without counting a failure. (#204)