2

I am attempting to automate an Ansible playbook that will run specific commands on a Docker container deployed on different servers.
For example, I have two VMs (vm1 and vm2 as shown in commands.yml), so, on vm1 I want to run command 1 and command 2 while on vm2 I want to run command 3 and command 4.
I just need to modify the loop to execute the commands one by one for each virtual machine, respectively. Currently, my configuration is structured as follows:

commands.yml

commands:
  - group: vm1
    commands:
     - command 1
     - command 2
  - group: vm2
    commands:
     - command 3
     - command 4

And my Ansible playbook looks like this:

- name: Deploy flex Docker image on GCP VM
  hosts: gcp_vms
  become: yes
  gather_facts: false
  vars_files:
    - vars.yml

  tasks:

    - name: Read commands from configuration file
      include_vars:
        file: commands.yml
        name: command_list

    - name: Run the commands
      community.docker.docker_container_exec:
        container: flex
        command: "{{ item.command }}"             
      loop: "{{ [command_list.commands].commands }}"

The inventory.ini is below

[gcp_vms]
vm1 ansible_host=ip
vm2 ansible_host= ip
; vm3 ansible_host= ip
β.εηοιτ.βε
  • 33,893
  • 13
  • 69
  • 83

2 Answers2

2

To select the elements of your list that do correspond to your host, you can use the inventory_hostname special variable.
Then you will have to use the selectattr and first filters of Jinja to get the corresponding list of commands.

So, your loop would become:

loop: >-
  {{ 
    (
      command_list.commands 
        | selectattr('group', '==', inventory_hostname) 
        | first
    ).commands
  }}

Another option, to make it more simple, would be to transform your commands list into a dictionary:

commands:
  vm1:
    commands:
     - command 1
     - command 2
  vm2:
    commands:
     - command 3
     - command 4

Then, accessing the commands of a host becomes trivial:

loop: "{{ command_list.commands[inventory_hostname].commands }}"

Given the task:

- debug:
    var: >-
      (
        command_list.commands
          | selectattr('group', '==', inventory_hostname)
          | first
      ).commands
  vars:
    command_list:
      commands:
        - group: vm1
          commands:
            - command 1
            - command 2
        - group: vm2
          commands:
            - command 3
            - command 4

It would yield:

ok: [vm1] => 
  ? |-
    (
      command_list.commands
        | selectattr('group', '==', inventory_hostname)
        | first
    ).commands
  : - command 1
    - command 2
ok: [vm2] => 
  ? |-
    (
      command_list.commands
        | selectattr('group', '==', inventory_hostname)
        | first
    ).commands
  : - command 3
    - command 4

Compare that to a simplified scenario with a dictionary:

- debug:
    var: command_list.commands[inventory_hostname].commands
  vars:
    command_list:
      commands:
        vm1:
          commands:
          - command 1
          - command 2
        vm2:
          commands:
          - command 3
          - command 4

Which yields:

ok: [vm1] => 
  command_list.commands[inventory_hostname].commands:
  - command 1
  - command 2
ok: [vm2] => 
  command_list.commands[inventory_hostname].commands:
  - command 3
  - command 4
β.εηοιτ.βε
  • 33,893
  • 13
  • 69
  • 83
2

Given the file for testing

shell> cat commands.yml
commands:
  - group: vm1
    commands:
     - echo command 1
     - echo command 2
  - group: vm2
    commands:
     - echo command 3
     - echo command 4

Q: "On vm1 run command 1 and command 2. On vm2 run command 3 and command 4."

Short answer: Convert the list to a dictionary and select the list of commands in the loop

    - command: "{{ item }}"
      loop: "{{ commands_dict[inventory_hostname] }}"
      vars:
        commands_dict: "{{ commands|
                           items2dict(key_name='group',
                                      value_name='commands') }}"

A: There are more options:

  1. Select the host and map the list(s) of commands
    - command: "{{ item }}"
      loop: "{{ commands|
                selectattr('group', 'eq', inventory_hostname)|
                map(attribute='commands')|flatten }}"
  1. Instead of attribute mapping, filter subelements
    - command: "{{ item.1 }}"
      loop: "{{ commands|
                selectattr('group', 'eq', inventory_hostname)|
                subelements('commands') }}"
  1. Convert the lists of commands to a dictionary
  commands_dict: "{{ commands|
                     items2dict(key_name='group',
                                value_name='commands') }}"

gives

  commands_dict:
    vm1:
    - echo command 1
    - echo command 2
    vm2:
    - echo command 3
    - echo command 4

Use the dictionary in the loop

    - command: "{{ item }}"
      loop: "{{ commands_dict[inventory_hostname] }}"

Details:

  1. Select the host and map the list(s) of commands
shell> cat pb.yml
- hosts: all

  vars_files:
    - commands.yml

  tasks:

    - command: "{{ item }}"
      register: out
      loop: "{{ commands|
                selectattr('group', 'eq', inventory_hostname)|
                map(attribute='commands')|flatten }}"

    - debug:
        msg: "{{ out.results|map(attribute='stdout') }}"

gives

shell> ansible-playbook pb.yml 

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

TASK [command] ********************************************************************************
changed: [vm1] => (item=echo command 1)
changed: [vm2] => (item=echo command 3)
changed: [vm2] => (item=echo command 4)
changed: [vm1] => (item=echo command 2)

TASK [debug] **********************************************************************************
ok: [vm2] => 
  msg:
  - command 3
  - command 4
ok: [vm1] => 
  msg:
  - command 1
  - command 2

PLAY RECAP ************************************************************************************
vm1: ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
vm2: ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 

  1. Iterate commands with subelements
shell> cat pb.yml
- hosts: all

  vars_files:
    - commands.yml

  tasks:

    - command: "{{ item.1 }}"
      register: out
      loop: "{{ commands|
                selectattr('group', 'eq', inventory_hostname)|
                subelements('commands') }}"
      loop_control:
        label: "{{ item.1 }}"

    - debug:
        msg: "{{ out.results|map(attribute='stdout') }}"

gives

shell> ansible-playbook pb.yml 

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

TASK [command] ********************************************************************************
changed: [vm2] => (item=echo command 3)
changed: [vm1] => (item=echo command 1)
changed: [vm1] => (item=echo command 2)
changed: [vm2] => (item=echo command 4)

TASK [debug] **********************************************************************************
ok: [vm1] => 
  msg:
  - command 1
  - command 2
ok: [vm2] => 
  msg:
  - command 3
  - command 4

PLAY RECAP ************************************************************************************
vm1: ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
vm2: ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

  1. Create a dictionary with the lists of the commands
shell> cat pb.yml
- hosts: all

  vars_files:
    - commands.yml

  vars:

    commands_dict: "{{ commands|
                       items2dict(key_name='group',
                                  value_name='commands') }}"

  tasks:

    - debug:
        var: commands_dict
      run_once: true

    - command: "{{ item }}"
      register: out
      loop: "{{ commands_dict[inventory_hostname] }}"

    - debug:
        msg: "{{ out.results|map(attribute='stdout') }}"

gives

shell> ansible-playbook pb.yml 

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

TASK [debug] **********************************************************************************
ok: [vm1] => 
  commands_dict:
    vm1:
    - echo command 1
    - echo command 2
    vm2:
    - echo command 3
    - echo command 4

TASK [command] ********************************************************************************
changed: [vm2] => (item=echo command 3)
changed: [vm1] => (item=echo command 1)
changed: [vm1] => (item=echo command 2)
changed: [vm2] => (item=echo command 4)

TASK [debug] **********************************************************************************
ok: [vm1] => 
  msg:
  - command 1
  - command 2
ok: [vm2] => 
  msg:
  - command 3
  - command 4

PLAY RECAP ************************************************************************************
vm1: ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
vm2: ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
Vladimir Botka
  • 58,131
  • 4
  • 32
  • 63