Use dynamic variable name - ansible

I'm trying to get the value of ip_address from the following yaml that I'm including as variables on ansible:
common:
ntp:
- time.google.com
node1:
default_route: 10.128.0.1
dns:
- 10.128.0.2
hostname: ip-10-128-5-17
device_interface: ens5
cluster_interface: ens5
interfaces:
ens5:
ip_address: 10.128.5.17
nat_ip_address: 18.221.63.178
netmask: 255.255.240.0
version: 2
However the network interface (ens5 here) may be named something else, such as eth0. My ansible code is this:
- hosts: all
tasks:
- name: Read configuration from the yaml file
include_vars: "{{ config_yaml }}"
- name: Dump Interface Settings
vars:
msg: node1.interfaces.{{ cvp_device_interface }}.ip_address
debug:
msg: "{{ msg }}"
tags: debug_info
Running the code like this I can get the key's name:
TASK [Dump Interface Settings] *************************************************
│ ok: [18.221.63.178] => {
│ "msg": "node1.interfaces.ens5.ip_address"
│ }
But what I actually need is the value (i.e: something like {{ vars[msg] }}, which should expand into {{ node1.interfaces.ens5.ip_address }}). How can I accomplish this?

Use sqare brackets.
Example: a minimal playbook, which defines a variable called "device". This variable is used to return the active status of the device.
- hosts: localhost
connection: local
vars:
device: enx0050b60c19af
tasks:
- debug: var=device
- debug: var=hostvars.localhost.ansible_facts[device].active
Output:
$ ansible-playbook example.yaml
[WARNING]: provided hosts list is empty, only localhost is available. Note that
the implicit localhost does not match 'all'
PLAY [localhost] *******************************************************************
TASK [Gathering Facts] *************************************************************
ok: [localhost]
TASK [debug] ***********************************************************************
ok: [localhost] => {
"device": "enx0050b60c19af"
}
TASK [debug] ***********************************************************************
ok: [localhost] => {
"hostvars.localhost.ansible_facts[device].active": true
}
PLAY RECAP *************************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0

see comment
- hosts: all
tasks:
- name: Read configuration from the yaml file
include_vars: "{{ config_yaml }}"
- name: Dump Interface Settings
debug:
msg: "{{ node1['interfaces'][cvp_device_interface]['ip_address'] }}"
debug:
msg: "{{ msg }}"
tags: debug_info

Related

Ansible: How to check multiple servers for a text file value, to decide which servers to run the script on?

I am trying to ask Ansible to check if a server is passive or active based on the value of a specific file in each server, then Ansible will decide which server it runs the next script on.
For example with 2 servers:
Server1
cat /tmp/currentstate
PASSIVE
Server2
cat /tmp/currentstate
ACTIVE
In Ansible
Trigger next set of jobs on server where the output was ACTIVE.
Once the jobs complete, trigger next set of jobs on server where output was PASSIVE
What I have done so far to grab the state, and output the value to Ansible is
- hosts: "{{ hostname1 | mandatory }}"
gather_facts: no
tasks:
- name: Grab state of first server
shell: |
cat {{ ans_script_path }}currentstate.log
register: state_server1
- debug:
msg: "{{ state_server1.stdout }}"
- hosts: "{{ hostname2 | mandatory }}"
gather_facts: no
tasks:
- name: Grab state of second server
shell: |
cat {{ ans_script_path }}currentstate.log
register: state_server2
- debug:
msg: "{{ state_server2.stdout }}"
What I have done so far to trigger the script
- hosts: "{{ active_hostname | mandatory }}"
tasks:
- name: Run the shutdown on active server first
shell: sh {{ ans_script_path }}stopstart_terracotta_main.sh shutdown
register: run_result
- debug:
msg: "{{ run_result.stdout }}"
- hosts: "{{ passive_hostname | mandatory }}"
tasks:
- name: Run the shutdown on passive server first
shell: sh {{ ans_script_path }}stopstart_terracotta_main.sh shutdown
register: run_result
- debug:
msg: "{{ run_result.stdout }}"
but I don't know how to set the value of active_hostname & passive_hostname based on the value from the script above.
How can I set the Ansible variable of active_hostname & passive_hostname based on the output of the first section?
A better solution came to my mind is to include hosts in new groups according to their state.
This would be more optimal in case there are more than two hosts.
- hosts: all
gather_facts: no
vars:
ans_script_path: /tmp/
tasks:
- name: Grab state of server
shell: |
cat {{ ans_script_path }}currentstate.log
register: server_state
- add_host:
hostname: "{{ item }}"
# every host will be added to a new group according to its state
groups: "{{ 'active' if hostvars[item].server_state.stdout == 'ACTIVE' else 'passive' }}"
# Shorter, but the new groups will be in capital letters
# groups: "{{ hostvars[item].server_state.stdout }}"
loop: "{{ ansible_play_hosts }}"
changed_when: false
- name: show the groups the host(s) are in
debug:
msg: "{{ group_names }}"
- hosts: active
gather_facts: no
tasks:
- name: Run the shutdown on active server first
shell: hostname -f # changed that for debugging
register: run_result
- debug:
msg: "{{ run_result.stdout }}"
- hosts: passive
gather_facts: no
tasks:
- name: Run the shutdown on passive server first
shell: hostname -f
register: run_result
- debug:
msg: "{{ run_result.stdout }}"
test-001 is PASSIVE
test-002 is ACTIVE
PLAY [all] ***************************************************************
TASK [Grab state of server] **********************************************
ok: [test-002]
ok: [test-001]
TASK [add_host] **********************************************************
ok: [test-001] => (item=test-001)
ok: [test-001] => (item=test-002)
TASK [show the groups the host(s) are in] ********************************
ok: [test-001] => {
"msg": [
"passive"
]
}
ok: [test-002] => {
"msg": [
"active"
]
}
PLAY [active] *************************************************************
TASK [Run the shutdown on active server first] ****************************
changed: [test-002]
TASK [debug] **************************************************************
ok: [test-002] => {
"msg": "test-002"
}
PLAY [passive] ************************************************************
TASK [Run the shutdown on passive server first] ****************************
changed: [test-001]
TASK [debug] **************************************************************
ok: [test-001] => {
"msg": "test-001"
}
PLAY RECAP ****************************************************************
test-001 : ok=5 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
test-002 : ok=4 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
For example, given two remote hosts
shell> ssh admin#test_11 cat /tmp/currentstate.log
ACTIVE
shell> ssh admin#test_13 cat /tmp/currentstate.log
PASSIVE
The playbook below reads the files and runs the commands on active and passive servers
shell> cat pb.yml
- hosts: "{{ host1 }},{{ host2 }}"
gather_facts: false
vars:
server_states: "{{ dict(ansible_play_hosts|
zip(ansible_play_hosts|
map('extract', hostvars, ['server_state', 'stdout'])|
list)) }}"
server_active: "{{ server_states|dict2items|
selectattr('value', 'eq', 'ACTIVE')|
map(attribute='key')|list }}"
server_pasive: "{{ server_states|dict2items|
selectattr('value', 'eq', 'PASSIVE')|
map(attribute='key')|list }}"
tasks:
- command: cat /tmp/currentstate.log
register: server_state
- debug:
var: server_state.stdout
- block:
- debug:
var: server_states
- debug:
var: server_active
- debug:
var: server_pasive
run_once: true
- command: echo 'Shutdown active server'
register: out_active
delegate_to: "{{ server_active.0 }}"
- command: echo 'Shutdown passive server'
register: out_pasive
delegate_to: "{{ server_pasive.0 }}"
- debug:
msg: |
{{ server_active.0 }}: [{{ out_active.stdout }}] {{ out_active.start }}
{{ server_pasive.0 }}: [{{ out_pasive.stdout }}] {{ out_pasive.start }}
run_once: true
shell> ansible-playbook pb.yml -e host1=test_11 -e host2=test_13
PLAY [test_11,test_13] ***********************************************************************
TASK [command] *******************************************************************************
changed: [test_13]
changed: [test_11]
TASK [debug] *********************************************************************************
ok: [test_11] =>
server_state.stdout: ACTIVE
ok: [test_13] =>
server_state.stdout: PASSIVE
TASK [debug] *********************************************************************************
ok: [test_11] =>
server_states:
test_11: ACTIVE
test_13: PASSIVE
TASK [debug] *********************************************************************************
ok: [test_11] =>
server_active:
- test_11
TASK [debug] *********************************************************************************
ok: [test_11] =>
server_pasive:
- test_13
TASK [command] *******************************************************************************
changed: [test_11]
changed: [test_13 -> test_11]
TASK [command] *******************************************************************************
changed: [test_11 -> test_13]
changed: [test_13]
TASK [debug] *********************************************************************************
ok: [test_11] =>
msg: |-
test_11: [Shutdown active server] 2022-10-27 11:16:00.766309
test_13: [Shutdown passive server] 2022-10-27 11:16:02.501907
PLAY RECAP ***********************************************************************************
test_11: ok=8 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
test_13: ok=4 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
From the description of your use case I understand that you like to perform tasks on certain servers which a have service role installed (annot.: Terracotta Server) and based on a certain service state.
Therefore, I like to recommend an approach with Custom facts.
Depending on if you have control about where the currentstate.log is placed or how it is structured, you could use in example something like
cat /tmp/ansible/service/terracotta.fact
[currentstate]
ACTIVE = true
PASSIVE = false
or add dynamic facts by adding executable scripts to facts.d ...
Means, alternatively, you can add the current service state to your host facts by creating and running a script in facts.d, which would just read the content of /tmp/currentstate.log.
Then, a sample playbook like
---
- hosts: localhost
become: false
gather_facts: true
fact_path: /tmp/ansible/service
gather_subset:
- "!all"
- "!min"
- "local"
tasks:
- name: Show Gathered Facts
debug:
msg: "{{ ansible_facts }}"
when: ansible_local.terracotta.currentstate.active | bool
will result into an output of
TASK [Show Gathered Facts] ******
ok: [localhost] =>
msg:
ansible_local:
terracotta:
currentstate:
active: 'true'
passive: 'false'
gather_subset:
- '!all'
- '!min'
- local
module_setup: true
An other approach is to address How the inventory is build and Group the hosts
[terracotta:children]
terracotta_active
terracotta_passive
[terracotta_active]
terracotta1.example.com
[terracotta_passive]
terracotta2.example.com
You can then just easily and simple define where a playbook or task should run, just by Targeting hosts and groups
ansible-inventory -i hosts--graph
#all:
|--#terracotta:
| |--#terracotta_active:
| | |--terracotta1.example.com
| |--#terracotta_passive:
| | |--terracotta2.example.com
|--#ungrouped:
ansible-inventory -i hosts terracotta_active --graph
#terracotta_active:
|--terracotta1.example.com
or Conditionals based on ansible_facts, in example
when: 'terracotta_active' in group_names
... from my understanding, both would be minimal and simple solutions without re-implementing functionality which seems to be already there.

Check for Value for a Key in Dict within Ansible

I want to run a Ansible Task in a string(vlan) is found within a keys (name) value. i.e
dict
interfaces_l3:
- name: vlan101
ipv4: 192.168.1.100/24
state: present
task
- name: Enable Features
nxos_feature:
feature: interface-vlan
state: enabled
when: vlan in interfaces_l3.values()
This is what I have but currently, this is not working.
There are a few problems with your expression:
interfaces_l3.values() should just blow up, because interfaces_l3 is a list, and lists don't have a .values() method.
You are referring to a variable named vlan rather than a string "vlan".
You are asking if any item in the interfaces_l3 list contains the string "vlan" in the value of the name attribute. You could do something like this:
---
- hosts: localhost
gather_facts: false
vars:
interfaces_l3_with_vlan:
- name: vlan101
ipv4: 192.168.1.100/24
state: present
interfaces_l3_without_vlan:
- name: something else
ipv4: 192.168.1.100/24
state: present
tasks:
- name: this should run
debug:
msg: "enabling features"
when: "interfaces_l3_with_vlan|selectattr('name', 'match', 'vlan')|list"
- name: this should be skipped
debug:
msg: "enabling features"
when: "interfaces_l3_without_vlan|selectattr('name', 'match', 'vlan')|list"
Which produces the following output:
PLAY [localhost] ******************************************************************************************************************************************************************************
TASK [this should run] ************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "enabling features"
}
TASK [this should be skipped] *****************************************************************************************************************************************************************
skipping: [localhost]
PLAY RECAP ************************************************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0

Ansible task to to get the localhost ip address and replace in the destination file with value

i am running below task and replacing whole content in the destination file with ip address
---
- hosts: localhost
connection: local
tasks:
- debug: var=ansible_all_ipv4_addresses
- debug: var=ansible_default_ipv4.address
- copy: content="{{ ansible_all_ipv4_addresses }}" dest=/root/curator.yml
i have variable in curator.yml, i want to update variable {{ ansible_default_ipv4.address }} with ip address.
---
client:
hosts:
- {{ ansible_default_ipv4.address }}
port: 9200
url_prefix:
use_ssl: False
ssl_no_validate: False
http_auth:
timeout: 30
master_only: False
logging:
loglevel: INFO
logfile:
logformat: default
blacklist: ['elasticsearch', 'urllib3']
When i execute above playbook task it's replacing whole information in curator.yml with ip address in the debug output
PLAY [localhost] ****************************************************************************************************************
TASK [Gathering Facts] **********************************************************************************************************
ok: [localhost]
TASK [debug] ********************************************************************************************************************
ok: [localhost] => {
"ansible_all_ipv4_addresses": [
"10.0.0.5"
]
}
TASK [debug] ********************************************************************************************************************
ok: [localhost] => {
"ansible_default_ipv4.address": "10.0.0.5"
}
TASK [copy] *********************************************************************************************************************
changed: [localhost]
PLAY RECAP **********************************************************************************************************************
localhost : ok=4 changed=1 unreachable=0 failed=0
i am also included below task but looks like it's not working
#- name: rewrite
# vars:
# ansible_default_ipv4.address: "{{ ansible_default_ipv4.address[0] }}"
# template:
# src: templates/curator.yml.j2
# dest: /root/curator.yml
There are issues with your copy and template tasks:
Copy Task - As you are using content parameter, it will "set the contents of a file directly to the specified value" copy_module
Template task - You can't define/update vars (or even set_fact) with .("dot") notation and you don't even need to, as the ansible_default_ipv4.address variable is already defined and the value is set.
This will work:
---
- name: Update ip
hosts: 127.0.0.1
connection: local
tasks:
- debug: var=ansible_all_ipv4_addresses
- debug: var=ansible_default_ipv4.address
- name: Template file with ip
template:
src: templates/curator.yml.j2
dest: /root/curator.yml
...

How to pass vars in nested Ansible playbook?

I have a nested ansible playbook (master) file and I want to call included playbook (slave) with their own JSON vars.
Master.yaml
- name: this is a play at the top level of a file
hosts: local
connection: local
tasks:
- debug: msg=hello
- include: slave_first.yaml
- include: slave_second.yaml
slave_first.yaml should make use of "vars/slave_first_vars.json" file and slave_second.yaml should make use of "vars/slave_second_vars.json" file.
When including playbooks you can only override variables with vars statement, like:
- include: slave_first.yaml
vars:
myvar: foo
- include: slave_second.yaml
vars:
myvar: foo
There are no other options for PlaybookInclude.
If you need to load variables from files, you have to use vars_files or include_vars inside your slave playbooks.
In your scenario, I'll use like this, master.yml:
- hosts: localhost
connection: local
tasks:
- include: slave_first.yml
vars:
VAR_FILE: "slave_first_vars"
- include: slave_second.yml
vars:
VAR_FILE: "slave_second_vars"
While slave_first.yml and slave_second.yml are like this, in my case both are same but you get an idea that how you can use them:
slave_first.yml:
---
- include_vars: "{{ VAR_FILE }}.yml"
- debug:
msg: "{{ DOMAIN_NAME }}"
slave_second.yml:
---
- include_vars: "{{ VAR_FILE }}.yml"
- debug:
msg: "{{ DOMAIN_NAME }}"
Now come to the different variable part:
slave_first_vars.yml: in your case it will be json
---
DOMAIN_NAME: "first.com"
slave_second_vars.yml:
---
DOMAIN_NAME: "second.com"
Then you can run and verify that if work as expected:
➤ansible-playbook -i localhost, master.yml
PLAY [localhost] **********************************************************************************
TASK [Gathering Facts] **********************************************************************************
ok: [localhost]
TASK [include_vars] **********************************************************************************
ok: [localhost]
TASK [debug] **********************************************************************************
ok: [localhost] => {
"changed": false,
"msg": "first.com"
}
TASK [include_vars] **********************************************************************************
ok: [localhost]
TASK [debug] **********************************************************************************
ok: [localhost] => {
"changed": false,
"msg": "second.com"
}
PLAY RECAP **********************************************************************************
localhost : ok=5 changed=0 unreachable=0 failed=0
Hope that might help you!

Ansible run task multiple times based on groups

How do you run a task on each group, not just once for the groups?
I was excepting the Run this on each host tasks would run once for each group_var value. Instead it seems to just be picking one and running it.
I plan on breaking these across multiple servers later but for now it should be able to run on one autoscale, and then easy break it up into multiple autoscale groups later as demand increases.
playbook.yml:
---
# Run with: ansible-playbook -i localhost, playbook.yml
- name: Register Groups
hosts: localhost
connection: local
tasks:
- name: Add the groups
add_host:
name: localhost
ansible_connection: local
groups: rest-api, msg-consumer
- name: Run this on each host
hosts:
- rest-api
- msg-consumer
tasks:
- name: Say type
debug: var=item
with_items: run_type
group_vars/rest-api:
---
run_type: web
group_vars/msg-consumer:
---
run_type: consumer
Output Ansible 1.8.2:
$ ansible-playbook -i localhost, playbook.yml
PLAY [Register Groups] ********************************************************
GATHERING FACTS ***************************************************************
ok: [localhost]
TASK: [Add the groups] ********************************************************
ok: [localhost]
PLAY [Run this on each host] **************************************************
GATHERING FACTS ***************************************************************
ok: [localhost]
TASK: [Say type] **************************************************************
ok: [localhost] => (item=consumer) => {
"item": "consumer"
}
PLAY RECAP ********************************************************************
localhost : ok=4 changed=0 unreachable=0 failed=0
Note: It may be something else. I thought I could also clutter my playbook but breaking up the tasks like like follows:
---
- name: Register Groups
hosts: localhost
connection: local
tasks:
- name: Add the groups
add_host:
name: localhost
ansible_connection: local
groups: rest-api, msg-consumer
- name: Run this on each host
hosts:
- msg-consumer
tasks:
- name: Say type
debug: var=item
with_items: run_type
- name: Run this on each host
hosts:
- rest-api
tasks:
- name: Say type
debug: var=item
with_items: run_type
But the output for the 2nd playbook is:
$ ansible-playbook -i localhost, playbook2.yml
PLAY [Register Groups] ********************************************************
GATHERING FACTS ***************************************************************
ok: [localhost]
TASK: [Add the groups] ********************************************************
ok: [localhost]
PLAY [Run this on each host] **************************************************
GATHERING FACTS ***************************************************************
ok: [localhost]
TASK: [Say type] **************************************************************
ok: [localhost] => (item=consumer) => {
"item": "consumer"
}
PLAY [Run this on each host] **************************************************
GATHERING FACTS ***************************************************************
ok: [localhost]
TASK: [Say type] **************************************************************
ok: [localhost] => (item=consumer) => {
"item": "consumer"
}
PLAY RECAP ********************************************************************
localhost : ok=6 changed=0 unreachable=0 failed=0
Edit: Yet Another attempt to access the data, it looks like group_vars isn't behaving like I expect. The following outputs consumer twice also.
-
# Run with: ansible-playbook -i localhost, playbook.yml
- name: Register Groups
hosts: localhost
connection: local
tasks:
- name: Add the groups
add_host:
name: localhost
ansible_connection: local
groups: rest-api, msg-consumer
- name: Run this on each host
hosts:
- msg-consumer
- rest-api
tasks:
- name: What's your run type
debug: var=hostvars[groups[item][0]]['run_type']
with_items: group_names
The easiest way to do this is to use aliases for the hostnames instead of the real hosts:
---
- name: Register Groups
hosts: localhost
connection: local
tasks:
- name: Add the rest-api alias for my app
add_host:
name: my-app-rest-api
ansible_ssh_host: 127.0.0.1
groups: rest-api
- name: Add the msg-consumer alias for my app
add_host:
name: my-app-msg-consumer
ansible_ssh_host: 127.0.0.1
groups: msg-consumer
- name: Test Run Types
hosts:
- msg-consumer
- rest-api
tasks:
- name: What's your run type
debug: msg="Run Type of {{ ansible_ssh_host }} is {{ run_type }}"
now you can use your group_vars again:
group_vars/rest-api:
---
run_type: web
group_vars/msg-consumer:
---
run_type: consumer
and the output will be:
PLAY [Register Groups] ********************************************************
TASK: [Add the rest-api alias for my app] *************************************
ok: [localhost]
TASK: [Add the msg-consumer alias for my app] *********************************
ok: [localhost]
PLAY [Test Run Types] *********************************************************
TASK: [What's your run type] **************************************************
ok: [my-app-msg-consumer] => {
"msg": "Run Type of 127.0.0.1 is consumer"
}
ok: [my-app-rest-api] => {
"msg": "Run Type of 127.0.0.1 is web"
}
For now this is the best I can come up with:
---
- name: Register Groups
hosts: localhost
connection: local
tasks:
- name: Add new host group
add_host:
name: 127.0.0.1
ansible_connection: local
groups: new-server
run_types:
- rest-api
- msg-consumer
- name: Add another new host group
add_host:
name: 127.0.0.2
ansible_connection: local
groups: new-server
run_types:
- nothing
- name: Test Run Types Server 1
hosts:
- new-server
tasks:
- name: What's your run type
debug: var=item
with_items: run_types
Note: The hosts must be different for this to work, otherwise it will override and use the last variable value used with add_host.
See my answer under Ansible run task once per database-name.
Basically, there is no run_once_per_group, and the closest method I'm aware of is a true run_once that loops over groups. To make matters more cluttered, there is no group_vars dictionary variable.
---
- hosts: all
tasks:
- name: "do this once per group"
delegate_to: localhost
debug:
msg: "do something on {{hostvars[groups[item.key].0]['somevar']}} for group named {{item}}"
run_once: yes
with_dict: groups
when: item.key not in ['all', 'ungrouped']

Resources