Ansible: How `check_mode` is set
by yaobin.wen
1. Overview
In general, there are (at least) two ways of setting check_mode
:
- 1). Use the option
--check
on the command line (e.g.,ansible-playbook --check
oransible --check
). - 2). Set
_ansible_check_mode
inANSIBLE_MODULE_ARGS
.
But the first method will (probably) eventually use the second method, because an Ansible module is not executed directly, but firstly packed into an AnsibleZ
tarball (the input arguments included) and then sent to the target machine to run.
2. Loading the CLI option variables into VariableManager
lib/ansible/vars/manager.py
has the class VariableManager
. VariableManager.__init__
calls load_options_vars()
to load option variables in self._options_vars = load_options_vars(version_info)
:
def load_options_vars(version):
if version is None:
version = 'Unknown'
options_vars = {'ansible_version': version}
attrs = {'check': 'check_mode',
'diff': 'diff_mode',
'forks': 'forks',
'inventory': 'inventory_sources',
'skip_tags': 'skip_tags',
'subset': 'limit',
'tags': 'run_tags',
'verbosity': 'verbosity'}
for attr, alias in attrs.items():
opt = context.CLIARGS.get(attr)
if opt is not None:
options_vars['ansible_%s' % alias] = opt
return options_vars
3. ansible_check_mode
is returned by VariableManager.get_vars()
ansible_check_mode
is returned by VariableManager._get_magic_variables()
in the “Set options vars” part (near the end of the function):
# Set options vars
for option, option_value in iteritems(self._options_vars):
variables[option] = option_value
In VariableManager.get_vars()
, the magic variables are combined into all_vars
: all_vars = combine_vars(all_vars, magic_variables)
.
4. All the variables in VariableManager
are loaded into task_vars
in the strategy
TaskQueueManager
is eventually used to run the Ansible tasks.
TaskQueueManager.run()
loads the strategy and call the strategy’s run()
method: play_return = strategy.run(iterator, play_context)
. By default, the strategy is the linear
strategy.
In linear.StrategyModule.run()
, the variables are loaded into task_vars
:
task_vars = self._variable_manager.get_vars(
play=iterator._play, host=host, task=task,
_hosts=self._hosts_cache, _hosts_all=self._hosts_cache_all
)
5. task_vars
are passed into the worker process
Then task_vars
are passed into self._queue_task(host, task, task_vars, play_context)
.
In StrategyBase._queue_task()
, task_vars
are passed into WorkerProcess
: ` worker_prc = WorkerProcess(self._final_q, task_vars, host, task, play_context, self._loader, self._variable_manager, plugin_loader)`.
In WorkerProcess.__init__()
, task_vars
are recorded in self._task_vars
. In WorkerProcess._run()
, self._task_vars
are passed into TaskExecutor
as its job_vars
:
executor_result = TaskExecutor(
self._host,
self._task,
self._task_vars,
self._play_context,
self._new_stdin,
self._loader,
self._shared_loader_obj,
self._final_q
).run()
6. TaskExecutor
passes the variables to the (normal) action plugin
TaskExecutor.run()
calls TaskExecutor._execute()
without setting variables
(L147): res = self._execute()
.
So TaskExecutor._execute()
uses self._job_vars
as variables
:
if variables is None:
variables = self._job_vars
TaskExecutor._execute()
then runs self._play_context = self._play_context.set_task_and_variable_override(task=self._task, variables=variables, templar=templar)
to override variables if needed.
Finally, TaskExecutor._execute()
runs the handler (which is an action plugin) to run the module: result = self._handler.run(task_vars=variables)
. self._handler = self._get_action_handler(connection=self._connection, templar=templar)
which in the default case the normal
handler that’s defined in lib/ansible/plugins/action/normal.py
.
7. normal
action plugin updates the module arguments
The normal
action plugin calls ActionBase._execute_module()
to run the module. ActionBase._execute_module()
runs self._update_module_args(module_name, module_args, task_vars)
to update the module’s arguments.
ActionBase._update_module_args()
:
# set check mode in the module arguments, if required
if self._play_context.check_mode:
if not self._supports_check_mode:
raise AnsibleError("check mode is not supported for this operation")
module_args['_ansible_check_mode'] = True
else:
module_args['_ansible_check_mode'] = False
OK, so now module_args
has _ansible_check_mode
set.
8. normal
action plugin executes the module
But note that the Ansible module is packed up into an AnsibleZ
tarball together with the arguments and sent to the target machine to run. On the target machine, the module’s run_module()
function is called.
Typically, inside run_module()
, AnsibleModule
is instantiated. AnsibleModule.__init__()
calls AnsibleModule._check_arguments()
which does the following:
for k in PASS_VARS:
# handle setting internal properties from internal ansible vars
param_key = '_ansible_%s' % k
if param_key in param:
if k in PASS_BOOLS:
setattr(self, PASS_VARS[k][0], self.boolean(param[param_key]))
else:
setattr(self, PASS_VARS[k][0], param[param_key])
# clean up internal top level params:
if param_key in self.params:
del self.params[param_key]
else:
# use defaults if not already set
if not hasattr(self, PASS_VARS[k][0]):
setattr(self, PASS_VARS[k][0], PASS_VARS[k][1])
where PASS_VARS
contains check_mode
:
PASS_VARS = {
'check_mode': ('check_mode', False),
# ...
}
For AnsibleModule._check_arguments(self, check_invalid_arguments, spec=None, param=None, legal_inputs=None)
, when param
is None
:
if param is None:
param = self.params
self.params
is set in def _load_params(self)
:
def _load_params(self):
''' read the input and set the params attribute.
This method is for backwards compatibility. The guts of the function
were moved out in 2.1 so that custom modules could read the parameters.
'''
# debug overrides to read args from file or cmdline
self.params = _load_params()
_load_params()
eventually only returns params['ANSIBLE_MODULE_ARGS']
. So if I want to override any ansible_*
variable, I can include it in params['ANSIBLE_MODULE_ARGS']
as _ansible_*
(note there must be the leading underscore _
). For example:
{
"ANSIBLE_MODULE_ARGS": {
"_ansible_check_mode": True,
}
}
See demo/ansible/roles/unittest-module/library/test_my_test.py
for an example.