Ansible: Conditional "when" clause with empty variable - ansible

I am tasked with creating an Ansible role, where I set a variable within the defaults/main.yml file in the same role.
role_variable: ""
This variable is then recalled within another task, which uses it as conditional, in order to perform operations:
ROLE
-name: "Task which is called"
# Stuff which will be done given that the conditional below is correct
set_fact:
role_variable: "{{ variable_1 if 'string' not in issuer else variable_2 }} "
when: (role_variable | length == 0)
Within the playbook, this variable is not set anywhere else -it is only used for debug- and if I print the above condition within a debug clause, it correctly returns 0.
PLAYBOOK
-name: "Import secrets"
vars_files:
- secrets.yml
hosts: "{{ target }}"
gather_facts: false
tasks:
- include_vars:
file: "{{ credentials }}"
- ansible.builtin.setup:
gather_subset: min ### This is used in other parts of the complete playbook
delegate_to: localhost
- name: "Task to call"
import_role:
name: role_name
tasks_from: role_task_name
When this variable is not set a value within the playbook, it evaluates correctly, e.g. :
ansible-playbook playbook.yaml -e ""
But whenever this is evaluated even with an empty string (e.g.):
ansible-playbook playbook.yaml -e "role_variable="
This is necessary for example within Jenkins builds, where this variable can either be passed or not by the user. If not, it should use a default, scripted within the role or play.
I have tried additional methods of doing the same thing:
when: role_variable is none
when: role_variable is null
when: role_variable is none
when: role_variable is match("")
So far, I had no luck.
The objective is to pass the role_variable as default with an empty string "", and re-set it only if such variable is empty.
Does anybody have any past experiences with this, on how it is possible to make this work?
Thanks in advance for any help

you have to do this test:
- name: "Task which is called"
debug: msg="ok"
when: role_variable is not defined or role_variable == ''
you'll see the task executed if role_variable is not defined or role_variable is empty
for your problem
- hosts: localhost
gather_facts: no
tasks:
- name: loop over
pause:
prompt: "role_variable is empty, give the content "
when: role_variable is not defined or role_variable == ''
register: out
- name: "Task which is called"
set_fact:
role_variable: "{{ out.user_input }}"
when: out.user_input is defined
- name: display
debug:
msg: "{{ role_variable }}"
but it seems the result is empty...when launching ansible-playbook playbook.yaml -e "role_variable=" i dunno if its bug

I verified - also thanks to the guys who contributed - that what I am trying to achieve is in fact not possible.
According to Ansible documentation, setting the variable with the --extra-vars or -e flag takes precedence in setting the variable.
Even the single act of defining it, and leaving it as empty, just triggers the condition, and it is not possible to override it.
In my case, since I have to pass it to Jenkins via a .groovy file, I have set up a condition where I instruct when variable is empty, it doesn't pass the argument.

Related

Ansible: variable is defined but error shows not defined

I have an ansible playbook as follows:
---
- name: test variables
hosts: all
tasks:
- name: test
command: echo {{ ansible_role_dir }}
register: result
- name: show result
debug:
msg="{{ result.stdout }}"
#roles: #line12
# - "{{ ansible_role_dir }}/testrole" #line13
the variable ansible_role_dir is defined under group_vars/all.yaml.
if I run the playbook which comments out the line12 and line13, it shows the result of variable correctly. Obviously it knows where the variable ansible_role_dir is defined. But if I uncomment line12 and line13, it shows error ERROR! 'ansible_role_dir' is undefined. Why it does not know where the ansible_role_dir is defined this time?
Ansible does not like jinja tags without quotes. try this:
---
- name: test variables
hosts: all
tasks:
- name: test
command: "echo {{ ansible_role_dir }}"
register: result
- name: show result
debug:
msg="{{ result.stdout }}"
#roles: #line12
# - "{{ ansible_role_dir }}/testrole" #line13
EDIT:
sorry, i think i misread the question.
have you tried putting in the real path instead of the var, just to see if it works? usually role paths are given in ansible's config, i never saw them run with a direct path

Ansible Task: with_dict statement got triggered, even if the when-clause should prevent this

I'am trying to automate the creation of the smart_[diskdevice] links to
/usr/share/munin/plugins/smart_
during the installation of the munin node via ansible.
The code here works partially, except there is no diskdevice to link on the target machine. Then I got a fatal failure with
{"msg": "with_dict expects a dict"}
I've review the ansible documentation and tried to search the problem in the web. For my understanding, the whole "file" directive should not be executed if the "when"-statement fails.
---
- name: Install Munin Node
any_errors_fatal: true
block:
...
# drives config
- file:
src: /usr/share/munin/plugins/smart_
dest: /etc/munin/plugins/smart_{{ item.key }}
state: link
with_dict: "{{ ansible_devices }}"
when: "item.value.host.startswith('SATA')"
notify:
- restart munin-node
On targets with a SATA-Drive, the code works. Drives like "sda" are found and the links are created. Loop- and other soft-Devices are ignored (as intended)
Only on a Raspberry with no SATA-Drive at all i got the fatal failure.
You are using the with_dict option to set the loop. This sets the value of the item variable for each iteration as a dictionary with two keys:
key: The name of the current key in the dict.
value: The value of the existing key in the dict.
You are then running the when option that checks the item variable on each iteration. So check if that is the behavior you want.
Regarding your error, it is being thrown because for some reason, ansible_devices is not a dict as the error says. And Ansible checks for the validity of the with_dict type before resolving the when condition.
Check the following example:
---
- name: Diff test
hosts: local
connection: local
gather_facts: no
vars:
dict:
value: True
name: "dict"
tasks:
- debug: var=item
when: dict.value == False
with_dict: '{{ dict }}'
- debug: var=item
when: dict.value == True
with_dict: '{{ dict }}'
- debug: var=item
when: dict.value == False
with_dict: "Not a dict"
The first two task will succeed because they have a valid dict on the with_dict option and a correct condition on the when option. The last one will fail because the with_dict value has the wrong type, even though the when condition resolves correctly and should guarantee to skip the task.
I hope it helps.

ansible when statement multi-variable substitution

I am running a playbook against a dynamic inventory so filtering out different hosts when running the playbook is not something I have done.
That being said, I have skipped/executed certain tasks based on the 'ansible_hostname' fact. As an example:
- name: say 'yes' to every server except server0[12]
shell: |
echo 'yes'
when: '"server01" not in ansible_hostname and
"server02" not in ansible_hostname'
- name: say 'no' for only server0[12]
shell: |
echo 'no'
when: '"server01" in ansible_hostname or
"server02" in ansible_hostname'
This has worked for me, but its not very sustainable. I am looking for a way to do this dynamically (variable substitution?). So for instance I can keep a variable that is a list of [server01,server02] and I can do a for loop of {{ var }} {not in ansible_hostname}.
I am not sure if this is possible or how to accomplish this. Any thoughts?
Let say you have list of hostname against you want to check:
---
- hosts: all
vars:
hostname_list: ["server01", "server01", "server01"]
tasks:
- debug:
msg: "test pass"
when: ansible_hostname not in hostname_list

How to get value of --limit argument inside an Ansible playbook?

In ansible, is it possible to get the value of the argument to the "--limit" option within a playbook? I want to do is something like this:
---
- hosts: all
remote user: root
tasks:
- name: The value of the --limit argument
debug:
msg: "argument of --limit is {{ ansible-limit-arg }}"
Then when I run he command:
$ ansible-playbook getLimitArg.yaml --limit webhosts
I'll get this output:
argument of --limit is webhost
Of course, I made up the name of the variable "ansible-limit-arg", but is there a valid way of doing this? I could specify "webhosts" twice, the second time with --extra-args, but that seems a roundabout way of having to do this.
Since Ansible 2.5 you can access the value using a new magic variable ansible_limit, so:
- debug:
var: ansible_limit
Have you considered using the {{ ansible_play_batch }} built-in variable?
- hosts: all
become: "False"
gather_facts: "False"
tasks:
- name: The value of the --limit argument
debug:
msg: "argument of --limit is {{ ansible_play_batch }}"
delegate_to: localhost
It won't tell you exactly what was entered as the argument but it will tell you how Ansible interpreted the --limit arg.
You can't do this without additional plugin/module. If you utterly need this, write action plugin and access options via cli.options (see example).
P.S. If you try to use --limit to run playbooks agains different environments, don't do it, you can accidentally blow your whole infrastructure – use different inventories instead.
Here is the small code block to achieve the same
- block:
- shell: 'echo {{inventory_hostname}} >> /tmp/hosts'
- shell: cat /tmp/hosts
register: servers
- file:
path: /tmp/hosts
state: absent
delegate_to: 127.0.0.1
- debug: var=servers.stdout_lines
Then use stdout_lines output as u wish like mine
- add_host:
name: "{{ item }}"
groups: cluster
ansible_user: "ubuntu"
with_items:
- "{{ servers.stdout_lines }}"

ansible: using with_items with notify handler

I want to pass a variable to a notification handler, but can't find anywhere be it here on SO, the docs or the issues in the github repo, how to do it. What I'm doing is deploying multiple webapps, and when the code for one of those webapps is changed, it should restart the service for that webapp.
From this SO question, I got this to work, somewhat:
- hosts: localhost
tasks:
- name: "task 1"
shell: "echo {{ item }}"
register: "task_1_output"
with_items: [a,b]
- name: "task 2"
debug:
msg: "{{ item.item }}"
when: item.changed
with_items: task_1_output.results
(Put it in test.yml and run it with ansible-playbook test.yml -c local.)
But this registers the result of the first task and conditionally loops over that in the second task. My problem is that it gets messy when you have two or more tasks that need to notify the second task! For example, restart the web service if either the code was updated or the configuration was changed.
AFAICT, there's no way to pass a variable to a handler. That would cleanly fix it for me. I found some issues on github where other people run into the same problem, and some syntaxes are proposed, but none of them actually work.
Including a sub-playbook won't work either, because using with_items together with include was deprecated.
In my playbooks, I have a site.yml that lists the roles of a group, then in the group_vars for that group I define the list of webapps (including the versions) that should be installed. This seems correct to me, because this way I can use the same playbook for staging and production. But maybe the only solution is to define the role multiple times, and duplicate the list of roles for staging and production.
So what is the wisdom here?
Variables in Ansible are global so there is no reason to pass a variable to handler. If you are trying to make a handler parameterized in a way that you are trying to use a variable in the name of a handler you won't be able to do that in Ansible.
What you can do is create a handler that loops over a list of services easily enough, here is a working example that can be tested locally:
- hosts: localhost
tasks:
- file: >
path=/tmp/{{ item }}
state=directory
register: files_created
with_items:
- one
- two
notify: some_handler
handlers:
- name: "some_handler"
shell: "echo {{ item }} has changed!"
when: item.changed
with_items: files_created.results
I finally solved it by splitting the apps out over multiple instances of the same role. This way, the handler in the role can refer to variables that are defined as role variable.
In site.yml:
- hosts: localhost
roles:
- role: something
name: a
- role: something
name: b
In roles/something/tasks/main.yml:
- name: do something
shell: "echo {{ name }}"
notify: something happened
- name: do something else
shell: "echo {{ name }}"
notify: something happened
In roles/something/handlers/main.yml:
- name: something happened
debug:
msg: "{{ name }}"
Seems a lot less hackish than the first solution!
To update jarv's answer above, Ansible 2.5 replaces with_items with loop. When getting results, item by itself will not work. You will need to explicitly get the name, e.g., item.name.
- hosts: localhost
tasks:
- file: >
path=/tmp/{{ item }}
state=directory
register: files_created
loop:
- one
- two
notify: some_handler
handlers:
- name: "some_handler"
shell: "echo {{ item.name }} has changed!"
when: item.changed
loop: files_created.results
I got mine to work like this - I had to add some curly brackets
tasks:
- name: Aktivieren von Security-, Backport- und Non-Security-Upgrades
lineinfile:
path: /etc/apt/apt.conf.d/50unattended-upgrades
regexp: '^[^"//"]*"\${distro_id}:\${distro_codename}-{{ item }}";'
line: ' "${distro_id}:${distro_codename}-{{ item }}";'
insertafter: "Unattended-Upgrade::Allowed-Origins {"
state: present
register: aenderung
loop:
- updates
- security
- backports
notify: Auskommentierte Zeilen entfernen
handlers:
- name: Auskommentierte Zeilen entfernen
lineinfile:
path: /etc/apt/apt.conf.d/50unattended-upgrades
regexp: '^\/\/.*{{ item.item }}";.*'
state: absent
when: item.changed
loop: "{{ aenderung.results }}"

Resources