Not picking up variable in expression correctly - Ansible - ansible

I'm trying the following -
---
- name: Test
hosts: "{{ hosts }}"
vars:
before: "groups.{{ hosts[0] }}_group_name"
after: "{{ before }}" # This equals {{ groups.test_group_name }}
roles:
- test-check
Just an explanation: I'm feeding hosts in when executing the playbook as a 'var'. In this case, var = test. The expected var string for before would be groups.test_group_name which is a group that contains multiple hosts in my inventory. However, when I execute this, after remains as groups.test_group_name instead of the expected array of hosts.
Does anybody know how I can remedy this? If I hard-code the host_name (test) into the after var, it picks it up, but if I don't, it doesn't. Thanks.

It appears you are trying to do pseudocode: {{ eval(before) }} but that is not how ansible, or jinja2, work. Thankfully, groups is a normal python dict and thus is subject to the __getitem__ syntax [] to dynamically look up keys
Thus, you likely want:
- hosts: "{{ hosts }}"
vars:
after: "{{ groups[ hosts[0]+'_group_name' ] }}"
tasks:
- debug: var=after

Related

How to append to a list where the string you're appending is being constucted dynamically in the play

Pulling my hair out. I know I've done this eons ago with much older versions of ansible. I haven't touched ansible much so don't recall how I did this. This seems so ridiculously easy at first glance so obviously something stupid i'm overlooking.
Given sample inventory, nothing fancy:
Note: removing FQDN's for brevity.
Note: this automation is working with a large number of hosts and many many more variables than I'm representing here. This is a majorly dumbed down example.
[nodes]
foo1
foo2
foo3
[all:vars]
minio_proto=http
disks_per_node=64
Ultimately I need to build a string that wraps text around the hostnames. This is for minio if you are familiar with it.
You start a server by speficying all the nodes and the disk paths:
minio server http://foo1/data/disk{0...79} http://foo2/data/disk{0...63} http://foo3/data/disk{0...63}
So inside my play I need to construct this string that eventually gets passed to a shell command to start the server on each node.
So I have a playbook (eventually this will be a role) that will be run once, not for every host, just to construct this server parameter.
I've tried so many permutations of stuff I can't possibly list them all but here's just one of my latest stabs:
---
- hosts: all
gather_facts: false
become: true
ignore_errors: false
vars:
minio_startup1: []
minio_startup2: []
tasks:
- name: Verifying hosts are up and we can become root
become: true
ping:
# THIS WORKS FINE, APPENDING A SIMPLE VARIABLE WORKS
- name: Building minio startup parameter
set_fact:
minio_startup1: "{{ minio_startup1 + [item] }}"
with_items:
- "{{ ansible_play_hosts }}"
- debug: var=minio_startup1
# DOING STUFF LIKE THIS DOES NOT WORK
- name: Building minio startup parameter
set_fact:
minio_startup2: "{{ minio_startup2 }} + [{{ minio_proto }}//{{ item }}/data{0...{{ disks_per_node|int - 1 }}}] }}"
with_items:
- "{{ ansible_play_hosts }}"
- debug: var=minio_startup2
Basically at this point in the playbook I want a list that looks like this:
[ 'http://foo1/data{0...63}', 'http://foo2/data{0...63}', 'http://foo2/data{0...63}']
Then later in the playbook, I can concatenate this into a single string I can feed my minio container thru a shell command:
{{ minio_startup|join(' ') }}
I know I did this years ago but after 3 hrs of hair pulling it eludes me.
* UPDATE *
Well I figured out a way, not sure if this is the best. If interested, this is what I did. It looks like you can use '~' to concatenate items inside the []'s. I still had to do the math of determinig the ending disk index number in a separate variable. (disk_ending_index)
minio_startup2: "{{ minio_startup2 + [ minio_proto ~ '//' ~ item ~ '/data{0...' ~ disk_ending_index ~ '}' ] }}"
Use Single-Quoted Style. The playbook below
- hosts: foo1:foo2:foo3
gather_facts: false
vars:
minio_proto: http
disks_per_node: 64
minio_startup: []
tasks:
- set_fact:
minio_startup: '{{ minio_startup +
[minio_proto ~ "://" ~ item ~
"/data{0.." ~ disks_per_node ~ "}"] }}'
loop: "{{ ansible_play_hosts }}"
run_once: true
- debug:
var: minio_startup
run_once: true
- debug:
msg: "{{ minio_startup|join(' ') }}"
run_once: true
gives (abridged)
ok: [foo1] => {
"minio_startup": [
"http://foo1/data{0..63}",
"http://foo2/data{0..63}",
"http://foo3/data{0..63}"
]
}
ok: [foo1] => {
"msg": "http://foo1/data{0..63} http://foo2/data{0..63} http://foo3/data{0..63}"
}
Quoting from Single-Quoted Style:
... the “\” and “"” characters may be freely used. This restricts single-quoted scalars to printable characters ...

Ansible Playbook - Import Playbook pass Host Name for each in Group - Rescursive Loop detected

please excuse my question firstly. I have searched and spent time looking at How to iterate over inventory group in ansible? and Ansible iterate over hosts in inventory group set by variable style questions.
So will try keep question simple.
Inventory Looks like: ( this is all sample adapted to explain a little better .. )
[GroupA]
host1 country=USA
host2 country=USA
host3 country=UK
So I want to pass the Group through only without the hosts , so that the playbook is executed across each host. However as part of the playbook , firstly i need to run a script on a different server ( depending on the country ) and pass the hostname to that script.
In my testing , I discovered "inventory_hostname" so that even if i didnt pass the host and I passed the Group , I could put the hostname into a variable.
When i put it all together and started with import_playbook ( because this playbook is for a different server ) , I saw :
Error was a <class 'ansible.errors.AnsibleError'>, original message: An unhandled exception occurred while templating '{{ runninghost }}'. Error was a <class 'ansible.errors.AnsibleError'>, original message: An unhandled exception occurred while templating '{{ v_host_name }}'. Error was a <class 'ansible.errors.AnsibleError'>, original message: recursive loop detected in template string: {{ v_host_name }}
I have a playbook where i have some conditionals based on the "country"
- hosts: "{{ v_world }}"
vars:
v_world: "{{ v_world }}"
hostvalue : "{{ inventory_hostname }}"
- import_playbook: country-stuff-USA.yml
vars:
v_host_name: "{{ hostvalue }}"
when: hostvars[groups[v_world][0]]['country'] == "USA"
- import_playbook: country-stuff-UK.yml
vars:
v_host_name: "{{ hostvalue }}"
when: hostvars[groups[v_world][0]]['country'] == "UK"
country-stuff-USA.yml
---
- hosts: "world-server-usa.world"
vars:
runninghost: "{{ v_host_name }}"
roles:
- role: world_peace-usa
poll: 0
vars:
hostvalue: "{{ runninghost }}"
country-stuff-UK.yml
---
- hosts: "world-server-usa.world"
vars:
runninghost: "{{ v_host_name }}"
roles:
- role: world_peace-uk
poll: 0
vars:
hostvalue: "{{ runninghost }}"
world_peace-uk ( main.yml )
- name: world_peace-uk
shell: ksh /mountA/scriptuk.sh -host={{hostvalue}}
world_peace-uk ( main.yml )
- name: world_peace-usa
shell: ksh /mountA/scriptusa.sh -host={{hostvalue}}
Any thoughts ? I am sure I am doing something very wrong with this combination.. but i couldnt think of a better way to use the Groups , but to pass the hostname onto a script on a different box.
Many Thanks for reading!
You are overcomplicating your issue, you should use delegation whenever you need to perform a task on a different host then the ones specified in the play. For example:
---
- hosts: running_hosts
tasks:
- name: tasks to execute on another host
module: ...
delegate_to: other_host
- name: tasks to execute on the running host
module: ...
Regarding the error you are getting, it's because v_world is referencing itself in the statement v_world: "{{ v_world }}"

How to set variables for all subsequent hosts in one place without redundant code?

TL/DR: I have "src_host" and "dest_host" variables that I want to use to set the "- hosts:" object in a play. However, I have to set them again for each play under "vars:" of each "- hosts:" section e.g. src_host="{{ hostvars['localhost']['src_host'] }}" how do I set these two variables at the beginning and not have to reset them?
My hosts file looks like this
[wordpress]
localhost ansible_user=user ansible_port=22 ansible_ssh_private_key_file=/home/user/.ssh/id_rsa
root_localhost ansible_user=root ansible_port=22 ansible_ssh_private_key_file=/home/user/.ssh/id_rsa
---snip---
server2.net ansible_host="server2.net" ansible_user=user ansible_port=22 ansible_ssh_private_key_file=/home/user/.ssh/id_rsa
root_server2.net ansible_host="server2.net" ansible_user=root ansible_port=22 ansible_ssh_private_key_file=/home/user/.ssh/id_rsa
The beginning of my playbook looks like this:
- hosts: localhost, server2.net, root_server2.net #always include "localhost" in this list because it is needed to store the variables for the src_host and dest_host
vars:
src_host: localhost #modify these and the host will be changed for all subsequent plays/tasks
dest_host: server2.net #modify these and the host will be changed for all subsequent plays/tasks
src_dump_path: /home/user/cvrt9_dump.sql #set vars for copying file
roles:
- set_facts_for_db_copy
- hosts: "{{ src_host }}"
vars:
src_host: "{{ hostvars['localhost']['src_host'] }}"
dest_host: "{{ hostvars['localhost']['dest_host'] }}"
---snip---
roles:
- dump_db
- copy_file
etc . . .
for "- set_facts_for_db_copy" I have "main.yml" as this where I set the "src_host" and "dest_host" variables:
---
# tasks file for set_facts_for_db_copy
- name: create variables that equal src_dump_path and set src_host/dest_host
set_fact:
---snip---
src_host: "{{ src_host }}"
dest_host: "{{ dest_host }}"
So I need to set the "src_host" and "dest_host" for all subsequent "- hosts:" that use them by getting the values from one of the host variables that "set_fact_for_db_copy" set. I randomly picked "localhost" as you may have noticed:
src_host: "{{ hostvars['localhost']['src_host'] }}"
dest_host: "{{ hostvars['localhost']['dest_host'] }}"
If I don't have that line there I get:
user#localhost:/home/maintainer/ansible-play$ ansible-playbook -i hosts_tat-kay playbook.yml
PLAY [localhost, server2.net, root_server2.net] **************
TASK [setup] *******************************************************************
ok: [server2.net]
ok: [root_server2.net]
ok: [localhost]
TASK [set_facts_for_db_copy : create variables that equal src_dump_path] *******
ok: [localhost]
ok: [server2.net]
ok: [root_server2.net]
ERROR! the field 'hosts' has an invalid value, which appears to include a variable that is undefined. The error was: 'src_host' is undefined
The error appears to have been in '/home/maintainer/ansible-play/playbook.yml': line 14, column 3, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
- hosts: "{{ src_host }}"
^ here
. . .
Now I can set the these variables in my host file:
[wordpress:vars]
src_host=localhost
dest_host=server2.net
But then I still have to reference them from the subsquent "-hosts:" objects in my playbook with "{{ hostvars['localhost']['src_host'] }}" etc . . . So my question is how do I get rid of this redundant code in all my subsequent "-hosts:" objects (shown below) while still letting me change the "src_host" and "dest_host" variables once at the beginning and have those changes affect the rest of the plays? Thanks.
src_host: "{{ hostvars['localhost']['src_host'] }}"
dest_host: "{{ hostvars['localhost']['dest_host'] }}"
For this use your inventory file, make a parent group of the host you need the variable as follow.
[desireenv:children]
wordpress
otherhost
etc
and then assigne th vars value to the new parent group created
[desireenv:vars]
src_host: "{{ hostvars['localhost']['src_host'] }}"
dest_host: "{{ hostvars['localhost']['dest_host'] }}"
One solution I found with the help of https://stackoverflow.com/users/4716639/bryan-calvo-benoit is to put this in my hosts file (inventory file)
[wordpress]
localhost
server2.net
[testenv:children]
wordpress
[testenv:vars]
src_host=localhost
dest_host=server2.net
And then in the ansible playbook and the roles that it calls I had to replace
"{{ src_host }}"
with
"{{ hostvars['localhost']['src_host'] }}"
and likewise for "{{ dest_host }}"
However, I could delete this redundant code in my ansible playbook:
src_host: "{{ hostvars['localhost']['src_host'] }}"
dest_host: "{{ hostvars['localhost']['dest_host'] }}"
It would be nice if I didn't have to change the src_host and dest_host to hostvars['localhost']... because it seems arbitrary to use localhost and also what if I want to run several ansible scripts one right after the other with different src_host and dest_host? Using the inventory file locks it down so this is not ideal. If no one else answers I'll accept this answer because it is the only one that works and it technically does what my question asked.

Return Variable from Included Ansible Playbook

I have seen how to register variables within tasks in an ansible playbook and then use those variables elsewhere in the same playbook, but can you register a variable in an included playbook and then access those variables back in the original playbook?
Here is what I am trying to accomplish:
This is my main playbook:
- include: sub-playbook.yml job_url="http://some-jenkins-job"
- hosts: localhost
roles:
- some_role
sub-playbook.yml:
---
- hosts: localhost
tasks:
- name: Collect info from Jenkins Job
script: whatever.py --url "{{ job_url }}"
register: jenkins_artifacts
I'd like to be able to access the jenkins_artifacts results back in main_playbook if possible. I know you can access it from other hosts in the same playbook like this: "{{ hostvars['localhost']['jenkins_artifacts'].stdout_lines }}"
Is it the same idea for sharing across playbooks?
I'm confused what this question is about. Just use the variable name jenkins_artifacts:
- include: sub-playbook.yml job_url="http://some-jenkins-job"
- hosts: localhost
debug:
var: jenkins_artifacts
This might seem complicated but I love doing this in my Playbooks:
rc defines the name of the variable which contains the return value
ar gives the arguments to the include tasks
master.yml:
- name: verify_os
include_tasks: "verify_os/main.yml"
vars:
verify_os:
rc: "isos_present"
ar:
image: "{{ os.ar.to_os }}"
verify_os/main.yml:
---
- name: check image on device
ios_command:
commands:
- "sh bootflash: | inc {{ verify_os.ar.image }}"
register: image_check
- name: check if available
shell: "printf '{{ image_check.stdout_lines[0][0] }}\n' | grep {{ verify_os.ar.image }} | wc -l"
register: image_available
delegate_to: localhost
- set_fact: { "{{ verify_os.rc }}": "{{ true if image_available.stdout == '1' else false }}" }
...
I can now use the isos_present variable anywhere in the master.yml to access the returned value.

Iterate over a array in with_items loop

Based on this question
Ansible recursive checks in playbooks
I have another one.
We need to go through this structure
Zone spec https://gist.github.com/git001/9230f041aaa34d22ec82eb17d444550c
Now I can adress the hostnames via the array index but can I also iterate over the array "hosts"?
playbook
--
- hosts: all
gather_facts: no
vars_files:
- "../doc/application-zone-spec.yml"
roles:
- { role: ingress_add, customers: "{{ application_zone_spec }}" }
role
- name: Print ingress hostnames
debug: msg="{{ item.hosts.0.hostname }} {{ item.hosts.1.hostname }}"
with_items: "{{ customers.ingress }}"
We use.
ansible-playbook --version
ansible-playbook 2.1.0.0
config file = /etc/ansible/ansible.cfg
configured module search path = Default w/o overrides
Use with_subelements:
- name: Print ingress hostnames
debug: msg="{{ item.0.type }} {{ item.1.hostname }}"
with_subelements:
- "{{ customers.ingress }}"
- "hosts"
There is quite a bit of examples for different loops in the Loops section of the documentation.

Resources