Ansible: Ошибка «рекурсивный цикл обнаружен в строке шаблона» с переменными в имени

Созданный на 13 авг. 2014  ·  58Комментарии  ·  Источник: ansible/ansible

Тип выпуска:

Сообщение об ошибке

Ансибл версия:

1.6.10 и 1.7

Окружающая обстановка:

Mac OSX 10.9.4

Резюме:

Я нашел странную ошибку с нестандартными плейбуками (или, может быть, с моим кодом/настройкой). Когда я использую сборник задач с включением, переданные переменные нельзя использовать в «имени» модуля.

Действия по воспроизведению:
  • Создайте сборник заданий
  • Создайте сборник игр для бегунов, который включает в себя сборник задач.
  • Добавьте задачу в плейбук задач, который берет параметр из плейбука бегуна, и в нем name: включите переменную.
  • Запустите плейбук для запуска задач
  • Вы получите ошибку обнаружения рекурсивного цикла.

Вот пример проблемы:
task_runner.yml

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

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

Задачи.yml

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

Следует отметить, что если я изменю name: "ping {{ host }} twice" на name: "ping host twice" , я не получу ошибку (которую я получаю в разделе «Фактические результаты» ниже).

Ожидаемые результаты:

Нет сообщения об ошибке.

Вот рабочий пример, впервые выполненный в одном плейбуке:
task_playbook.yml

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

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

Вот как это работает:

(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
Фактические результаты:
(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}}

Спасибо.

Самый полезный комментарий

Я сталкиваюсь с той же ошибкой, хотя и с другой конфигурацией. Этого достаточно, чтобы воспроизвести его в плейбуке:

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

Если я сравню переменные с app_user и app_home , все будет замечательно, но я подумал, что было бы неплохо сохранить иерархию, особенно когда у меня много переменных с префиксом app . Использование анзибл 1.8.3.

Все 58 Комментарий

Привет!

Похоже, это работает именно так, как было задумано для меня, потому что вы определяете переменную с именем host, значением которой является host.

   vars:
        host: "{{ host }}"

Я бы рекомендовал _НЕ_ этого делать.

Это что-то вроде "Доктор, у меня болит рука, когда я это делаю", если перевести эту шутку :)

Я собираюсь закрыть этот вопрос, так как он работает, как задумано, но не стесняйтесь заходить на ansible-project, если вы хотите продолжить обсуждение. Мы открыты для этого.

Я сталкиваюсь с той же ошибкой, хотя и с другой конфигурацией. Этого достаточно, чтобы воспроизвести его в плейбуке:

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

Если я сравню переменные с app_user и app_home , все будет замечательно, но я подумал, что было бы неплохо сохранить иерархию, особенно когда у меня много переменных с префиксом app . Использование анзибл 1.8.3.

Я в той же лодке, что и Томник. Хотя, если подумать, это имеет смысл. Мы пытаемся сослаться на переменную до того, как она была назначена.

Попробуйте то же самое в питоне:

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

Это позор, потому что это делает хорошие аккуратные конфиги.
Мой обходной путь - определить повторно используемые переменные отдельно, т.е. в вашем случае:

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

Немного некрасиво, но пока это компромисс, с которым я согласен.

Имея нечто подобное после перехода с 1.6.X на 1.8.4. Раньше это работало для нас, теперь мы сталкиваемся с проблемой рекурсии.

У нас есть плейбук следующим образом:

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

Затем в group_vars для конкретной среды мы определяем rsyslog:

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

После запуска роли rsyslog мы получаем ошибку:
=> в строке шаблона обнаружен рекурсивный цикл: {{rsyslog.producers}}

Я очень надеюсь, что это была не ошибка, которую исправили и теперь мы не можем ею воспользоваться. Моя интуиция чувствует, что это изменение порядка в том, как создаются переменные. Обратите внимание, что мы принудительно объединили hash_behaviour в нашей конфигурации ansible.

Любые мысли или предложения будут приветствоваться, прежде чем я углублюсь или переработаю то, как мы определяем вещи.

та же проблема :-1:

Та же проблема, которую удалось обойти, просто переместив и переименовав «конфликтующую» переменную.

То же самое здесь ... потому что вы вслух передаете «переменные» в качестве дополнительных аргументов, бывают случаи, когда мне нужно использовать переданное значение, иначе используйте значение по умолчанию.

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

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

Только что столкнулся с тем, что сделал @thom-nic. Я не совсем уверен, почему это приводит к ошибке. Глядя на это, похоже, что-то, что должно работать отлично.

Ага. когда я вставляю зависимости ролей, это происходит.

Та же проблема здесь. Хотел использовать более приятный формат так плохо

Программное обеспечение может решить все виды проблем, и, конечно же, это тоже можно решить. Однако формат словаря, вызывающего сам себя во время его определения, находится в его собственном домене. Год назад кто-то задал этот вопрос, где предлагается формат, это может быть способ сделать это:

https://stackoverflow.com/questions/22989401/using-a-relative-path-to-reference-another-element-in-a-многомерный-массив

Я также сталкиваюсь с той же ошибкой. Может кто-нибудь сказать мне, как мне это сделать. Я использую приведенную ниже переменную и получаю ошибку рекурсивного цикла.

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

Я тоже в него попадаю. Это будет исправлено? Должен ли он быть вновь открыт?

+1

+1

+1

+1

+1

+1

+1

Кажется глупым, но понятно, почему это не должно работать из-за того, что словарь пытается рекурсивно запросить у себя ключ, когда сам словарь еще не полностью инициализирован.
Вот как мне удалось обойти это с ролью:

Сломан с ошибкой рекурсии

Это то, что я пытался сделать заранее, чтобы иметь только одно место для изменения версии.

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:
    ...

Исправить

Я решил использовать папку по умолчанию и запихнуть туда случайные вещи, так как сохранение плоского состояния vars заставляет их сначала инициализироваться. Затем я мог бы втянуть их в свою «красивую» структуру в vars/main.yml.

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

В defaults/main.yml у меня есть:

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

В defaults/vars.yml у меня есть:

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

  is_agent: false
  agent:
    ...

Таким образом, переменные по-прежнему могут быть изменены выше, но также позволяет единый источник, из которого можно изменить «переменные по умолчанию»;)

Редактировать

После тестирования я изменил переменные по умолчанию, чтобы они имели подчеркивание из-за того, что они обозначались таким образом (согласно соглашению) как «частные». Кроме того, таким образом вы точно знаете, из какого файла считываются переменные. хе.

+1

+1

Я хотел бы иметь возможность использовать такую ​​​​конфигурацию:

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

Я бы рекомендовал НЕ делать этого.

Это что-то вроде "Доктор, у меня болит рука, когда я это делаю", если перевести эту шутку :)

@mpdehaan , представьте, что я только что установил другой модуль от ansible galaxy взамен того, который я использовал раньше. Представьте, что он установил переменную по умолчанию с именем, скажем, mysql_version . Представьте, что у меня есть переменная с точно таким же именем в моем модуле, которая имеет метазависимость от модуля, который я только что установил.

Я не хочу менять имя переменной внутри моего модуля. Поскольку это само по себе мнемоника, и все переменные названы по одному и тому же правилу, то есть service_name_version , и я не хочу создавать свою команду с чем-то вроде mysql_version_another_stupid_name_because_ansible_cannot_do_that_properly . Специальное имя переменной — еще один нюанс, который член команды должен помнить, а значит, больше шансов на ошибку. И я не могу принести это из инвентаря, так как модуль должен быть общим.

Что бы вы посоветовали для этого довольно распространенного варианта использования?

+1

+1

Отсутствие этого мешает иметь красивую, чистую иерархию переменных, поскольку мы должны делать это старомодным способом с префиксами. Учитывать:

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

против

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

Хотя это всего лишь пример, а не реальный случай (по крайней мере, для меня), он показывает, в чем проблема. ИМО, последнее определение просто противно. Первый применяет своего рода DRY'ing.

+1

Ссылаясь на ответ @bn0 выше:

Хотя, если подумать, это имеет смысл. Мы пытаемся сослаться на переменную до того, как она была назначена.

А также со ссылкой на всех, кто говорит, что эта функция была бы логически бессмысленной:

Эта функция на самом деле четко определена с точки зрения значения ровно одной вещи. Синтаксис и семантика (с точки зрения логики) хорошо определены для такого рода «обратной ссылки».
Проблема здесь действительно относится только к тому, как будет выглядеть реализация.

1. Проблема реализации

Но подождите: как значение хэш-карты может ссылаться на хэш-карту, которая еще не определена?

Предположение здесь отчасти неверно. Потому что здесь мы обрабатываем два синтаксиса. YAML и доступные переменные. Нужно знать, что недоступное понятие переменных {{ some_var_name_here }} не имеет смысла в YAML (см. раздел 2 этого). На самом деле он сам является доступным, что придает смысл этой строке. И, таким образом, ansible свободен в том, как реализовать парсер для обработки этого. Давайте рассмотрим быстрый пример, учитывая следующий документ настроек YAML:

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

Когда определена переменная hash_map ? Уже на _строке 1_ или только после _строки 3_?

Две разные реализации

Посмотрим, как это решение повлияет на реализацию.

Обратите внимание: эти примеры действительно упрощены, на самом деле все это будет обрабатываться синтаксическим анализатором по-другому.

Реализация №1 подразумевает, что структура данных определяется только после _строки 3_ в приведенном выше примере:

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"] }

Реализация № 2 подразумевает, что _строки 1_ выше вполне достаточно для размещения структуры данных.

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

Оба представляют одно и то же, но, как вы можете видеть, последний немного мощнее.

2. Хорошо, тогда в чем проблема?

Что говорит стандарт?

Давайте посмотрим, чего мы можем добиться с помощью простого YAML.

Представление YAML по стандарту

Стандарт YAML описывает семантику YAML как корневой ориентированный граф. Узлы описаны в разделе 3.2.1.1.

В соответствии со _стандартом_ значения также представлены в виде узлов. Но — и вот в чем дело — насколько мне известно, не существует типа узла, который мог бы семантически правильно (как мы этого хотим) представить описанную выше функцию как один узел . Единственным реальным вариантом здесь будет использование узла sequence (он же список в YAML).

Последствия этого

Приведенный выше пример YAML будет представлен следующим графиком в соответствии с моим пониманием стандарта:

yaml-representation

Поскольку мы хотим придать значение {{hash_map.version}} , нам придется использовать anchor и псевдонимы узлов, подобные этому:

Примечание. Эта функция описана в разделе 6.2.9. Раздел

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

Проверьте это с помощью онлайн-парсера YAML.

получается следующий график:

yaml-representation-sequence

Что из этого следует?

Выше мы видим, что мы не можем объединить эти два узла. Таким образом, невозможно реализовать эту функцию, используя только синтаксис YAML.
Но мы также можем видеть, что {{ some_var_name_here }} — это всего лишь анзиблируемая нотация, которая не имеет значения в YAML. Таким образом, мы вольны определять семантику этого по своему усмотрению!

TLDR

YAML - это язык разметки (несмотря на то, что может предполагать его аббревиатура), и язык разметки просто отвечает на вопрос "что", а не "как". И поскольку совершенно ясно, что означает ссылка, реализовать эту функцию вполне возможно.

Имейте в виду, что эта функция не соответствует текущему стандарту YAML v.1.2 (последняя версия согласно Википедии ), но это не соответствует нашему понятию переменных {{ some_var_name_here }} .

РЕДАКТИРОВАНИЕ:

  • 2018-04-15 21:11:

    • Переформулируйте вступление.

    • Исправьте слово с ошибкой (теперь -> знаю).

Ну и какой вывод? Поскольку {{ some_var_name_here }} - это просто неизменяемая нотация, и ее семантика может быть изменена для обработки "отложенного разрешения", планируете ли вы реализовать что-то подобное (например, привязку замыкания, например, в groovy)? Вопрос все еще закрыт.

Я бы очень хотел реализовать эту функцию! Хотя это не так просто. Поскольку эта функция имеет большее влияние, мне потребуется некоторое время, если я сделаю это самостоятельно. Желательно, чтобы был один или два других участника, готовых помочь, и в этом случае все ускорится.

Для меня такое поведение было действительно неожиданным и странным. Есть много возможных сценариев, когда с текущим поведением не очень приятно работать:

~~~

  • set_fact:
    хост: "some.host.com"
  • включают: task.yml
    вары:
    хост: "{{ хост }}"
    ~~~

Это потенциально происходит каждый раз, когда вы передаете данные из одной роли/задачи в другую. Особенно с внешними ролями (Galaxy) это может быть основным PITA. Обходной путь установки промежуточного факта или использования промежуточной фиктивной задачи довольно уродлив:

~~~

  • set_fact:
    not_host: "{{ хост }}"
  • включают: task.yml
    вары:
    хост: "{{ not_host }}"
    ~~~

Это очень неожиданное поведение. Любой приличный язык позволяет вам установить ключ dict, равный переменной с таким же именем. В питоне, например:

{'host': host}

Было бы настоящей проблемой, если бы нужно было изменить имя переменной только для того, чтобы передать его дальше:

{'host': _host}

В настоящее время лучшим обходным решением, которое я нашел, является префикс переменных с именем роли. например:

nginx_domain: '{{ myapp_domain }}'

Это работает, но это довольно хлопотно, тебе не кажется?

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1

Это ошибка реализации YAML.

+1

+1

+1

+1

+1

Я также попал в это. Не могли бы вы подумать о повторном открытии этого?

@everybody , я имею в виду буквально всех :-).

Это поразило меня раньше, единственное, что вы можете сделать, это реструктурировать свои переменные. Обратите внимание, что вы не можете ссылаться на переменную, которая еще не определена (см. приоритет переменных в документах ansible. Также обратите внимание, что если вы ссылаетесь на переменную, которую вы определяете позже в файле, вы получите сообщение об ошибке.).

Настоящая «проблема» не в Ansible, а в Jinja2. Если вы думаете, что можете это исправить, будьте моим гостем. Однако я не думаю, что это легко исправить, если не невозможно в рамках текущей архитектуры/проекта jinja2.

Когда у вас есть что-то подобное (например, @rqelibari ):

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

А также хочет чистый и красивый файл с переменными и / или каталог. Рассмотрим другую архитектуру, вы можете добавить переменные в другие файлы, вы не ограничены файлом main.yml или файлом all.yml. Ansible гибок, поэтому, если вы сообразительны, двигайтесь и играйте с ним :-D.

Например, вы можете создать файл в каталоге all группы group_vars с именем hash_map и создать в этом файле нужные вам переменные.

Да, было бы неплохо, если бы можно было сослаться на отмеченный вами ключ в том же хранилище ключей, в настоящее время это невозможно. Я не вижу, чтобы это произошло в ближайшие несколько месяцев...

Обходите это, проявляйте творческий подход и не делайте беспорядок в своих скриптах. Лично мне хотелось, чтобы все настраивалось с помощью ansible-variables. Единственной наградой, которую я получил, была неуправляемая пьеса и нечеткая структура переменных :-).

Хорошо, не добавляйте +1 к этому в ansible, но исправьте это в проекте jinja2.

Эта проблема является прямым результатом использования ansibles парсера yaml.
функция интерполяции вместо загрузки объекта yaml и выполнения второго
проход для интерполяции. Это не просто ошибка jinja, это
ошибка реализации в ansible. Пожалуйста, осмотрите. Загрузка как необработанная строка и
затем обработка инициализированных членов объекта во втором проходе должна исправить
это.

+1

+1

+1

+1

Была ли эта страница полезной?
0 / 5 - 0 рейтинги