0

I want to launch my playbook only on the first host of each inventory group.

[ego]
server1
server2

[asp]
server1
server2

[bre]
server1
server2

I saw there is an option to specify group_name but this is just for 1 group_name

hosts: group_name[0]

This option will only run it on 1 host in general and doesn't take into account the groups.

The run_once:True

Is there a way to run the playbook or tasks over the first host of all groups without defining which groups in the playbook.

So when executing the playbook i want to be able to select different groups as a limit and only run the play against the first host of each group.

Kevin
  • 143
  • 1
  • 8
  • 1
    Does this answer your question? [How to run ansible playbook on one/first server across different groups](https://stackoverflow.com/questions/73898543/how-to-run-ansible-playbook-on-one-first-server-across-different-groups) – β.εηοιτ.βε Oct 04 '22 at 07:26
  • Hi, it partially does but i cannot "hardcode" the groups upfront. So during the start of the play a limit is given of which host groups to run on or even to run it over the complete inventory which consist out of 150 or more host groups. – Kevin Oct 04 '22 at 08:04
  • 1
    Regarding "_i cannot "hardcode" the groups upfront_" and "_the complete inventory which consist out of 150 or more host groups_", can you provide more details about your inventory and the naming convention used for hosts and groups? It might be possible to answer your question then. – U880D Oct 04 '22 at 08:23
  • 1
    The inventory list consists out of groups that do not have a naming standard or convention. The names are like asp, ego, cba, gen,... These are all seperate groups. They are not also not a child of a larger overall group. – Kevin Oct 04 '22 at 08:38
  • Just a note, whoever is maintaining the inventory should start with implementing a good (data) structure, naming convention, etc. to gt rid of unstructured (meta) data as soon as possible. There are already some examples under [How to build your inventory](https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html). – U880D Oct 04 '22 at 11:25
  • 1
    @Kevin, you shouldn't have remove the part about "_The inventory list consists out of groups that do not have a naming standard or convention. The names are random like ... These are all separate groups. They are not also not a child of a larger overall group._" Since that is an important part of the data (un)structure in your inventory. Even your changed example inventory could lead to the assumption there are only 3 hardcoded groups and a kind of structure and therefore to an unwanted answer for you. – U880D Oct 04 '22 at 15:42
  • 1
    Just another note, your current description and requirement(?) "_Is there a way to run the playbook ... over the first host of all groups without defining which groups ... I want to be able to select different groups as a limit and only run the play against the first host of each group._" excludes each other. – U880D Oct 04 '22 at 15:51

2 Answers2

2

You can use add_host module to build a new group containing only the first host of every group in the inventory.

- hosts: all
  gather_facts: no
  tasks:
  # groups.keys() gets all groups names. I will exclude 'all' and 'ungrouped'
  # solutions to do that in a better way are welcomed
  - set_fact:
      groups_names: "{{ groups.keys() | difference(['all'])| difference(['ungrouped']) }}"
    run_once: true
 
  # Add first host from every group to new_group
  - add_host:
      name: "{{ groups[item][0] }}"
      groups: new_group
    loop: "{{ groups_names }}"
    changed_when: false

  - debug:
      msg: "{{ inventory_hostname }}"
    when: inventory_hostname in groups['new_group']

My inventory:

[test]
test-001
test-002

[staging]
staging-001
staging-002
staging-003

[prod]
prod-001
prod-002

The output:

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

TASK [set_fact] ********************************************
ok: [test-001]

TASK [add_host] ********************************************
ok: [test-001] => (item=test)
ok: [test-001] => (item=staging)
ok: [test-001] => (item=prod)

TASK [debug] ***********************************************
skipping: [test-002]
ok: [test-001] => {
    "msg": "test-001"
}
skipping: [staging-002]
ok: [staging-001] => {
    "msg": "staging-001"
}
skipping: [staging-003]
skipping: [prod-002]
ok: [prod-001] => {
    "msg": "prod-001"
}

PLAY RECAP ***************************************************
prod-001    : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
prod-002    : ok=0    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0   
staging-001 : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
staging-002 : ok=0    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0   
staging-003 : ok=0    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0   
test-001    : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
test-002    : ok=0    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0
Khaled
  • 775
  • 1
  • 5
  • 19
  • 2
    I like your solution since it is showing a good option for how to do it dynamically inside the playbook and tasks and extend the approach of [how to add hosts dynamically to a dynamic group](https://stackoverflow.com/a/73373684/6771046). – U880D Oct 04 '22 at 16:01
  • 1
    @U880D thank you. I use AWX all the time, therefore I always try to find solutions inside the playbook, instead of command line. – Khaled Oct 04 '22 at 18:15
  • 1
    I do too, but I've interpreted the comments "_... it partially does but I cannot "hardcode" the groups upfront ..._" and "_... without defining which groups in the playbook ..._" that there can nothing be done within the playbook. Therefore I've looked for an approach outside of Ansible, in CLI and for an unknown naming. There was just not enough information for me. – U880D Oct 04 '22 at 18:22
1

How to launch playbook only on the first host of each inventory group? ... I cannot "hardcode" the groups upfront ... the complete inventory which consist out of 150 or more host groups ...

From the question and description you've provided I understand that you like to dynamically limit the execution of a playbook to certain hosts and groups. As it is still not clear (for me) what exactly and where you try to achieve, I'll provide one possible approach with CLI tools. Within a playbook and for tasks there would be other options available.

An example inventory hosts like

[tomcat]
tomcat1.example.com
tomcat2.example.com
tomcat3.example.com

[apache]
apache1.example.com
apache2.example.com
apache3.example.com

[sql]
sql1.example.com
sql2.example.com
sql3.example.com
ansible-inventory -i hosts --graph
@all:
  |--@apache:
  |  |--apache1.example.com
  |  |--apache2.example.com
  |  |--apache3.example.com
  |--@sql:
  |  |--sql1.example.com
  |  |--sql2.example.com
  |  |--sql3.example.com
  |--@tomcat:
  |  |--tomcat1.example.com
  |  |--tomcat2.example.com
  |  |--tomcat3.example.com
  |--@ungrouped:

with a group list

ansible-inventory -i hosts --list | jq --raw-output '.all.children[]'
apache
sql
tomcat
ungrouped

and example playbook groups.yml like

---
- hosts: '*'
  become: false
  gather_facts: false

  tasks:

  - name: Show Facts
    debug:
      msg: "{{ item }}"
    when: item == inventory_hostname
    loop: "{{ ansible_play_hosts }}"

called via

LIMIT=$(ansible-inventory -i hosts --list | jq --raw-output '.all.children[]' | head -n -1 | sed s/$/[0]/ | tr '\n' ',' | rev | cut -c2- | rev)

echo ${LIMIT}
apache[0],sql[0],tomcat[0]

ansible-playbook -i hosts groups.yml --limit="${LIMIT}"

will result into an oputput of

TASK [Show Facts] ****************************************
ok: [apache1.example.com] => (item=apache1.example.com) =>
  msg: apache1.example.com
ok: [tomcat1.example.com] => (item=tomcat1.example.com) =>
  msg: tomcat1.example.com
ok: [sql1.example.com] => (item=sql1.example.com) =>
  msg: sql1.example.com

As one can see it is executing on the first host of each inventory group only.

The approach is using

  • an environment variable LIMIT to store the generated list for --limit
  • the ansible-iventory to list all group
  • removes the default group ungrouped from the output
  • defines one list element by adding [0]
  • creates an CSV from the output by exchanging line breaks \n with ,
  • and deletes the last ,

Similar Q&A

Further Readings and used during research


Since there might be other or better solutions possible ...

Follow-Ups

U880D
  • 8,601
  • 6
  • 24
  • 40