Using a variable from one play across other plays

With anchors no need to have any tasks at all. This will also work:

---
- name: Testing Bitwarden lookups
  hosts: localhost
  gather_facts: false
  tasks: []
  vars:
    global_tasks:
    - &set_fact_bitwarden
      name: Get Bitwarden password for badmin lab user
      ansible.builtin.set_fact:
        ansible_become_pass: >-
          {{ lookup('community.general.bitwarden', 'badmin - Lab User', field='password')[0] }}
        delegate_to: localhost

- name: Test using Bitwarden varaible
  hosts: ansible
  gather_facts: false
  tasks:
    - *set_fact_bitwarden

    - name: Check if `/etc/profile.d/ps1.sh` exists
      ansible.builtin.stat:
        path: /etc/profile.d/ps1.sh
      become: true
      register: stat_ps1_sh

There will be no LSP highlights as “global task” is not a task from ansible point of view

Anchor just copies the content from one place to another.
I did not want to make a huge jump right to this final solution on the first post as it might confuse people.

Well yes and no… Anchors work just inside a single yaml file, as they are yaml objects. When ansible reads tihs file it is as if you would copy and paste the same code at multiple places.

Having this code at multiple places means it would be execuded MULTIPLE TIMES. This might not be desired, especially when running on an inventory with thousands of hosts - you do not want (not need) to re-execute this task for each play!

My suggested approach eecutes this lookup just a single time for entire playbook file regardless of number of hosts. It assigns a set_fact level variable to all hosts and it is visible in ALL following plays without the need to re-execute it.

I know that anchors might get handy in some places, but for me this is a quick and dirty hack to avoid copy/paste same code within same file. I however do not like the idea to have same code in one file at multiple places in the first place…

Hi Bartowl, I’m having a bit of trouble envisioning what’s different from your suggestion and my original post (and what I’ve done wrong in that context). Ideally, I’m not installing Bitwarden and syncing that everywhere, I want to do the lookup from the place I’m running the playbook. The sync is a manual process from the CLI tool, and I have to grab a session key once I log in. Originally, I was running this I think as you described, except I was delegating it and having trouble.

I definitely didn’t really like that this was running more than once.

You can use run_once in the play targeting your ansible_test_vm group. It will run once and you can access the fact directly instead of through hostvars:

- name: Use Bitwarden pass from task
  hosts: ansible_test_vm
  tasks:
    - name: 'Get password for "badmin - Lab User" from Bitwarden'
      set_fact:
        bw_data: >-
          {{ lookup('community.general.bitwarden', 'badmin - Lab User', field='password')[0] }}
        cacheable: True
      run_once: True

    - name: Check if `/etc/profile.d/ps1.sh` exists
      ansible.builtin.stat:
        path: /etc/profile.d/ps1.sh
      become: true
      register: stat_ps1_sh

  vars:
    ansible_become_pass: "{{ bw_data | default(omit) }}"

Originally, I was running this I think as you described, except I was delegating it and having trouble.

Your error was unrelated to delegation, it was caused by how you defined vars:. In this example I used | default(omit), but see my comment above for other options to avoid that issue (regardless of delegation).

Hi Shertel, I don’t want to run the play to get the password against the same host as I do other things. I either have to delegate it or have a play to grab it. It’s always worked just fine if I do everything on the same host as I grab the password.

Just add delegate_to: localhost on that task in my example.

It’s always worked just fine if I do everything on the same host as I grab the password.

Without run_once it runs on every host you target. With run_once it runs once and you can access the fact without using hostvars.

I swear I did that before (before I tried the variable thing in the original post) but maybe I had a typo or something.

So now I have this (what you recommended) and it works.

- name: Test using Bitwarden varaible
  hosts: ansible_test_vm
  gather_facts: false
  tasks:
    - name: Get Bitwarden password for badmin lab user
      ansible.builtin.set_fact:
        bw_data: >-
          {{ lookup('community.general.bitwarden', 'badmin - Lab User', field='password')[0] }}
        cacheable: true
      delegate_to: localhost

    - name: Check if `/etc/profile.d/ps1.sh` exists
      ansible.builtin.stat:
        path: /etc/profile.d/ps1.sh
      become: true
      register: stat_ps1_sh

  vars:
    ansible_become_pass: "{{ bw_data | default(omit) }}"

If I have some other play further down that targets some other group of VMs for some reason, would I then have to run it again on that new group of hosts? I assumed something like the below would be cleaner since I’d only do it a single time but the variable isn’t persistent for the second play.

- name: Testing Bitwarden lookups
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Get Bitwarden password for badmin lab user
      ansible.builtin.set_fact:
        bw_data: >-
          {{ lookup('community.general.bitwarden', 'badmin - Lab User', field='password')[0] }}
        cacheable: true
      run_once: true

- name: Test using Bitwarden varaible
  hosts: ansible
  gather_facts: false
  tasks:
    - name: Check if `/etc/profile.d/ps1.sh` exists
      ansible.builtin.stat:
        path: /etc/profile.d/ps1.sh
      become: true
      register: stat_ps1_sh

  vars:
    ansible_become_pass: "{{ bw_data | default(omit) }}"

A few clarifications:

  • set_fact runs on the controller FOR the inventory_hostname, not on the hosts, this is true for many actions like ‘debug’, see the ‘attributes’ section, anything not using the connection should behave this way
  • run_once has several side effects, one of them is to apply results to all hosts
  • the result of set_fact is to set a variable on a host .. so it kind of does run set_fact for every host, you just skip the parts that show in the callback
  • when using this strategy you are making N copies of the variables (N == number of hosts), when using the localhost/hostvars[‘localhost’] you only have 1 copy of the variable
2 Likes

That is not what I suggested, and I don’t know why it would work unless localhost is the only host in that group. You asked how run_once was different from your original post, so my example was just a demonstration of how to use it, not a recommendation per se.

If you want one copy of the variable or want that variable accessible for other plays, I’d write it as:

- name: Use Bitwarden pass from task
  hosts: ansible_test_vm
  tasks:
    # You could move this to its own play targeting localhost and remove delegate_to and delegate_facts if you want.
    - name: 'Get password for "badmin - Lab User" from Bitwarden'
      set_fact:
        bw_data: >-
          {{ lookup('community.general.bitwarden', 'badmin - Lab User', field='password')[0] }}
        cacheable: True
      delegate_to: localhost
      delegate_facts: True

    - name: Check if `/etc/profile.d/ps1.sh` exists
      ansible.builtin.stat:
        path: /etc/profile.d/ps1.sh
      become: true
      register: stat_ps1_sh

  vars:
    ansible_become_pass: "{{ hostvars['localhost']['bw_data'] | default(omit) }}"  # You were missing "| default(omit)" in the original example (although IMO this is a bug in ansible-core)

- hosts: ansible
  vars:
    ansible_become_pass: "{{ hostvars['localhost']['bw_data'] }}"  # Note that "| default(omit)" is no longer necessary since the fact was defined in an earlier play that always runs
2 Likes

That makes sense. Thanks a lot, I appreciate it!

1 Like

Yes, you are right. To be honest I forgot that inside one playbook set_fact is visible in different plays. Scope of what is available where is not good documented for ansible.
There are still use case where one would want to have lookup to be executed for each host, for instance when each host has different credentials.

1 Like