Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update_fact will prevent variable replacement under certain conditions #135

Open
ipc-zpg opened this issue Jan 31, 2022 · 5 comments
Open
Assignees

Comments

@ipc-zpg
Copy link

ipc-zpg commented Jan 31, 2022

SUMMARY

under certain conditions, update_facts will prevent variables being substituted within variables it manipulates

ISSUE TYPE
  • Bug Report
COMPONENT NAME

ansible.utils.update_fact

ANSIBLE VERSION
ansible [core 2.11.4]
  config file = /Users/pookey/.ansible.cfg
  configured module search path = ['/Users/pookey/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /opt/homebrew/Cellar/ansible/4.5.0/libexec/lib/python3.9/site-packages/ansible
  ansible collection location = /Users/pookey/.ansible/collections:/usr/share/ansible/collections
  executable location = /opt/homebrew/bin/ansible
  python version = 3.9.7 (default, Sep  3 2021, 04:31:11) [Clang 12.0.5 (clang-1205.0.22.9)]
  jinja version = 3.0.1
  libyaml = True
COLLECTION VERSION
ansible-galaxy collection list ansible.utils

# /opt/homebrew/Cellar/ansible/4.5.0/libexec/lib/python3.9/site-packages/ansible_collections
Collection    Version
------------- -------
ansible.utils 2.4.0

# /Users/pookey/.ansible/collections/ansible_collections
Collection    Version
------------- -------
ansible.utils 2.4.3
CONFIGURATION

OS / ENVIRONMENT

Tested on OSX and Linux

STEPS TO REPRODUCE
---

- hosts: localhost
  vars:
    env_name: moo
    datadog_checks:
      mysql:
        - host: "{{ env_name }}.moo"
      codedeploy:
        logs: []
  tasks:
    - debug:
        var: datadog_checks.mysql[0].host
    - name: update fact
      ansible.utils.update_fact:
        updates:
          - path: "datadog_checks['codedeploy']['logs']"
            value: "{{ datadog_checks['codedeploy']['logs'] + ['/path/to/logfile'] }}"
      register: new_dd
    - name: replace the datadog_checks array
      set_fact:
        datadog_checks: "{{ new_dd.datadog_checks }}"
    - debug:
        var: datadog_checks.mysql[0].host
EXPECTED RESULTS

the second debug should print 'moo.moo', in the same way the first debug does.

ACTUAL RESULTS

I get {{ env_name }}.moo as the output.

❯ ansible-playbook ./test.yaml
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

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

TASK [Gathering Facts] ***********************************************************************************************************************************************************
ok: [localhost]

TASK [debug] *********************************************************************************************************************************************************************
ok: [localhost] => {
    "datadog_checks.mysql[0].host": "moo.moo"
}

TASK [update fact] ***************************************************************************************************************************************************************
changed: [localhost]

TASK [replace the datadog_checks array] ******************************************************************************************************************************************
ok: [localhost]

TASK [debug] *********************************************************************************************************************************************************************
ok: [localhost] => {
    "datadog_checks.mysql[0].host": "{{ env_name }}.moo"
}

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


@ipc-zpg ipc-zpg changed the title update_fact will not result variables under certain conditions update_fact will not replace variables under certain conditions Jan 31, 2022
@ipc-zpg ipc-zpg changed the title update_fact will not replace variables under certain conditions update_fact will prevent variable replacement under certain conditions Jan 31, 2022
@cidrblock
Copy link
Collaborator

@ipc-zpg Thanks for this, it took a few minutes to remember the subtleties of vars vs. facts in this case.

This is not entirely unexpected, at the time the update_fact task is run the datadog_checks variable has not had its references resolved yet. This can be seen in the following playbook:

- hosts: localhost
  gather_facts: false
  vars:
    env_name: moo
    datadog_checks:
      mysql:
        - host: "{{ env_name }}.moo"
      codedeploy:
        logs: []
  tasks:
    - name: Show delayed resolution of variables
      set_fact:
        env_name: A cow says
  
    - name: What does a cow say?
      debug:
        var: datadog_checks

TASK [What does a cow say?] ******************************************************************************************************************
ok: [localhost] => {
    "datadog_checks": {
        "codedeploy": {
            "logs": []
        },
        "mysql": [
            {
                "host": "A cow says.moo"
            }
        ]
    }
}

The workaround for this is to resolve all the references in datadog_checks by setting a fact referring to it, and updating that:

---
- hosts: localhost
  gather_facts: false
  vars:
    env_name: moo
    datadog_checks:
      mysql:
        - host: "{{ env_name }}.moo"
      codedeploy:
        logs: []
  tasks:

    - name: Set a fact, this will resolve references
      set_fact:
        resolved_datadog: "{{ datadog_checks }}"

    - name: Update the fact
      ansible.utils.update_fact:
        updates:
          - path: "resolved_datadog['codedeploy']['logs']"
            value: "{{ resolved_datadog['codedeploy']['logs'] + ['/path/to/logfile'] }}"
      register: updated_datadog
    
    - name: Show the new fact
      ansible.builtin.debug:
        var: updated_datadog

    - name: Replace the datadog_checks array
      set_fact:
        datadog_checks: "{{ updated_datadog.resolved_datadog }}"
    - debug:
        var: datadog_checks.mysql[0].host

TASK [debug] *********************************************************************************************************************************
ok: [localhost] => {
    "datadog_checks.mysql[0].host": "moo.moo"
}

In your example, ansible passes the unresolved var to the task, so that is what is being returned.

Let me know if this helps

-Brad

@cidrblock cidrblock added the waiting_on_author Waiting on the issue author to confirm label Apr 13, 2022
@cidrblock
Copy link
Collaborator

@ipc-zpg Just checking in to see if ^^^ helped explain the behaviour

@alice-rc
Copy link

alice-rc commented Feb 2, 2023

So I have another example. It seems that if the path includes a variable representing a number, it also will not work.

This works fine:

- ansible.utils.update_fact:
    updates:
      - path: "vm_info['value']['cdroms']['3000']['backing']['type']"
        value: CLIENT_DEVICE

This works fine:

- ansible.utils.update_fact:
    updates:
      - path: "vm_info['value']['cdroms']['3000']['backing'][{{ lastpart }}]"
        value: CLIENT_DEVICE
  vars:
    lastpart: type

This fails:

- ansible.utils.update_fact:
    updates:
      - path: "vm_info['value']['cdroms'][{{ cdrom_id }}]['backing']['type']"
        value: CLIENT_DEVICE
  vars:
    cdrom_id: '3000'

TASK [ansible.utils.update_fact] ***********************************************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: KeyError: 3000
fatal: [XXX06-XX1-XXX29]: FAILED! => {"changed": false, "msg": "Error: the key '3000' was not found in {'3000': {'start_connected': False, 'backing': {'auto_detect': True, 'device_access_type': 'EMULATION', 'type': 'HOST_DEVICE'}, 'allow_guest_control': True, 'ide': {'master': True, 'primary': True}, 'label': 'CD/DVD drive 1', 'state': 'NOT_CONNECTED', 'type': 'IDE'}}."}

@Rockawear
Copy link

I'm hitting this bug, any workaround?

@KB-perByte KB-perByte removed the waiting_on_author Waiting on the issue author to confirm label Feb 28, 2024
@danielleshoemake
Copy link

I also have the issue of not being able to use a variable that is set to equal an index number, but in my case, the error seems to indicate that update_fact interprets all variables as strings.

This works

- ansible.utils.update_fact:
    updates:
      - path: installation_media["{{sqlserver_version}}"][0]['product_id']
        value: ''

These both fail with the same error

- ansible.utils.update_fact:
    updates:
      - path: installation_media["{{sqlserver_version}}"]["{{idx}}"]['product_id']
        value: ''
  vars:
    idx: 0

- ansible.utils.update_fact:
    updates:
      - path: installation_media["{{sqlserver_version}}"]["{{idx | int}}"]['product_id']
        value: ''
  vars:
    idx: 0

The error

The error was: TypeError: list indices must be integers or slices, not str
FAILED! => {"changed": false, "msg": "Error: the key '0' was not found in....

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants