Skip task if the host name does not appear as key in a dict

The use case is to install SSH public keys on a number of servers. The hosts, users and the files containing the public keys are specified in a variable. Using a list of dictionaries it works. As an exercise the variable is changed into a dictionary of lists of dictionaries, resulting in a compact and more readable definition.However, I can’t find a way to skip the task if the host is not mentioned in the variable.

The version which works looks like:

---
- name: Install SSH public keys
  hosts: hostA,hostB

  vars:
    key_distribution:
    - host: HostA
      user: user0
      file: key.source0.pub
    - host: HostA
      user: user0
      file: key.source1.pub

  tasks:  
  - name: Install public SSH key 
    authorized_key:
      user: "{{ item.user }}"
      key:  "{{ lookup('file','/some/path/' + item.file ) }}"
      state: present 
    loop: "{{ key_distribution | selectattr('host','==',inventory_hostname_short) | list }}"

When run, the counter ‘skipped’ in the summary is 1 for hostB and no error is reported.

As an exercise variable key_distribution is reworked to a dictionary using the host name as a key. It results in a less cluttered definition of the variable. The playbook is now:

---
- name: Install SSH public keys
  hosts: hostA,hostB

  vars:
    key_distribution:
      HostA:
      - user: user0
        file: key.source0.pub
      - user: user0
        file: key.source1.pub

  tasks:  
  - name: Install public SSH key 
    authorized_key:
      user: "{{ item.user }}"
      key:  "{{ lookup('file','/some/path/' + item.file ) }}"
      state: present 
   with_items: "{{ [inventory_hostname_short] | map('extract',key_distribution) | list }}"
   when: "inventory_hostname_short in key_distribution"
#  when: "{%if inventory_hostname_short in key_distribution %}true{%else%}false{%endif%}"
#  when: key_distribution|selectattr(inventory_hostname_short,"defined")|list|count >0

It runs fine for hostA, but stops with error message

  KeyError: 'hostB'

when the task is run for hostB. However, the summary shows both "failed=1’ and ‘skipped=1’ for hostB.
In the comment lines a few other when-clauses are shown, which also fail to skip the task for hostB without an error message.

How can one skip this task for hostB, not knowing in advance which hosts will be mentioned in variable key_distribution?

I think the issue is here

with_items: "{{ [inventory_hostname_short] | map('extract',key_distribution) | list }}"

its essentially:

"{{ key_distribution[inventory_hostname_short] }}"

when clauses on loops are evaluated per item in the loop. So your when clause is checked after ansible tries to run the code above. Hence the key error

The task could be re-written as

  - name: Install public SSH key 
    authorized_key:
      user: "{{ item.user }}"
      key:  "{{ lookup('file','/some/path/' + item.file ) }}"
      state: present 
   with_items: "{{ key_distribution[inventory_hostname_short] | default([]) }}"

I believe you can drop the when clause completely this way too, but i may be wrong.

As an alternative to all of this, you could use host vars.
https://docs.ansible.com/ansible/latest/inventory_guide/intro_inventory.html#splitting-out-vars

Heres an example for hostA, use your imagination for hostB…

#host_vars/hostA.yml
ssh_keys:
  - user: blah
    file: foo

Then this becomes trivial

---
- name: Install SSH public keys
  hosts: hostA,hostB
  tasks:  
  - name: Install public SSH key 
    authorized_key:
      user: "{{ item.user }}"
      key:  "{{ lookup('file','/some/path/' + item.file ) }}"
      state: present 
    loop: "{{ ssh_keys | default([]) }}"

Thanks very much. Your solution, that is:

works perfectly. One of the things I learned from this is to try to look at a problem in a different way: instead of trying to skip some hosts (typically using a when-clause) the hosts to be skipped are fed with nothing, that is an empty list. I could have learned that from the initial, working version.

1 Like

This alternative solution is now implemented. It works perfectly, thanks for the pointer.

1 Like