Definition

In Ansible lookup plugins are Ansible-specific extensions to the Jinja2 templating engine that are used to get some information on the node that is the controller (usually your PC) Examples of lookup plugins:

  • community.hashi_vault.vault_kv2_get — to get the value of a key from Hashicorp Vault
  • ansible.builtin.file — to read a file
  • ansible.builtin.env — the value of an environment variable

Here is the full list of lookup plugins from ansible-galaxy

using delegate_to will not allow the lookup plugin to be executed on a managed node, it can only be executed on a controller node

Let me give you a very simple example: let’s display the contents of the environment variable $HOME on my computer:

---
- hosts: localhost
  become: false
  gather_facts: false
  tasks:
    - name: Display $HOME
      ansible.builtin.debug:
        msg: "{{ lookup('env', 'HOME') }}"

Result:

TASK [Display $HOME]
ok: [localhost] =>
  msg: /home/alexander

Interpolation

However, the logic of lookup differs from the usual logic of modules, because their definition occurs at the moment of interpolation:

---
- hosts: localhost
  become: false
  gather_facts: false
  vars:
    current_time: "{{ lookup('pipe','date +%H:%M:%S') }}"
  tasks:
    - name: Display time 1/2
      ansible.builtin.debug:
        msg: Current time {{ current_time }}

    - name: Pause for 3 seconds
      ansible.builtin.pause:
        seconds: 3

    - name: Display time 2/2
      ansible.builtin.debug:
        msg: Current time is {{ current_time }}

In the example above, it may seem that both tasks will output the same result, but as I said, the result of the work will be obtained at the moment of interpolation:

TASK [Display current time 1/2]
ok: [localhost] =>
  msg: Current time 21:21:44

TASK [Pause for 3 seconds]
Pausing for 3 seconds
(ctrl+C then 'C' = continue early, ctrl+C then 'A' = abort)
ok: [localhost]

TASK [Display current time 2/2]
ok: [localhost] =>
  msg: Current time is 21:21:47

until:

Another interesting example of working together with the until: directive (let me remind you that until: allows you to restart the module until you get the desired result)

Let’s assume that we need to generate a UUID that starts with zero and we wrote a simple play like this:

---
- hosts: localhost
  become: false
  gather_facts: false
  tasks:
    - name: Generate random UUID
      ansible.builtin.set_fact:
        random_uuid: "{{ lookup('pipe', 'uuidgen') }}"
      until: random_uuid.startswith('0')
      delay: 1
      retries: 20

At first glance, everything looks correct: we get the random_uuid value from the lookup plugin and then check if it starts with zero, but in fact the random_uuid value will be obtained once during the first interpolation of the plugin and then until: will check the same value

Ansible UUID Wrong

How to bypass this limitation? The is no way, you can’t combine until:+lookup and “catch” the resulting value, the lookup value is generated once before the module itself is executed, the best thing to do in this case is to select the right plugin (or write it yourself)

You can achieve correct operation of the until:+lookup condition by substituting the lookup plugin directly into the condition, but you still won’t be able to get the desired value, since the set_fact module will store the value it received at the time of the call (see picril)

---
- hosts: localhost
  become: false
  gather_facts: false
  tasks:
    - name: Generate random UUID
      vars:
        _random_uuid: "{{ lookup('pipe', 'uuidgen') }}"
      ansible.builtin.set_fact:
        random_uuid: "{{ _random_uuid }}"
      until: _random_uuid.startswith('0')
      delay: 1
      retries: 20

Ansible UUID OK