113

How to escape double curly braces in Ansible 1.9.2?

For instance, how can I escape double curly braces in the following shell command?

- name: Test 
  shell: "docker inspect --format '{{ .NetworkSettings.IPAddress }}' instance1"
udondan
  • 57,263
  • 20
  • 190
  • 175
Davide Guerri
  • 1,887
  • 2
  • 17
  • 25
  • 1
    Note: The trouble here is that the `inspect` docker client command takes in input a go template which uses the double curly braces as jinja2. I need a way to get the curly braces to the final command Ansile run on the target servers. See http://docs.docker.com/reference/commandline/inspect – – Davide Guerri Sep 04 '15 at 08:06

10 Answers10

169

Whenever you have problems with conflicting characters in Ansible, a rule of thumb is to output them as a string in a Jinja expression.

So instead of {{ you would use {{ '{{' }}:

- debug: msg="docker inspect --format '{{ '{{' }} .NetworkSettings.IPAddress {{ '}}' }}' instance1"

Topic "Escaping" in the Jinja2 docs.

udondan
  • 57,263
  • 20
  • 190
  • 175
  • 1
    I can't get it working. I am starting to think that I am missing something... I get this: `changed: [docker-server.local] => {"changed": true, "cmd": "docker inspect --format '{# .NetworkSettings.IPAddress #}' glance-api.os-in-a-box", "delta": "0:00:00.029344", "end": "2015-08-29 17:36:37.962143", "rc": 0, "start": "2015-08-29 17:36:37.932799", "stderr": "", "stdout": "172.17.1.6", "warnings": []} ` – Davide Guerri Aug 29 '15 at 16:38
  • Closing curly braces not needed – Nelson G. Aug 29 '18 at 13:32
  • 1
    This doesn't work. I don't know why there are so many upvotes - this only works for one level. If you create a variable `raw_var: '{{ '{{' }}something{{ '}}' }}' ` and reference it elsewhere `command: "{{ raw_var }}"` you will get `Error: something is undefined`. – Megakoresh Jul 18 '19 at 08:20
  • 3
    `raw_var: "{{ '{{' }}something{{ '}}' }}"`? – udondan Jul 18 '19 at 09:55
  • you can also wrap the entire string in {{''}}, makes it a bit more readable. So to escape {{.ImageName}}|{{.Name}}|{{.ImageFullID}}|{{.FullID}} you write {{'{{.ImageName}}|{{.Name}}|{{.ImageFullID}}|{{.FullID}}'}} – Niwla23 Jul 22 '22 at 15:15
74

This:

- name: Test 
  shell: "docker inspect --format {% raw %}'{{ .NetworkSettings.IPAddress }}' {% endraw %} instance1"

Should work

Another way to do is using backslashes like \{\{ .NetworkSettings.IPAddress \}\}

Hope it helps

Filipe Felisbino
  • 2,712
  • 25
  • 26
  • 2
    Thanks Filippe but I have already tried both and they don't work :( – Davide Guerri Aug 28 '15 at 21:06
  • What version of ansible are you using? – Filipe Felisbino Aug 28 '15 at 21:08
  • ansible version 1.9.2 – Davide Guerri Aug 28 '15 at 21:09
  • 7
    I just tested this in 1.9.2 and `{ raw }...{% endraw %}` works for me. Escaping though indeed does not work. It will result in `\\{\\{ .NetworkSettings.IPAddress \\}\\}` – udondan Aug 29 '15 at 06:55
  • It seems to work, but unfortunately Ansible tries to expand `{{ .NetworkSettings.IPAddress }}`. This is what I get: `changed: [docker-server.local] => {"changed": true, "cmd": "docker inspect --format '{# .NetworkSettings.IPAddress #}' glance-api.os-in-a-box", "delta": "0:00:00.028659", "end": "2015-08-29 17:25:53.416944", "rc": 0, "start": "2015-08-29 17:25:53.388285", "stderr": "", "stdout": "172.17.1.6", "warnings": []}` – Davide Guerri Aug 29 '15 at 16:26
  • 1
    And, as a bonus, it's not even possible to debug the output of the previous step! : `fatal: [docker-server.local] => Failed to template {{test}}: Failed to template docker inspect --format '{{ .NetworkSettings.IPAddress }}' glance-api.os-in-a-box: template error while templating string: unexpected '.'` – Davide Guerri Aug 29 '15 at 16:29
  • 1
    can be debugged with this trick: `- name: Find ipv6 of of some-host shell: docker inspect -f "{% raw %} {{ .NetworkSettings.GlobalIPv6Address }} {% endraw %}" {{some.host}} > tmp.txt - command: cat tmp.txt register: result - debug: msg="{{ result.stdout }}"` – lanwen Dec 07 '15 at 19:45
  • {% raw %}...{% endraw %} it worked for me. My ansible version is 2.1.0.0 – David Lee Jun 06 '16 at 09:43
  • Thx [lanwen](http://stackoverflow.com/users/1731801/lanwen), that was the missing bit to get a working debug-output! – jonashackt Feb 22 '17 at 16:43
  • this worked (ansible 2.4.0.0): `docker info --format {% raw %}"{{ .Swarm.LocalNodeState }}"{% endraw %}` – Remigius Stalder Sep 27 '17 at 12:42
  • For complex expressions I personally found the `{% raw %}...{% endraw %}` suggested here the best option. As a further improvement in order to keep the code cleaner one may choose to extract the pattern between raw excaping to a separate ansible task-level variable, like this: `- name: Collect helm3-owned releases list vars: go_template: '{% raw %}{{range .items}}{{printf "%s:%s\n" .metadata.name .metadata.namespace}}{{end}}{% endraw %}' shell: kubectl get secrets --all-namespaces --field-selector type=helm.sh/release.v1 -o go-template='{{ go_template }}}}'` – Illya Doos Jan 19 '21 at 13:41
29

Tried on with ansible 2.1.1.0

{%raw%}...{%endraw%} block seems the clear way

- name: list container images and name date on the server
  shell: docker ps --format {%raw%}"{{.Image}} {{.Names}}"{%endraw%}

Only need to escape leading '{{'

tasks:
- name: list container images and names
  shell: docker ps --format "{{'{{'}}.Image}} {{'{{'}}.Names}}"

No harm to escap the tailing '}}', except more difficult to read.

tasks:
- name: list container images and names
  shell: docker ps --format "{{'{{'}}.Image{{'}}'}} {{'{{'}}.Names{{'}}'}}"

Backslash '\' seems do not work

Tiger peng
  • 647
  • 6
  • 10
13

New in Ansible 2.0 is the ability to declare a value as unsafe with the !unsafe tag.

In your example you could do:

- name: Test 
  shell: !unsafe "docker inspect --format '{{ .NetworkSettings.IPAddress }}' instance1"

See the docs for details.

mmoya
  • 1,901
  • 1
  • 21
  • 30
Jess
  • 8,628
  • 6
  • 49
  • 67
  • 1
  • 3
    Shh, I said 2.0 – Jess Dec 23 '17 at 00:12
  • It works on 2.4.6 and I found the solution in the docs, so I can confirm. – eco Oct 16 '18 at 22:50
  • interchange single quote and double quotes looks more linux-way ```!unsafe 'docker inspect --format "{{ .NetworkSettings.IPAddress }}" instance1'``` – Timmy Chiu Apr 21 '20 at 13:31
  • Personally, I like to keep all Jinja templates in double quotes as a hint to anyone reading the code. Most languages that have string Interpolation treat single quotes as literals and double quotes as templateable, I try to replicate that in ansible (even though it's not required). Also, wrapping the argument in the shell script with single quotes reduces the # of characters that can be interpreted to " ' " and " \ ", potentially preventing issues if there's say a "$" in the variable. Either quoting way works though – Jess Apr 22 '20 at 16:34
6

I have a similar issue: i need to post a JSON doc made from a jinja2 template containing some go templates variables (yes, i know :-P), such as

"NAME_TEMPLATE": %{{service_name}}.%{{stack_name}}.%{{environment_name}}

Trying to fence this part of the template between

{% raw %} ... {% endraw %}

didn't work because there is some sort of magic in ansible which will run the template and variable substition twice (i'm not sure about that, but it definitively looks like this)

You end up with "undefined variable service_name" when trying to use the template...

So i ended up using a combination of !unsafe and {% raw %} ... {% endraw %} to define a fact that's later used in the template.

- set_fact:
   __rancher_init_root_domain: "{{ rancher_root_domain }}"
   #!unsafe: try to trick ansible into not doing substitutions in that string, then use %raw% so the value won't substituted another time
   __rancher_init_name_template: !unsafe "{%raw%}%{{service_name}}.%{{stack_name}}.%{{environment_name}}{%endraw%}"

- name: build a template for a project
  set_fact:
    __rancher_init_template_doc: "{{ lookup('template', 'templates/project_template.json.j2') }}"

the template contains this:

    "ROOT_DOMAIN":"{{__rancher_init_root_domain}}",
    "ROUTE53_ZONE_ID":"",
    "NAME_TEMPLATE":"{{__rancher_init_name_template }}",
    "HEALTH_CHECK":"10000",

and the output is ok:

"NAME_TEMPLATE": "%{{service_name}}.%{{stack_name}}.%{{environment_name}}",
zuzur
  • 85
  • 1
  • 6
  • This is so unfortunate, but is seems that sometimes Ansible does some weird stuff and such double-escaping constructs are the only way of dealing with it... – WindyFields Jun 22 '20 at 00:37
6

Here's a shorter alternative to udondan's answer; surround the whole string with double brackets:

shell: "docker inspect --format {{ '{{ .NetworkSettings.IPAddress }}' }} instance1"
bmaupin
  • 14,427
  • 5
  • 89
  • 94
4

The solution by using raw has been already mentioned but the command in the answer before unfortunately didn't work for me.

Without ansible:

docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' docker_instance_name

With ansible:

- name: Get ip of db container
  shell: "{% raw %}docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' docker_instance_name{% endraw %}"
  register: db_ip_addr
- debug:
  var: db_ip_addr.stdout
Tobias Ernst
  • 4,214
  • 1
  • 32
  • 30
2

I managed to work around my issue using a small script:

#!/usr/bin/env bash

docker inspect --format '{{ .NetworkSettings.IPAddress }}' "$1"

And the following Ansible play

- copy:
    src: files/get_docker_ip.sh
    dest: /usr/local/bin/get_docker_ip.sh
    owner: root
    group: root
    mode: 0770

- shell: "/usr/local/bin/get_docker_ip.sh {{ SWIFT_ACCOUNT_HOSTNAME }}"
  register: swift_account_info

Nevertheless, it's very surprising that Ansible doesn't allow escaping double curly braces!

Davide Guerri
  • 1,887
  • 2
  • 17
  • 25
1

I was unable to get @Ben's answer to work (shell: !unsafe ...)

What follows here is a complete (and working!) answer to the OP's question, updated for Ansible >2.0

---
# file: play.yml

- hosts: localhost
  connection: local
  gather_facts: no
  vars:
    # regarding !unsafe, please see:
    # https://docs.ansible.com/ansible/latest/user_guide/playbooks_advanced_syntax.html
    #
    - NetworkSettings_IPAddress: !unsafe "{{.NetworkSettings.IPAddress}}"
  tasks:
    - shell: "docker inspect --format '{{NetworkSettings_IPAddress}}' instance1"
      register: out
    - debug: var="{{item}}"                                                                                                   
      with_items:                                                                                                             
        - out.cmd                                                                                                             
        - out.stdout                                                                                                          

outputs: ([WARNINGS] removed)

# ansible-playbook play.yml
PLAY [localhost] ***************************************************************

TASK [shell] *******************************************************************
changed: [localhost]

TASK [debug] *******************************************************************
ok: [localhost] => (item=out.cmd) => {
    "item": "out.cmd", 
    "out.cmd": "docker inspect --format '{{.NetworkSettings.IPAddress}}' instance1"
}
ok: [localhost] => (item=out.stdout) => {
    "item": "out.stdout", 
    "out.stdout": "172.17.0.2"
}

PLAY RECAP *********************************************************************
localhost                  : ok=2    changed=1    unreachable=0    failed=0   

# ansible --version | head -1
ansible 2.6.1
Marc Tamsky
  • 794
  • 5
  • 9
-8

Here is a mostly clean and Ansible native workaround not depending on docker --inspect with curly braces. We assume we have just referenced one container with the Ansible docker module before:

- name: query IP of client container
  shell: "docker exec {{ docker_containers[0].Id }} hostname -I"
  register: _container_query

- name: get IP of query result
  set_fact:
    _container_ip: "{{ _container_query.stdout | regex_replace('\\s','') }}"

You now have the IP of the Docker container in the Variable _container_ip. I also published this workaround on my article The Marriage of Ansible with Docker.

[Update 2015-11-03] Removed whitespaces of the stdout of the container query.

[Update 2015-11-04] BTW, there were two pull requests in the official Ansible repository, that would made this workaround needless by recovering the facts returned by the Docker module. So you could acces the IP of a docker container via docker_containers[0].NetworkSettings.IPAddress. So please vote for those pull requests:

Thomas Steinbach
  • 1,019
  • 1
  • 9
  • 19
  • Very nice. This answer works out of the box with the current docker implementation. In my case, I will have to add some processing because I am adding an additional interface on some container. Nevertheless this approach is really minimalistic and clean. Thanks! – Davide Guerri Nov 02 '15 at 07:22
  • For the sake of completeness, in some container I am adding multiple interfaces: virtual bridges and veths. The project I am talking about is here: https://github.com/dguerri/dockerstack – Davide Guerri Nov 02 '15 at 07:29
  • 10
    This does not answer the question "How can I escape double curly braces in Ansible 1.9.2?" – augurar Feb 06 '17 at 19:52
  • 3
    Downvoted because this answer is totally unhelpful for 99% of use-cases. Workable answer: Curly printing via jinja tpl: https://stackoverflow.com/a/32283447/293064 – Jay Taylor Aug 17 '18 at 21:53