Nested facts get clobbered with an update to child fact

I am testing on both 1.9.4 and 2.1 and seeing the exact same behavior.

We have a fact.d generator that creates a tree of facts under ansible_local. We use this to set defaults, but if during playbook execution it needs to update a default, it clobbers the entire ansible_local parent fact. I created a test playbook to replicate the same issue.


  • hosts: localhost
    connection: local
    vars:
    my_roles:
    info:
    hardware: “CPU info”
    cmdb:
    local: [{‘name’:‘blah’}]
    remote: False

tasks:

  • name: set new facts
    set_fact:
    args:
    test_dict: {‘name’: meh’}

  • name: test
    set_fact:
    args:
    my_roles:
    cmdb:
    local: “{{ my_roles.cmdb.local + ([test_dict]|list) }}”
    remote: True

  • debug: var=my_roles

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

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

TASK [set facts] ***************************************************************
ok: [localhost]

TASK [test] ********************************************************************
ok: [localhost]

TASK [debug] *******************************************************************
ok: [localhost] => {
“my_roles”: {
“cmdb”: {
“local”: [
{
“name”: “blah”
},
{
“name”: “meh”
}
],
“remote”: true
}
}
}

PLAY RECAP *********************************************************************
localhost : ok=4 changed=0 unreachable=0 failed=0

As you can see, the playbook clobbers the parent my_roles and updates with whatever is in the set_fact task. I have read through various documentation links, and searched around but have not seen anyone come across this specifically. I understand that ansible uses everything as a string, so taking a fact and then updating obliterates the original fact, but how does one update a nested fact in Ansible?

I'm no authority, for instance I've not switched to using 2.x versions of Ansible since I got an essentially functional setup, but I'll chip in and say I *think* this is normal behaviour when variable scopes are composed.

I used to try and nest role variables into hierarchies but a long time ago decided this was simply too fraught when composing them (and this not even using set_fact). I started treating them as globals (albeit "with structure"), and naming them with prefixes accordingly: replace underscores for dots. This resolved my problems at the time.

If it wasn't or is no longer the case, I'd be interested to hear about it, I'm not aware of anything in the docs which describe scope composition rules in this level of detail.

N