Previous Post | Top | Next Post |
TOC
Here is a series of memos of me trying to use ansible on Debian 12 (bookworm
).
Recap of my trials of playbooks
Up to here, I tried simple playbooks in which a playbook runs in order from top to bottom.
Let me read over again “Playbook execution”:
- Playbook execution
- A playbook runs in order from top to bottom. Within each play, tasks also run in order from top to bottom.
- Task execution: By default, Ansible executes each task in order, one at a time, against all machines matched by the host pattern. Each task executes a module with specific arguments. When a task has executed on all target machines, Ansible moves on to the next task
So far, it was simple.
Using Variables in Ansible
Then I read “Using Variables”. This feature of “Ansible uses variables to manage differences between systems.” is a very attractive one. But when I tried variables, I got confused a bit until I found “Ansible loads every possible variable it finds, and then chooses the variable to apply based on variable precedence”.
Variable precedence
Here is the order of precedence from least to greatest for notable ones:
- role defaults (defined in role/defaults/main.yml)
- …
- host facts / cached set_facts
- play vars (defined in
vars:
immediately under each play) - …
- role vars (defined in role/vars/main.yml)
- task vars (only for the task, defined in
vars:
immediately under each task) - set_facts / registered vars
- extra vars (for example, -e “user=my_user”)(always win precedence)
I noticed these variable settings are somewhat like Makefile. Unlike normal
shell scripts, vars: ...
definition after task: ...
are effective and
values may be updated as tasks are processed.
Strategy to use variables
I can understand the rationale behind making “extra vars” to have a powerful precedence position. It’s handy for debugging.
But, I can’t use such “extra vars” variable names casually within the play and try to rewrite their effective values with other variables. So the approach of using corresponding but different internal variable names is the only reasonable alternative. Then I can handle default values for the undefined cases and variable type issues gracefully.
I put such variable name conversion code from externally exposed to change Ansible behavior to corresponding internal one in the low priority “role default”. It also takes care variable type issues.
Testing variables in action
Let me setup variable test code using following modules:
- ansible.builtin.debug – Print statements during execution
- ansible.builtin.set_fact – Set host variable(s) and fact(s)
roles/base/defaults/main.yml
:
---
# prefix internal variables with underscore
# -- let -e to set external variables without underscore
# ensure internal variables to be defined (and boolean for override)
#
_var1: "{{ var1 | default('1') }}"
_var2: "{{ var2 | default('2') }}"
#_varTF: True
_varTF: "{{ _var1 == _var2 }}"
_var1_override: "{{ var1_override | default(false) | bool }}"
_var2_override: "{{ var2_override | default(false) | bool }}"
roles/base/vars/main.yml
:
---
# vars file
_var3: ""
Then tested these against roles/base/tasks/main.yml
and played with it from
the command line. Many repeated long lines are for the variable value tracing.
roles/base/tasks/main.yml
:
---
- name: Debug external variables with msg
ansible.builtin.debug:
msg: "var1_override = '{{ var1_override | default('***UNDEFIINED***') }}' var2_override = '{{ var2_override | default('***UNDEFIINED***') }}'"
- name: Debug internal variables with msg
ansible.builtin.debug:
msg: "_var1 = '{{ _var1 }}' _var2 = '{{ _var2 }}' _var3 = '{{ _var3 }}' _varTF = '{{ _varTF }}' _var1_override = '{{ _var1_override }}' _var2_override = '{{ _var2_override }}'"
- name: set var1 to "111" (if _var1_override is True)
ansible.builtin.set_fact:
_var1: "111"
when: _var1_override
- name: Debug internal variables with msg
ansible.builtin.debug:
msg: "_var1 = '{{ _var1 }}' _var2 = '{{ _var2 }}' _var3 = '{{ _var3 }}' _varTF = '{{ _varTF }}' _var1_override = '{{ _var1_override }}' _var2_override = '{{ _var2_override }}'"
- name: set var2 to "222" (if _var2_override is True)
ansible.builtin.set_fact:
_var2: "222"
when: _var2_override
- name: Debug internal variables with msg
ansible.builtin.debug:
msg: "_var1 = '{{ _var1 }}' _var2 = '{{ _var2 }}' _var3 = '{{ _var3 }}' _varTF = '{{ _varTF }}' _var1_override = '{{ _var1_override }}' _var2_override = '{{ _var2_override }}'"
- name: set _var3 (_var1+_var2)
ansible.builtin.set_fact:
_var3: "{{ _var1 + _var2 }}"
- name: Debug internal variables with msg
ansible.builtin.debug:
msg: "_var1 = '{{ _var1 }}' _var2 = '{{ _var2 }}' _var3 = '{{ _var3 }}' _varTF = '{{ _varTF }}' _var1_override = '{{ _var1_override }}' _var2_override = '{{ _var2_override }}'"
- name: set _var3 to "zzzzz" with var1 == var2
ansible.builtin.set_fact:
_var3: "zzzzz"
when: _varTF
- name: Debug internal variables with msg
ansible.builtin.debug:
msg: "_var1 = '{{ _var1 }}' _var2 = '{{ _var2 }}' _var3 = '{{ _var3 }}' _varTF = '{{ _varTF }}' _var1_override = '{{ _var1_override }}' _var2_override = '{{ _var2_override }}'"
- name: Debug result of _var1 == _var2
ansible.builtin.debug:
var: _var1 == _var2
Using these, I tested as:
$ ansible-playbook pb_main.yml -e "var1=0 var2=0 var1_override=1 var2_override=0"
...
YAML or JSON is too much to type in the command line. The -e
option with
easier to type key=value
produces value
in string. The application of
|bool
in Ansible code as above ensures the value to be boolean .
Now I understand why I see many when: some_vaiable |bool
in Ansible code
examples. See Ansible issue #17193.
YAML block and linebreak
I knew “|
” and “>
” are for keeping the linebreak or converting the
linebreak to the space from
Ansible’s YAML syntax page.
I see many playbooks use “|+
” instead and wasn’t sure what it means until I found
YAML spec on block
style defining
“+
” to retain linebreaks for blank lines following the string block and
“-
” to strip the linebreak at the end of string block.
For any “|
”, “|+
”, “|-
” cases, linebreaks inside of the string block are retained.
YAML and shell “Here Documents”
With Ansible (2.14.3) with jinja (3.1.2), many previously raised issues (#12856, #32800) seem to be resolved now.
So no more extra space added to require <<' END'
etc.
I have
tested this
with a single “cat``" command:
ansible.builtin.shell: |
(a.k.a.: free_form)ansible.builtin.shell:
withcmd:|
in the following indented lineansible.builtin.script: |
(a.k.a.: free_form)ansible.builtin.script: |
withcmd:|
in the following indented lineansible.builtin.raw: |
(a.k.a.: free_form)
Any time “free_form” is used, single leading spaces are somehow dropped.
ansible.builtin.shell:
and ansible.builtin.raw:
don’t require command name
to be specified as the full path from /
on the target system.
ansible.builtin.script:
requires command name to be specified as the full
path from /
on the target system.
In short, ansible.builtin.shell:
with cmd:|
in the following indented line
to start indented shell code block is the best option for me.
If creating a file is the objective, following builtin should also be considered.
- ansible.builtin.copy
- ansible.builtin.template
- ansible.builtin.assemble
- ansible.builtin.blockinfile
- ansible.builtin.file
Debug hints
I found interesting post on redit.
- Setting
export ANSIBLE_STDOUT_CALLBACK=debug
turns that block of a “single string with \n"s errors into something more readable. - Setting
DEFAULT_STDOUT_CALLBACK=debug
inansible.cfg
does the same. - Tacking on
| default("NOTHING")
after yaml extraction of data.
Notable links to documentation
TBH, “Ansible Documentation” is too big to read though whenever I wonder. Here are links to the portion I keep coming back.
- “Ansible concepts” – Control node, Managed nodes, Inventory, Playbooks, Plays, Roles, Tasks, Handlers, Modules, Plugins, Collections
- “Ansible.Builtin” – Reference for all modules and plugins contained in
ansible-core
- “Using Ansible playbooks”
- “Ansible playbooks” – Ansible Playbooks offer a configuration management and multi-machine deployment system.
- “Working with playbooks”
- “Conditionals” – Conditional execution of task depending on the value of variable and fact.
- “Blocks” – Blocks create logical groups of tasks
- “Handler” – Running operations on change
- “Roles” – Roles let you automatically load related vars, files, tasks, handlers, etc.
- “Using Variables” – Manage differences between systems by creating variables with standard YAML syntax
- “Executing playbooks
- “Tags” – Enable to run only specific parts of playbook
- REFERENCE & APPENDICES
Previous Post | Top | Next Post |