Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

'recursive loop detected in template string' error with variables in name #8603

Closed
inderpreet99 opened this issue Aug 13, 2014 · 58 comments
Closed

Comments

@inderpreet99
Copy link

Issue Type:

Bug Report

Ansible Version:

1.6.10 and 1.7

Environment:

Mac OSX 10.9.4

Summary:

I've found a weird bug with ansible playbooks (or maybe my code/setup). When I use a tasks playbook using an include, the variables passed in can't be used in the 'name' of a module.

Steps To Reproduce:
  • Create a tasks playbook
  • Create a runner playbook that includes tasks playbook
  • Add a task to tasks playbook that takes a parameter from the runner playbook, and in it's name: include the variable.
  • Run the task runner playbook
  • You will receive a recursive loop detected error.

Here's the problem example:
task_runner.yml


---
- name: Disable {{ host }}
  hosts: all
  gather_facts: no # for debug purposes

  tasks:
    - name: Run task
      include: tasks.yml
      vars:
        host: "{{ host }}"

tasks.yml

- name: "ping {{ host }} twice"
  shell: ping -c 2 {{ host }}

It should be noted that if I change name: "ping {{ host }} twice" to name: "ping host twice", I will not receive the error (that I'm receiving in "Actual Results" below).

Expected Results:

No error message.

Here's a working example first done in a single playbook:
task_playbook.yml


---
- name: Disable {{ host }}
  hosts: all
  gather_facts: no # for debug purposes

  tasks:
    - name: ping {{ host }} twice
      shell: ping -c 2 {{ host }}

Here's how it runs:

(venv) isingh$ ansible-playbook -c local task_playbook.yml -i hosts --extra-vars host=google.com

PLAY [Disable google.com] *****************************************************

TASK: [ping google.com twice] *************************************************
changed: [fionn]

PLAY RECAP ********************************************************************
fionn                      : ok=1    changed=1    unreachable=0    failed=0
Actual Results:
(venv) isingh$ ansible-playbook -c local tasks_runner.yml -i hosts --extra-vars host=google.com

PLAY [Disable google.com] *****************************************************
ERROR: recursive loop detected in template string: {{host}}

Thanks.

@mpdehaan
Copy link
Contributor

Hi!

So this seems to be working exactly as designed for me, because you're defining a variable named host who's value is host.

   vars:
        host: "{{ host }}"

I would recommend NOT doing that.

It's kind of a "Doctor, My Arm Hurts When I Do This" kind of thing, if that joke translates :)

I'm going to close this one as working as designed, but feel free to stop by ansible-project if you'd like to discuss further. We're open to it.

@thom-nic
Copy link

I'm encountering the same error, although with a different configuration. This is enough to reproduce it in a playbook:

 vars:
    app:
      user:    rails
      home:    "/home/{{ app.user }}"

If I flatten the vars to app_user and app_home everything is peachy but I thought it was nice keep the hierarchy, especially when I have a lot of app-prefixed vars. Using ansible 1.8.3.

@bn0
Copy link

bn0 commented Mar 3, 2015

I'm in the same boat as thom-nic. Although after thinking about it, it does make sense. We're trying to reference a variable before it has been assigned.

Try the same thing in python:

>>> app = {"user": "rails", "home": "/home/" + app['user']}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'app' is not defined

It's a shame, because it does make for nice neat configs.
My workaround is to define re-used variables separately, i.e. in your case:

app_user: "rails"
app:
   user: "{{ app_user }}"
   home: /home/{{ app_user }}

Bit ugly, but so far it's a trade-off I'm ok with.

@octalthorpe
Copy link

Having something similar after moving from 1.6.X to 1.8.4. It was working before for us, now we run into the recursion issue.

We have a playbook as follows:

- role: rsyslog
      tags: ['rsyslog']
      rsyslog:
        reception:
          tcp:
            enabled: True
        transmission:
          tcp:
            enabled: True
            hosts: "{{rsyslog.producers}}"

Then in a group_vars for a specific environment we define the rsyslog:

rsyslog:
  producers: "{% set hosts = ['localhost:5544'] %}{% for host in groups['logmaster'] %}{% do hosts.append(host + ':5140') %}{% endfor %}

Once the rsyslog role is run we get the error:
=> recursive loop detected in template string: {{rsyslog.producers}}

I really hope this was not a bug that was fixed and now we cannot take advantage of it. My gut feel it is a ordering change, in the way variables are built up. Note we have forced the hash_behaviour to merge in our ansible configuration.

Any thoughts or suggestions would be welcomed before I dig in deeper or rework how we define things.

@sebas5384
Copy link

same problem here 👎

@jaytaylor
Copy link
Contributor

Same issue, worked around it by just moving and renaming the "conflicting" variable.

@leafpeak
Copy link

leafpeak commented May 5, 2015

Same here...because you're aloud to pass "variables" as extra arguments there are times where I need to use the passed in value otherwise use a default value.

ansible-playbook playbook.yml -e "my_var=1"

# playbook.yml
vars:
  my_var: "{{ my_var | default(my_default_var) }}"

@michaeljs1990
Copy link
Contributor

Just encountered what @thom-nic did. I am not entirely sure why that results in an error though. Looking at it it looks like something that should work perfectly fine.

@majidaldo
Copy link

yup. when i nest role dependencies, this happens.

@boyangwang
Copy link

Same issue here. Wanted to use the nicer format so bad

@conrado
Copy link
Contributor

conrado commented Jul 17, 2015

Software can solve all sorts of issues, and certainly this could be solved too. However the format for a dictionary calling on itself while it is being defined is in its own domain. A year ago someone asked this question, where a format is proposed, it could be a way to do it:

https://stackoverflow.com/questions/22989401/using-a-relative-path-to-reference-another-element-in-a-multi-dimensional-array

@anirudh-wa
Copy link

I am also running into the same error. Can someone tell me how do I go about it. I am using the below variable and I get recursive loop error.

maxHeapSizeAnsi: "{{(hostvars[inventory_hostname]['ansible_memtotal_mb'] * 3 / 4)|int}}"

@Hubbitus
Copy link

I also fall in it. Is it will be fixed? Should it be reopened?

@dan-petty
Copy link
Contributor

+1

6 similar comments
@jmcollin78
Copy link

+1

@ivan7farre
Copy link

+1

@penguinjournals
Copy link

+1

@d3vil-st
Copy link

+1

@wenerme
Copy link

wenerme commented Feb 24, 2016

+1

@mburz
Copy link

mburz commented Feb 27, 2016

+1

@matthewhartstonge
Copy link

Seems dumb, yet it makes sense as to why this shouldn't work due to the dictionary trying to recursively ask itself for a key when the dictionary itself hasn't been fully initialized yet.
This is how I managed to get around it with a role:

Broken w/ recursion error

This is what I was trying to do before hand to have only one place to alter the version.

application:
  params:
    state: present
    version: "0.6.3"
    download_url: "https://releases.hashicorp.com/consul/{{ consul.params.version }}/consul_{{ consul.params.version }}_linux_amd64.zip"

  agent:
    ...

Fix

I decided to make use of the defaults folder and stuff the random things in there as keeping the vars flat forces them to be initialized first. Then, I could pull them into my 'beautiful' structure in vars/main.yml

   .
   |-roles
   |---Application
   |-----defaults
   |-------main.yml
   |-----files
   |-----handlers
   |-----meta
   |-----tasks
   |-----templates
   |-----vars
   |-------main.yml

In defaults/main.yml i have:

_version: "0.6.3"
_download_url: "https://releases.hashicorp.com/consul/{{ _version }}/consul_{{ _version }}_linux_amd64.zip"

In defaults/vars.yml i have:

consul:
  params:
    state: present
    version: "{{ _version }}"
    download_url: "{{ _download_url }}"

  is_agent: false
  agent:
    ...

This way the variables can still be altered higher up, but also allows a single source of where the 'default vars' can be changed from ;)

Edit

After testing, I changed default vars to have an underscore due to denoting them this way (via convention) as 'private'. Also, this way you know exactly what file the variables are being read from. heh.

@mamoit
Copy link

mamoit commented May 12, 2016

+1

@oupala
Copy link

oupala commented Aug 16, 2016

+1

I would love to be able to use such configuration:

myapp:
  version: 1.6.3
  download_url: http://example.org/download/myapp-{{ myapp.version }}.tgz

@netkgk
Copy link

netkgk commented Sep 22, 2016

I would recommend NOT doing that.

It's kind of a "Doctor, My Arm Hurts When I Do This" kind of thing, if that joke translates :)

@mpdehaan, imagine I've just installed another module from the ansible galaxy in replacement for that I've used before. Imagine it has set default variable named, let's say mysql_version. Imagine I have the variable of the exact same name in my module, which has dependency in meta to the module I've just installed.

I don't want to change the variable name inside my module. As it's mnemonic by itself, and all the variables are named following the same rule, i.e. service_name_version and I don't want to stage my team up with something like mysql_version_another_stupid_name_because_ansible_cannot_do_that_properly. A special variable name is another nuance which a team member supposed to remember, hence more chance for an error. And I can't bring this from the inventory, as the module supposed to be sharable.

What would be your advice for that pretty much common use case?

@jeliser
Copy link

jeliser commented Oct 12, 2016

+1

@kravvcu
Copy link

kravvcu commented Nov 3, 2016

+1

Not having this prevents having a nice, clean variable hierarchy, since we have to do it the old-fashioned prefixed way. Consider:

mysql:
  version: 5.5
  install_path: "/var/lib/mysql/{{ mysql.version }}"
  user: mysql
  group: mysql

vs

mysql_version: 5.5
mysql_install_path: "/var/lib/mysql/{{ mysql_version }}"
mysql_user: mysql
mysql_group: mysql

Although this is just an example not a real world case of (at least for me), it presents what the problem is. IMO, the latter definition is just nasty. The former applies some kind of DRY'ing out.

@kbechler
Copy link

kbechler commented Nov 3, 2016

+1

@rqelibari
Copy link
Contributor

rqelibari commented Nov 15, 2016

Referring to @bn0 answer above:

Although after thinking about it, it does make sense. We're trying to reference a variable before it has been assigned.

And also referring to all who say, that this feature would be logically nonsense:

This feature is actually well-defined in terms of meaning exactly one thing. The syntax and semantics (in terms of logic) are both well defined for this kind of "back reference".
The problem here really refers only on how an implementation would look like.

1. Implementation Problem

But wait: how can a hash map value refer to the hash map which is still not defined?

The assumption here is partly wrong. Because we are handling two syntaxes here. YAML and ansible variables. One must know, that ansibles notion of variables {{ some_var_name_here }} has no meaning in YAML (see section 2 of this) . It's actually ansible itself, which gives meaning to that string. And thus ansible is free on how to implement the parser to handle that. Let's see a quick example, given the following YAML settings document:

1. hash_map:
2.    version: ab
3.    path: "/somepath/{{hash_map.version}}"

When is the variable hash_map defined? Already on line 1 or only after line 3?

Two different implementations

Let's see how the implementation would be affected by this decision.

Notice: Those examples are really simplified, in reality all this would be handled by a parser differently.

Implementation#1 implies that the data structure is only defined after line 3 in the example above:

variables = {}
variables["hash_map"] = {"version": "ab", "path": "/somepath/{{hash_map.version}}" }
# We cannot do this!!!
variables["hash_map"] = {"version": "ab", "path": "/somepath/%s" % variables["hash_map"]["version"] }

Implementation#2 implies that line 1 above is totally sufficient to allocate the data structure.

variables = {}
variables["hash_map"] = {}
variables["hash_map"]["version"] = "ab"
# This is totally possible
variables["hash_map"]["path"] = "/somepath/%s" % variables["hash_map"]["version"]

Both represent the same, but as you can see the latter is somehow a bit more powerful.

2. Ok, so where is the problem then?

What does the standard say?

Let's see what we can achieve with plain YAML.

Representation of YAML according to the standard

The YAML standard describes the semantics of YAML as a rooted directed graph. Nodes are described under section 3.2.1.1. Nodes.

According to the standard values are represented as nodes too. But - and here is the point - there is no node type as far as I know, which can semantically represent the feature above correctly (as in the way we want it), as one node. The only real option here would be using a sequence node (aka list in YAML).

Consequences of this

The YAML example above would be represented by the following graph according to my understanding of the standard:

yaml-representation

As we want to give meaning to {{hash_map.version}} we would have to use anchor and alias nodes like that:

Note: This feature is described in the 6.2.9. Node Anchors section

1. hash_map:
2.    version: &anchor "ab"  #setting the anchor
3.    path:
4.        - "/somepath/"
5.        - *anchor          # referring to the anchor, yields ab

Verify this with this YAML online parser

yielding the following graph:

yaml-representation-sequence

What follows of this?

Above we can see that we cannot combine those two nodes. As such there is no way to achieve this feature using YAML only syntax.
But we can also see, that {{ some_var_name_here }} is only an ansible notation and has no meaning in YAML. As such, we are free to define the semantics of that as we want!

TLDR

YAML is a markup language (despite of what it's acronym may suppose) and a markup language just answers the "what" question not "how". And as it is totally clear what the reference means, it is totally possible to implement this feature.

Keep in mind, that this feature is not conform with the current YAML standard v.1.2 (latest release according to Wikipedia), but so isn't our notion of ansible variables {{ some_var_name_here }}.

EDITS:

  • 2018-04-15 21:11:
    • Reformulate the intro.
    • Correct misspelled word (now -> know).

@Hubbitus
Copy link

And so what the conclusion? As {{ some_var_name_here }} is just ansible notation and its semantic may be changed to handle "deffer resolving" do you plan implement something similar (like closure binding for example in groovy)? Issue still closed.

@Erouan50
Copy link
Contributor

+1

11 similar comments
@pierrefevrier
Copy link

+1

@tehfink
Copy link

tehfink commented Aug 2, 2017

+1

@wujie1993
Copy link

+1

@florres
Copy link

florres commented Aug 8, 2017

+1

@alexander-balashov
Copy link

+1

@colin-byrne-1
Copy link

+1

@Alwaysc0d3
Copy link

+1

@yaroslav-shabalin
Copy link

+1

@cars10
Copy link

cars10 commented Dec 1, 2017

+1

@duanshiqiang
Copy link

+1

@jn-bedag
Copy link
Contributor

jn-bedag commented Jan 5, 2018

+1

@cmpunches
Copy link

+1

This is a YAML implementation bug.

@acjohnson
Copy link

+1

4 similar comments
@JasonSheedy
Copy link

+1

@knowshan
Copy link

+1

@BennyHarv3
Copy link

BennyHarv3 commented Jan 24, 2018

+1

@martindines
Copy link

+1

@daMupfel
Copy link

daMupfel commented Mar 2, 2018

I also hit this. Can you please think about reopening this?

@Alwaysc0d3
Copy link

@everybody, and i mean literally everybody :-).

This did hit me before, the only thing you can do is restructure your variables. Note you can't refer to a variable that's not already definded (see variable precedence in the ansible docs. Note also that if you refer to a variable you define later on in the file, you will get an error.).

The real 'problem' is not in Ansible but in Jinja2. If you think you can fix this, be my guest. However, i don't think its easy to fix, if not impossible within the current jinja2 architecture / project.

When you have something like this (credits to @rqelibari for the example):

hash_map:
  version: ab
  path: "/somepath/{{hash_map.version}}"

And also wants a clean and nice ansible variable file and / or directory. Consider another architecture, you could add variables in other files, you're not limited to the main.yml file or the all.yml file. Ansible is flexible, so if your'e smart, move and play with it :-D.

For example, you could create a file in the ' all ' directory of the group_vars named 'hash_map' and create the variables you need in this file.

Yes it should be nice if it's possible to refer to the key you noted in the same key-store, currently it is'nt possible. I don't see it happen within now and a few months either...

Work around it, be creative and don't make your ansible scripts a mess. Personally i did like to make everything configurable with the ansible-variables. The only reward i got was an unmanageable playbook and a fuzzy variable structure :-).

Alright, dont +1 this on ansible but fix this at the jinja2 project.

@cmpunches
Copy link

cmpunches commented Mar 2, 2018 via email

@iron77
Copy link

iron77 commented Mar 25, 2018

+1

3 similar comments
@bsberry
Copy link

bsberry commented Mar 27, 2018

+1

@ghost
Copy link

ghost commented May 25, 2018

+1

@liubog2008
Copy link

+1

@ansible ansible locked as resolved and limited conversation to collaborators Jun 7, 2018
LOMS pushed a commit to LOMS/ansible that referenced this issue Nov 8, 2020
This fixes issue, when variables config like below will fail with "infinite recursion in template":

root_var:
  nested_var: value
  another: "{{ root_var.nested_var }}/extra_path or something"
LOMS pushed a commit to LOMS/ansible that referenced this issue Nov 8, 2020
This fixes issue, when variables config like below will fail with "infinite recursion in template":

root_var:
  nested_var: value
  another: "{{ root_var.nested_var }}/extra_path or something"
LOMS pushed a commit to LOMS/ansible that referenced this issue Nov 8, 2020
Fixes variable templating for structures like this:

root_var:
  nested_var: "value"
  another: "{{ root_var.nested_var }}_extra"

 or

 root:
   var: "value"
   subdict:
     subvar: "{{ root.var }} and puppies!"
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests