SaintRough
SaintRough

Reputation: 61

'gather_facts' seems to break 'set_fact' and 'hostvars'

I am using set_fact and hostvars to pass variables between plays within a playbook. My code looks something like this:

- name: Staging play
  hosts: localhost
  gather_facts: no
  vars_prompt:
    - name: hostname
      prompt: "Enter hostname or group"
      private: no
    - name: vault
      prompt: "Enter vault name"
      private: no
    - name: input
      prompt: "Enter input for role"
      private: no
  tasks:
    - set_fact:
        target_host: "{{ hostname }}"
        target_vault: "{{ vault }}"
        for_role: "{{ input }}"

- name: Execution play
  hosts: "{{ hostvars['localhost']['target_host'] }}"
  gather_facts: no
  vars_files:
    - "vault/{{ hostvars['localhost']['target_vault'] }}.yml"
  tasks:
    - include_role:
        name: target_role
      vars:
        param: "{{ hostvars['localhost']['for_role'] }}"

This arrangement has worked without issue for months. However, our environment has changed and now I need to take a timestamp and pass that to the role as well as the other variable, so I made the following changes (denoted by comments):

- name: Staging play
  hosts: localhost
  gather_facts: yes # Changed from 'no' to 'yes'
  vars_prompt:
    - name: hostname
      prompt: "Enter hostname or group"
      private: no
    - name: vault
      prompt: "Enter vault name"
      private: no
    - name: input
      prompt: "Enter input for role"
      private: no
  tasks:
    - set_fact:
        target_host: "{{ hostname }}"
        target_vault: "{{ vault }}"
        for_role: "{{ input }}"
        current_time: "{{ ansible_date_time.iso8601 }}" # Added fact for current time

- name: Execution play
  hosts: "{{ hostvars['localhost']['target_host'] }}"
  gather_facts: no
  vars_files:
    - "vault/{{ hostvars['localhost']['target_vault'] }}.yml"
  tasks:
    - include_role:
        name: target_role
      vars:
        param: "{{ hostvars['localhost']['for_role'] }}"
        timestamp: "{{ hostvars['localhost']['current_time'] # Passed current_time to 
        Execution Play via hostvars

Now, when I execute, I get the error that the vault hostvars variable is undefined in the Execution Play. After some experimenting, I've found that setting gather_facts: yes in the Staging Play is what is triggering the issue.

However, I need gather_facts enabled in order to use ansible_time_date. I've already verified via debug that the facts are being recorded properly and can be called by hostvars within the Staging Play; just not in the following Execution Play. After hours of research, I can't find any reasoning for why gathering facts in the Staging Play should affect hostvars for the Execution Play or any idea on how to fix it.

At the end of the day, all I need is the current time passed to the included role. Anyone who can come up with a solution that actually works in this use case wins Employee of the Month. Bonus points if you can explain the initial issue with gather_facts.

Thanks!

Upvotes: 1

Views: 842

Answers (2)

U880D
U880D

Reputation: 12009

... if you can explain the initial issue with gather_facts ... Any insight in this regard would be appreciated.

This is caused by variable precedence and because Ansible do not "overwrite or set a new value" for a variable. So it will depend on when and where they become defined.

You may test with the following example

---
- hosts: localhost
  become: false

  gather_facts: false

  tasks:

  - name: Show Gathered Facts
    debug:
      msg: "{{ hostvars['localhost'].ansible_facts }}" # will be {} only 

  - name: Gather date and time only
    setup:
      gather_subset:
        - 'date_time'
        - '!min'

  - name: Show Gathered Facts
    debug:
      msg: "{{ ansible_facts }}" # from hostvars['localhost'] again

and "try to break it" by adding

  - name: Set Fact
    set_fact:
      ansible_date_time:
        date: '1970-01-01'

  - name: Show Facts
    debug:
      msg: "{{ hostvars['localhost'] }}"

Just like to note that for your use case you should use

  gather_subset:
     - 'date_time'
     - '!min'

since your are interested in ansible_date_time only. See what is the exact list of Ansible setup min?.

Be also aware of caching facts since "When created with set_facts’s cacheable option, variables have the high precedence in the play, but are the same as a host facts precedence when they come from the cache."

Upvotes: 1

SaintRough
SaintRough

Reputation: 61

So, I had to reinvent the wheel a bit, but came up with a much cleaner solution. I simply created a default value for a timestamp in the role itself and added a setup call for date/time at the appropriate point, conditional on there being no existing value for the variable in question.

- name: Gather date and time.
  setup:
    gather_subset: date_time
  when: timestamp is undefined and ansible_date_time is undefined

I was able to leave gather_facts set to no in the dependent playbook but I still have no idea why setting it to yes broke anything in the first place. Any insight in this regard would be appreciated.

Upvotes: 1

Related Questions