Home > Back-end >  How to prevent ansible from parsing string as json?
How to prevent ansible from parsing string as json?

Time:06-30

I am putting labels on a container with ansible. The labels I want (in docker compose syntax) are as follows:

labels:
  com.datadoghq.ad.check_names: '["portainer"]'
  com.datadoghq.ad.init_configs: '[{}]'
  com.datadoghq.ad.instances: '[{"host": "%%host%%","port":"9999"}]'

Note that the labels are pure strings that contain some json-like formatting.

Now to do this with ansible, my naïve syntax was this:

- docker_container:
    name: portainer
    # ....
    labels:
      com.datadoghq.ad.check_names: "[\"portainer\"]"
      com.datadoghq.ad.init_configs: "[{}]"
      com.datadoghq.ad.instances: "[{\"host\": \"%%host%%\", \"port\":\" {{ host_port }}\" }]"

This fails with

"msg": "Error creating container: 500 Server Error for http docker://localhost/v1.41/containers/create?name=portainer: Internal Server Error ("json: cannot unmarshal array into Go struct field ContainerConfigWrapper.Labels of type string")"

After some trial and error I have found the reason to be that the 3rd label somehow is parsed as json, as evident from this log output from ansible-playbook -vvv:

        "labels": {
            "com.datadoghq.ad.check_names": "[\"portainer\"]",
            "com.datadoghq.ad.init_configs": "[{}]",
            "com.datadoghq.ad.instances": [
                {
                    "host": "%%host%%",
                    "port": " 9000"
                }
            ]
        },

You can see check_names and init_configs are properly parsed as strings, while instances has been converted to a structure suggesting it was parsed as json.

Two tested methods that don't work in my case:

!unsafe will work but disallows template_variables which I depend on (host_port).

| to_json will work if the original string is in a template_variable, but I don't want that.

My question is, how can I avoid ansible parsing strings as json?

CodePudding user response:

Ansible will more or less reinterpret every string it comes accross as a yaml/json datastructure whenever a variable is expanded.

As you already found out, you can use the !unsafe yaml datatype to turn templating off for needed variables.

    labels:
      com.datadoghq.ad.check_names: !unsafe "[\"portainer\"]"
      com.datadoghq.ad.init_configs: !unsafe "[{}]"
      com.datadoghq.ad.instances: !unsafe "[{\"host\": \"%%host%%\", \"port\":\" {{ host_port }}\" }]"

Unfortunately, as you also pointed out, this will block the expansion of {{ host_port }} which will appear raw in the result string and that is not what we want. So we have to be a bit trickier for the definition of com.datadoghq.ad.instances. Possible solution demonstrated in the following playbook:

---
- host: localhost
  gather_facts: false


  vars:
    host_port: 9000

    instances:
      - hosts: !unsafe "%%host%%"
        port: "{{ host_port }}"

    labels:
      com.datadoghq.ad.check_names: !unsafe "[\"portainer\"]"
      com.datadoghq.ad.init_configs: !unsafe "[{}]"
      com.datadoghq.ad.instances: "{{ instances | to_json }}"

  tasks:
    - debug:
        msg: "{{ labels }}"

which gives:

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

TASK [debug] *********************************************************
ok: [localhost] => {
    "msg": {
        "com.datadoghq.ad.check_names": "[\"portainer\"]",
        "com.datadoghq.ad.init_configs": "[{}]",
        "com.datadoghq.ad.instances": "[{\"hosts\": \"%%host%%\", \"port\": 9000}]"
    }
}

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

If you don't like that solution, an other one is to declare the vars as pure inline json inside a var expansion and transform it to a json string on the fly:

    labels:
      com.datadoghq.ad.check_names: '{{ ["portainer"] | to_json }}'
      com.datadoghq.ad.init_configs: '{{ [{}] | to_json }}'
      com.datadoghq.ad.instances: '{{ [{"host": "%%host%%", "port": host_port}] | to_json }}'

If this is still not what you are looking for... sorry that's all I have in my stock for now.

  • Related