How to gather localhost facts in Ansible while gather_facts is set to "no" for all hosts in Ansible?

Viewed 2756

My playbook uses routers as the hosts to perform tasks on. I have disabled facts for the hosts but I need to get access to ansible_date_time from the local host that I'm running the playbook on. The local host is an Ubuntu VM.

This is what my playbook looks like:

---
- hosts: lab
  gather_facts: no

  tasks:   
    - name: Run block tasks
      delegate_to: 127.0.0.1
      block:
        - name: Get cert serial number using OpenSSL
          shell: |
            openssl s_client -connect {{ inventory_hostname }}:50051 2>/dev/null  | sed -n -e '/BEGIN\ CERTIFICATE/,/END\ CERTIFICATE/ p' |openssl x509 -noout -serial  | cut -d'=' -f2 | sed -e 's/\(.*\)/\L\1/'
          register: serialNum
      
        - name: Print Serial Numbers
          debug:
            msg: "{{ serialNum.stdout_lines }}"

    - name: Ansible fact - ansible_date_time
      # gather_facts: yes
      delegate_to: 127.0.0.1
      debug:
        var: ansible_date_time.date

I can't put gather_facts: yes in the last task since that errors out.

If I enable gather_facts: yes at the play level then I get the facts of the routers which is not what I want.

Running the playbook as above gives me the following message:

TASK [Ansible fact - ansible_date_time] ***************************************************************************************************************************************************
ok: [router1.mgt.net] => {
    "ansible_date_time.date": "VARIABLE IS NOT DEFINED!"
}

Is this possible to do with Ansible?

4 Answers

The task setup can be used for this. This is actually the task called behind the scene, by Ansible, when you have gather_facts: yes.

For this, you don't even need to gather all facts, you can do with a minimal subset of them, with the parameter gather_subset.

Given the playbook:

- hosts: localhost
  gather_facts: no

  tasks:
    - setup:
        gather_subset:
          - 'min'
    - debug:
        var: ansible_date_time.date

This will yield the recap:

PLAY [localhost] *************************************************************************************************

TASK [setup] *****************************************************************************************************
ok: [localhost]

TASK [debug] *****************************************************************************************************
ok: [localhost] => 
  ansible_date_time.date: '2021-07-08'

PLAY RECAP *******************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

You don't have to run setup if you need the date only. Simply format strftime. This will give you the current date (and time) without setup, e.g.

    - debug:
        msg: "{{ '%Y-%m-%d'|strftime }}"

gives

  msg: '2021-07-08'

Why does it work? It would be very awkward having to run a setup always when the current time is needed, e.g.

    - debug:
        msg: "{{ '%H:%M:%S'|strftime }}"
    - wait_for:
        timeout: 3
    - debug:
        msg: "{{ '%H:%M:%S'|strftime }}"

gives

  msg: '22:27:07'
  msg: '22:27:11'

I'm not 100% sure I got your use case but here is my take.

Before going further, a few notes.

  • Delegating to 127.0.0.1 is globally a bad practice as it might use ssh rather than the local connection plugin and force you to define a host in your inventory rather than using the implicit localhost
  • We need to gather facts from localhost to get its ansible_date_time info. We will do this in a separate play and assign the value as a play var in the next one for ease of use. There are many other ways to deal with this but the bottom line is the use of the hostvars magic variable to get a fact from a specific host.
  • If you find gathering the full set of facts from localhost is too resource intensive, you can mix my proposition with the one from @β.εηοιτ.βε

Here is how I would fix your playbook (not fully tested)

---
- name: Gather facts from localhost for later use
  hosts: localhost
  # If facts gathering is disabled in ansible.cfg you will
  # have to turn it on explicitly (i.e. `gather_facts: true`)
  

- name: Do the actual work on lab routers
  hosts: lab
  gather_facts: no

  vars:
    # Get current date from localhost in a play var
    current_date: "{{ hostvars['localhost'].ansible_date_time.date }}"

  tasks:   
    - name: Get cert serial number using OpenSSL
      shell: |
        openssl s_client -connect {{ inventory_hostname }}:50051 2>/dev/null  | sed -n -e '/BEGIN\ CERTIFICATE/,/END\ CERTIFICATE/ p' |openssl x509 -noout -serial  | cut -d'=' -f2 | sed -e 's/\(.*\)/\L\1/'
      register: serialNum
      delegate_to: localhost
      
    - name: Print Serial Numbers
      debug:
        msg: "{{ serialNum.stdout_lines }}"

    - name: Show date from localhost gathered at very beginning
      debug:
        var: current_date

Perhaps you can use a combination of the built-in date command and the ansible.builtin.command module? For example, I was able to get the date in YYYY-mm-dd format using the following playbook:

- connection: local
  hosts: all
  gather_facts: false
  tasks:
    - name: Get the current date
      ansible.builtin.command: date +%Y-%m-%d
      changed_when: false
      register: date
    - debug:
        var: date.stdout

Of course, you can always change the output format of the date command.

Related