Inconsistent expression evaluation

  • The below expressions are valid YAML dictionaries
{"socket": "test"}
{socket: "test"}
  • They become valid YAML strings if you quote them
'{"socket": "test"}'
'{socket: "test"}'
  • When you put a substitution into the expression, given the variable
test_var: test

the below expression evaluates to a string

'{socket: "{{ test_var }}"}'

, but if the ‘key’ is quoted

'{"socket": "{{ test_var }}"}'

it evaluates to a dictionary

  • The evaluation becomes weirder when you append a comma. The string remains a string
'{socket: "{{ test_var }}"},'

, but the dictionary becomes a list with a single item [{'socket': 'test'}]

'{"socket": "{{ test_var }}"},'
  • In addition to this. If you write it to a file
    - copy:
        dest: /tmp/test.txt
        content: |
          {"socket": "{{test_var}}"},

you get a set

shell> cat /tmp/test.txt 
({'socket': 'test'},)

Is this a bug?

If it isn’t a bug can you point me to a docs that explain this?

Example of a complete playbook for testing

- hosts: localhost

  vars:

    test_var: test
  
  tasks:

    - debug:
        msg: |
          x: {{ item }}
          type(x): {{ item | type_debug }}
      loop:
        - {"socket": "test"}
        - {socket: "test"}
        - '{"socket": "test"}'
        - '{socket: "test"}'
        - '{"socket": "{{ test_var }}"}'
        - '{socket: "{{ test_var }}"}'
        - '{"socket": "{{ test_var }}"},'
        - '{socket: "{{ test_var }}"},'
1 Like

This isn’t a bug, it’s just a side effect of how templating works, and the implementation historically necessary to get jinja2 to be able to provide python data types, instead of just strings. See:

2 Likes

For the record.

Jinja2 evaluates a substitution in an expression. Ansible does some magic to try and convert strings that look like Python data structures to Python data structures. This undocumented behavior of a substitution may cause unpredicted malfunctions. For example, the below task

    - debug:
        msg: "{{ s }} {{ s | type_debug }}"
      vars:
        s: '{"foo"},'

gives

msg: '{"foo"}, AnsibleUnicode'

The substitution

x: foo

and the task

    - debug:
        msg: "{{ s }} {{ s | type_debug }}"
      vars:
        x: foo
        s: '{"{{ x }}"},'

gives

msg: ({'foo'},) tuple

Notes:

  1. Module copy vs. module template
    - copy:
        dest: /tmp/test.txt
        content: |
          {"{{ x }}"},
      vars:
        x: foo

gives

shell> cat /tmp/test.txt
({'foo'},)

In the option content, the docs says:

… if content contains a variable, use the ansible.builtin.template module.

shell> cat test.txt.j2 
{"{{ x }}"},
    - template:
        src: test.txt.j2
        dest: /tmp/test.txt
      vars:
        x: foo

gives

shell> cat /tmp/test.txt
{"foo"},
1 Like

Thank you. I think, this should be documented. In Manipulating data perhaps?

2 Likes

I’ve always been bothered by this admonition. There are plenty of instances where the result of using content: containing variables is exactly what’s desired.

It would be better I think to describe the difference and dangers between real templates and sneaking templated text into content: values, or at least point to a discussion of same. (Previous messages in this topic would be a good start.) Then the user can make an informed decision.

Not that it’s bad advice. It isn’t. But without further discussion it’s like, “Here be dragons!” on old maps. “Pray, tell us more about these dragons…”

2 Likes

Technically, there is no reason why copy.content doesn’t implement the same functionality template.src does.

The problem isn’t the copy action, but what ansible does with content before passing it to the copy action. With Data Tagging we’re finally a big step closer to getting rid of the need to use the template module for some inputs.

You should really try out the Data Tagging preview: Data Tagging preview and testing