how to choose a specific item from groups with a use of variable in ansible

Viewed 179

So I have a provision file with content as follow:

[dev-hosts]
dev-host-1
dev-host-2
dev-host-3

[tst-hosts]
tst-host-1
tst-host-2
tst-host-3

==========

In jenkins I choose an environment (dev, tst) and I need a playbook to get access to a specific item from the groups. I tried an expression as follows:

{{ groups[\"{{ env }}-hosts\"][0] }} << this gives me unexpected char u'\\' error and fails the job

{{ groups["{{ env }}-hosts"][0] }} << this on the other hand results in 'dict object' has no attribute '{{ env }}-hosts'"}

I went through ansible documentation but found no answer. How can I end up with 'dev-host-1' or any other item from the groups having the {{ env }} variable provided in Jenkins.

3 Answers

From the documentation:

Another rule is ‘moustaches don’t stack’. We often see this:

{{ somevar_{{other_var}} }} 

The above DOES NOT WORK as you expect, if you need to use a dynamic variable use the following as appropriate:

{{ hostvars[inventory_hostname]['somevar_' + other_var] }}

Source: https://docs.ansible.com/ansible/latest/reference_appendices/faq.html#when-should-i-use-also-how-to-interpolate-variables-or-dynamic-variable-names

If you want to have a variable concatenated with a string inside a moustaches block, use the Jinja operator designed for this: ~

Mind that the + sign would also work but migh have side effect, as explained in Jinja documentation:

+ Adds two objects together. Usually the objects are numbers, but if both are strings or lists, you can concatenate them this way. This, however, is not the preferred way to concatenate strings! For string concatenation, have a look-see at the ~ operator. {{ 1 + 1 }} is 2.

Source: https://jinja.palletsprojects.com/en/2.11.x/templates/#math

For example:

"{{ groups[env ~ '-hosts'][0] }}"

The answer from @β.εηοιτ.βε perfectly addresses the question, but I am adding an answer to provide a different perspective.

As per Ansible best practices:

You can keep your production environment separate from development, test, and staging environments by using separate inventory files or directories for each environment. This way you pick with -i what you are targeting. Keeping all your environments in one file can lead to surprises!

So if you can maintain two different folders, i.e. dev/hosts and tst/hosts (which also helps in organizing group_vars/ and host_vars/ by the way), you will be able to target the hosts with -i dev/hosts or -i tst/hosts. It may also simplify the requirements for using variables such as {{ env }} for targeting the hosts.

(as an addendum)

Simplify the code by creating the selected list, e.g.

    my_hosts: "{{ groups[env ~ '_hosts'] }}"

Then you can simply refer to the list my_hosts, e.g.

shell> cat playbook.yml
- hosts: all
  gather_facts: false
  vars:
    my_hosts: "{{ groups[env ~ '_hosts'] }}"
  tasks:
    - debug:
        var: my_hosts.1
      run_once: true

gives

shell> ansible-playbook playbook.yml -e env=tst

PLAY [all] **********************************************************************

TASK [debug] ********************************************************************
ok: [dev-host-1] => 
  my_hosts.1: tst-host-2
Related