Dynamically passing a json as a parameter in a "command" task

I have this task in my playbook:

- name: Update instance tags
    command: oci compute instance update -c {{ compartment }} --freeform-tags {{ tag_var_json }}

According to the oracle documentation for this command, the parameter --freeform-tags accepts a json that represents the key-value pair for the tag. I need to have this json be created dynamically in the playbook itself, so prior to running that task, I have this one for testing purposes:

  - name: Create a json object to use as tag
    set_fact:
      tag_var: '{ "test": "thisisatest" }'
    set_fact:
      tag_var_json: "{{ tag_var | to_json }}"

But I must be doing something wrong because I keep getting this error:
fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'tag_var' is undefined

Is there an easier way of creating a json directly in the playbook and passing it as an argument to that parameter?

Thank you.


Solution 1:

There are two things going on here. The first is that you have created YAML that is accepted by the parser, but behaves in a slightly unexpected way (and will produce a warning in the current version of Ansible.)

  - name: Create a json object to use as tag
    set_fact:
      tag_var: '{ "test": "thisisatest" }'
    set_fact:
      tag_var_json: "{{ tag_var | to_json }}"

Keys in YAML are unique; when the parser encounters a second instance of the same key it throws away the first one. Since you've repeated set_fact, this is equivalent to:

  - name: Create a json object to use as tag
    set_fact:
      tag_var_json: "{{ tag_var | to_json }}"

Correcting the syntax error will still result in a failure, however.

  - name: Create a json object to use as tag
    set_fact:
      tag_var: '{ "test": "thisisatest" }'
      tag_var_json: "{{ tag_var | to_json }}"

The arguments to set_fact have to be templated before the task runs, at which point tag_var is still undefined (because this task is defining it.)

One correct way to write this task is as two separate tasks:

  - name: Create a tag object
    set_fact:
      tag_var:
        test: thisisatest

  - name: Create a JSON string for tagging
    set_fact: 
      tag_var_json: "{{ tag_var | to_json }}"

However, set_fact isn't required at all. You can just set the var directly on the task where you use it, which is both more efficient and makes it more tightly scoped.

- name: Update instance tags
    command: oci compute instance update -c {{ compartment }} --freeform-tags "{{ tag_var | to_json }}"
  vars:
    tag_var:
      test: thisisatest