ansible when statement multi-variable substitution - ansible

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

Related

Ansible: Conditional "when" clause with empty variable

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.

Is there a way to get a list of unreachable hosts in an ansible playbook

I have the ansible role coded below.
---
- name: Get host facts
set_fact:
serverdomain: "{{ansible_domain}}"
server_ip: "{{ansible_ip_addresses[1]}}"
- name: Host Ping Check
failed_when: false
win_ping:
register: var_ping
- name: Get Host name
debug: msg="{{the_host_name}}"
- name: Set Execution File and parameters
set_fact:
scriptfile: "{{ansible_user_dir}}\\scripts\\host_check.ps1"
params: "-servername '{{the_host_name}}' -response var_ping.failed"
- name: Execute script
win_command: powershell.exe "{{scriptfile}}" "{{params}}"
It works the way it should do but of course unreachable hosts are not touched at all. I would like to generate a list or is there a variable which contains all the unreachable hosts. Is it possible to have this in a comma delimmeted list and stored in a variable ?
Lastly, how/where do i need to set gather_facts: no. I tried several places to no avail.
EDIT 1
- name Unreachable servers
set_fact:
down: "{{ ansible_play_hosts_all | difference(ansible_play_hosts)}}"
- name: Set Execution File and parameters
set_fact:
scriptfile: "{{ansible_user_dir}}\\scripts\\host_check.ps1"
params: "-servername '{{the_host_name}}' -response var_ping.failed -unreachable_hosts {{ down }}"
- name: Execute script
win_command: powershell.exe "{{scriptfile}}" "{{params}}"
when: inventory_hostname == {{ db_server_host }}
Thanks for the answers, I have now been able to use thesame login in my ansible role, and it appears to work.
I do however have some questions. My playbook runs against hosts defined in my inventory, in this case what I want to achieve is a situation where the unreachable hosts is passed onto a powershell script right at the end and at once. So for example, 100 hosts, 10 were unreachable. The playbook should gather the 10 unreachable hosts, and pass the list of hosts in an array format to a powershell script or as a json data type.
All I want to do is be able to process the list of unreachable servers from my powershell script.
At the moment, I get
[
"server1",
"server2"
]
MY script will work with a format like this "server1","server2"
Lastly.The process of logging the unreachable servers at the end, only needs to happen once to a specific server which does the logging to a database. I created a task to do this as seen above.
db_server_host is being passed as a extra variables from ansible tower. The reason i added the when is that I only want it to run on the DB server and not on every host. I get the error The conditional check inventory_hostname == {{ db_server_host }} failed. The error was error while evaluating conditional inventory_hostname == {{ db_server_host }} 'mydatabaseServerName' is undefined
Q: "Get a list of unreachable hosts in an Ansible playbook."
Short answer: Create the difference between the lists below
down: "{{ ansible_play_hosts_all|difference(ansible_play_hosts) }}"
Details: Use Special Variables. Quoting:
ansible_play_hosts_all:
List of all the hosts that were targeted by the play.
ansible_play_hosts:
List of hosts in the current play run, not limited by the serial. Failed/Unreachable hosts are excluded from this list.
For example, given the inventory
shell> cat hosts
alpha
beta
charlie
delta
The playbook
- hosts: alpha,beta,charlie
gather_facts: true
tasks:
- block:
- debug:
var: ansible_play_hosts_all
- debug:
var: ansible_play_hosts
- set_fact:
down: "{{ ansible_play_hosts_all|difference(ansible_play_hosts) }}"
- debug:
var: down
run_once: true
gives (bridged) if alpha is unreachable (see below)
ansible_play_hosts_all:
- alpha
- beta
- charlie
ansible_play_hosts:
- beta
- charlie
down:
- alpha
Host alpha was unreachable
PLAY [alpha,beta,charlie] *************************************************
TASK [Gathering Facts] ****************************************************
fatal: [alpha]: UNREACHABLE! => changed=false
msg: 'Failed to connect to the host via ssh: ssh: connect to host test_14 port 22: No route to host'
unreachable: true
ok: [charlie]
ok: [beta]
...
Note
The dictionary hostvars keeps all hosts from the inventory, e.g.
- hosts: alpha,beta,charlie
gather_facts: true
tasks:
- debug:
var: hostvars.keys()
run_once: true
gives (abridged)
hostvars.keys():
- alpha
- beta
- charlie
- delta
tasks:
- ping:
register: ping_out
# one can include any kind of timeout or other stall related config here
- debug:
msg: |
{% for k in hostvars.keys() %}
{{ k }}: {{ hostvars[k].ping_out.unreachable|default(False) }}
{% endfor %}
yields (when an inventory consisting of only an alive alpha host):
msg: |-
alpha: False
beta: True
charlie: True
Lastly, how/where do i need to set gather_facts: no. I tried several places to no avail.
It only appears one time in the playbook keywords and thus it is a play keyword:
- hosts: all
gather_facts: no
tasks:
- name: now run "setup" for those who you wish to gather_facts: yes
setup:
when: inventory_host is awesome

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: variable interpolation in task name

I cannot get this seemingly simple example to work in Ansible 1.8.3. The variable interpolation does not kick in the task name. All examples I have seen seem to suggest this should work. Given that the variable is defined in the vars section I expected the task name to print the value of the variable. Why doesn't this work?
Even the example from the Ansible documentation seems to not print the variable value.
---
- hosts: 127.0.0.1
gather_facts: no
vars:
vhost: "foo"
tasks:
- name: create a virtual host file for {{ vhost }}
debug: msg="{{ vhost }}"
This results in the following output:
PLAY [127.0.0.1]
**************************************************************
TASK: [create a virtual host file for {{ vhost }}]
****************************
ok: [127.0.0.1] => {
"msg": "foo"
}
PLAY RECAP
********************************************************************
127.0.0.1 : ok=1 changed=0 unreachable=0 failed=0
Update
This works with 1.7.2 but does not work with 1.8.3. So either this is a bug or a feature.
Variables are not resolved inside the name. Only inside the actual tasks/conditions etc. the placeholders will be resolved. I guess this is by design. Imagine you have a with_items loop and use the {{ item }}in the name. The tasks name will only be printed once, but the {{ item }} would change in every iteration.
I see the examples, even the one in the doc you linked to, use variables in the name. But that doesn't mean the result would be like you expected it. The docs are community managed. It might be someone just put that line there w/o testing it - or maybe it used to work like that in a previous version of Ansible and the docs have not been updated then. (I'm only using Ansible since about one year). But even though it doesn't work like we wish it would, I'm still using variables in my name's, just to indicate that the task is based on dynamic parameters. Might be the examples have been written with the same intention.
An interesting observation I recently made (Ansible 1.9.4) is, default values are written out in the task name.
- name: create a virtual host file for {{ vhost | default("foo") }}
When executed, Ansible would show the task title as:
TASK: [create a virtual host file for foo]
This way you can avoid ugly task names in the output.
Explanation
Whether the variable gets interpolated depends on where it has been declared.
Imagine You have two hosts: A and B.
If variable foo has only per-host values, when Ansible runs the play, it cannot decide which value to use.
On the other hand, if it has a global value (global in a sense of host invariance), there is no confusion which value to use.
Source: https://github.com/ansible/ansible/issues/3103#issuecomment-18835432
Hands on playbook
ansible_user is an inventory variable
greeting is an invariant variable
- name: Test variable substitution in names
hosts: localhost
connection: local
vars:
greeting: Hello
tasks:
- name: Sorry {{ ansible_user }}
debug:
msg: this won't work
- name: You say '{{ greeting }}'
debug:
var: ansible_user
I experienced the same problem today in one of my Ansible roles and I noticed something interesting.
When I use the set_fact module before I use the vars in the task name, they actually get translated to their correct values.
In this example I wanted to set the password for a remote user:
Notice that I use the vars test_user and user_password that I set as facts before.
- name: Prepare to set user password
set_fact:
user_password: "{{ linux_pass }}"
user_salt: "s0m3s4lt"
test_user: "{{ ansible_user }}"
- name: "Changing password for user {{ test_user }} to {{ user_password }}"
user:
name: "{{ ansible_user }}"
password: "{{ user_password | password_hash('sha512', user_salt) }}"
state: present
shell: /bin/bash
update_password: always
This gives me the following output:
TASK [install : Changing password for user linux to LiNuXuSeRPaSs#]
So this solved my problem.
It might be ugly, but you can somewhat workaround with something like this:
- name: create a virtual host file
debug:
msg: "Some command result"
loop: "{{ [ vhost ] }}"
or
- name: create a virtual host file
debug:
msg: "Some command result"
loop_control:
label: "{{ vhost }}"
loop: [1]
I wouldn't do this in general, but it shows how you can use items or label to give information outside of the command result. While it might not
Source: https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html

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