Current Provisioner Contract
This page records the current Test Kitchen provisioner contract. It describes how Kitchen uses provisioners today, including behavior that exists because of the Ruby base classes and current transport integration.
This is not a redesign of the provisioner API. It is a snapshot of the current state so future work can make the contract clearer.
How Kitchen loads provisioners
Provisioners are Ruby plugins. Kitchen loads a provisioner by calling
Kitchen::Provisioner.for_plugin(name, config), which delegates to the shared
plugin loader.
For a provisioner named example, Kitchen expects to be able to require:
kitchen/provisioner/example
and find a class under Kitchen::Provisioner, such as:
module Kitchen
module Provisioner
class Example < Kitchen::Provisioner::Base
end
end
end
Kitchen initializes the provisioner with a config hash and then finalizes it with the instance before convergence.
Methods Kitchen calls
The current provisioner methods Kitchen depends on are:
check_license
call(state)
doctor(state)
Kitchen calls check_license immediately before call(state) during converge.
doctor(state) is called by kitchen doctor.
state is the mutable instance state hash. Kitchen reads it before converge and
writes it after the action.
Using the base workflow
A provisioner can subclass Kitchen::Provisioner::Base and use the default
remote execution workflow.
In that workflow, a provisioner can implement these command hooks:
install_command
init_command
prepare_command
run_command
The base workflow:
- creates a local sandbox
- uploads configured
uploads - opens
instance.transport.connection(state) - runs install and init commands
- uploads sandbox contents to
root_path - runs prepare
- runs the main command with
execute_with_retry - downloads configured
downloads - cleans up the local sandbox
Each command hook may return nil. Current transports are expected to no-op
for execute(nil).
Overriding the workflow
A provisioner may override call(state) entirely. In that case it owns its
transport usage, local execution, logging, state updates, and failure behavior.
The built-in dummy provisioner demonstrates this minimal pattern: it does not
use the base transport workflow and raises Kitchen::ActionFailed when
configured to fail.
What the base class provides
Provisioners normally get these shared helpers from the base class and configuration mixin:
finalize_config!(instance)- access to
instance,instance.platform,instance.suite, andinstance.transport - hash-like config lookup with
[] - path and shell helpers for Unix, Windows, Bourne shell, and PowerShell
- proxy and environment wrapping helpers
- sandbox helpers such as
create_sandbox,sandbox_path,sandbox_dirs, andcleanup_sandbox - diagnostics and plugin metadata
- logging helpers
no_parallel_for :converge
Failure behavior
Provisioners should raise Kitchen::ActionFailed for expected converge
failures.
The base workflow rescues Kitchen::Transport::TransportFailed and re-raises
Kitchen::ActionFailed. Kitchen then records the failure in state, logs details,
preserves the previous successful lifecycle state, and raises
Kitchen::InstanceFailure.
Current rough edges
The current provisioner contract includes several implicit behaviors:
- The base workflow depends heavily on the transport command and file APIs.
- Provisioner state is shared mutable instance state with no namespace.
- Windows over SSH has special behavior that inspects transport config details.
- Secret handling and redaction are not expressed as a formal provisioner contract.
- A provisioner can either use the base workflow or replace it completely, which makes the true minimum contract smaller than the base class suggests.
Future work should define a clearer provisioner contract, including serialized configuration, state ownership, events, failure reporting, transport boundaries, and secret handling.