Pull to refresh

Comments 78

Ох, сколько боли за этой! Я могу сказать, что "Variable precedence" делает практически невозможным разработку больших объемов Ansible кода. Вместо этого механизма, я бы предпочел жесткую инкапсуляцию переменных, т.е. полную невозможность для ролей или модулей использовать переменные, кроме тех, что переданы непосредственно.


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

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

Нет там никакой боли. Без этого механизма вы не сможете, например, задавать умолчания в роли (defaults/main.yml) и, при этом, сохранить возможность их переопеределять в отдельных случаях.


На самом деле, самая большая боль ансибла — это Jinja2. Если бы они взяли mako, было бы сильно лучше.


ansible-playbook бинарник

Это не бинарник, а код на питоне.


внутри других плейбуков

Зачем?

Иногда не хватает возможности запустить роль A из ролей B и C с разными параметрами. Это позволило бы уменьшить дублирование, да и вообще сделало бы ансибл более интуитивным для людей привыкших программировать.

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


Ансибл — не язык программирования. Когда вы пишите программу на Ансибл, вы используете Ансибл неправильно. Никакие подсистемы Ансибла не предназначены для использования в качестве языков программирования, и в Ансибле "не хватает для программирования" не каких-то отдельных элементов, а всего.

Да, ансибл не язык программирования, тут я с вами полностью согласен.

Вот есть задача — сделать несколько однотипных вещей отличающихся отдельными элементами, например десяток конфигов в conf.d или скажем несколько папочек со стандартной структурой. Есть несколько вариантов:
1) написать десять почти одинаковых блоков — прямое дублирование, большой объем глупой ручной работы и ошибок в случае последующих изменений
2) include_role — получим все проблемы с приоритетами
3) модуль — сложно, долго и не очень понятно зачем тогда ансибл нужен, если все равно программируем

как правильнее всего решать задачу?

Я обычно делаю по «2». Никаких проблем с приоритетами нет, на самом деле. Автор статьи преувеличивает.


модуль — сложно, долго и не очень понятно зачем тогда ансибл нужен

Модули писать — это правильное решение. И тесты к ним обязательно.


Вопрос — в уровнях абстракции. Например, вот есть модуль mongodb_replicaset, хотя можно было тот же самый функционал и на yaml написать, с определёнными костылями. Но тут мы получаем законченный модуль, который делает всё, что нужно и не требуется в него внутрь лезть.

Переделать входные данные. Каждое действие идёт по списку и делает его по item. Три таски, каждая из которых работает со своим loop.


Главная ошибка, которую делают при разработке в Ансибл, фиксируют формат данных на входе (инвентори/груп-варз) и пытаются трансформировать их (или подстроить код с loop/include) под эти данные. Этот подход работает в программировании и не работает в анисбл.


Вместо этого вы пишите простой код и смотрите, какой формат данных под этот код хорошо подойдёт. Дальше либо у вас всё совсем просто, либо вам нужна программа (на языке программирования), чтобы трансформировать данные из неудобного для Ансибл формата в удобный.

запустить роль A из ролей B и C с разными параметрами

include_role же есть.


более интуитивным для людей привыкших программировать.

Программную часть надо выносить в кастомные модули/фильтры/lookup. У ансибла — декларативный (в целом, но не полностью) подход. С опытом понимание приходит.

Вот include_role вам обещает приключения невероятного масштаба, потому что у include_role другие наборы приоритетов, которые не совпадают при приоритетами для обычной роли. Я могу допустить существование include_tasks, потому что очень нужно цикл для блока, но include_role — это реальное программирование ненадлежащими средствами.

include_role — это реальное программирование ненадлежащими средствами.

Например, есть набор рутинных тасков. Ну конкретнее — добавляет набор из 1 файла и 1 шаблона. Этих наборов может быть несколько, вызывается из разных ролей, передаются разные значения. Очень отдалённое подобие LWRP из шефа (если знакомо).


- include_role:
    name: foo
    tasks_from: fubar
  vars:
    foo__v_smth: "{{ baz__whatever }}"

Можно написать модуль для этого, но относительно долго и получаем кучу питонокода против 3 ансиблотасков (создать диру, положить файл, положить шаблон).


Есть другие идеи как это сделать лучше?

Добавить файл: 1 таска. Добавить шаблон — одна таска. Надо — написали. Не надо — не написали. Всё же просто. И так в каждом месте, где вам надо файл и таску.


… Зачем в этом месте программировать микрофреймворк на ненадлежащем инструменте, когда всех дел — две таски?

Ну это тупой копипаст одного и того же. А как же DRY?


Если авторы ансибла, добавили include_role, то они считают, что это полезно.

DRY касается не каждой строчки, которую вы написали (сто пятьсот вызовов роли — это вам не нарушение DRY?), а бизнес-логики, данных и алгоритмов. Две таски — ни то и не другое, так что лучше их повторить. Вам будет проще писать код, всем окружащим — проще его читать.


Если вы хотите формализм — include_role находится в статусе preview и к использованию в продакшене не должен допускаться. Феерические взрывы в нём на границе 2.4-2.7 должны были хорошо проиллюстрировать, что такое 'preview'.

Феерические взрывы в нём на границе 2.4-2.7 должны были хорошо проиллюстрировать, что такое 'preview'.

И что там взрывалось? У меня всё отлично работало, с момента его появление. И это не «ошибка выжившего». Это означает, что его можно использовать, если понимать, как.

Ну, поскольку я с вами разговариваю, я тоже "выжил". Но, например, вот этот код:


- include_role: role=foo
  delegate_to: other_host

В 2.4 role foo выполнялась на хосте other_host, а теперь начала выполняться на текущем хосте. Догадайтесь, насколько всё взорвалось при наличии такой конструкции в коде и смены того, где роль исполняется.


Если вы прошли по минному полю и ни разу не наступили на регрессию, ура.


А есть те, кто наступили. https://github.com/ansible/ansible/issues?q=is%3Aissue+is%3Aopen+include_role+label%3Abug

Ну вот я не вижу смысла инклюдить всю роль целиком без указания конкретного таска. Я бы так ни за что не сделал. Потому что такое выносится в play, там ему и место.


Если include_role с заданным таском (как в моих комментах выше), то этот таск пишется с расчётом, что его будут инклюдить из других ролей. Поэтому с переменными аккуратно обращаюсь.


Ну и опять же, я не топлю за include_role повсюду — по опыту, такая необходимость возникает редко. Например, для добавления кастомных apt репозиториев.


apt_repo не годится, т.к. не умеет в deb822 добавлять. Но когда будет время, я всё же модуль напишу, может даже PR в апстрим сделаю.


И когда модуль будет готов, то все include_role удобно заменятся на модуль.

В апстрим уже не сделаете, а сделать коллекцию с модулем — это всегда пожалуйста.


Это лучше, чем include_role.

В апстрим уже не сделаете

Почему?

В 2.10 все модули (кроме нужных для работы самого ансибла, типа debug) вынесли во внешние коллекции (community.general и т.д.), так что заслать модуль в апстрим анисбла больше не опция.

А что делать, если надо сделать 100 почти одинаковых наборов действий, например создать десяток каталогов и заполнить их файлами из темплейтов с разными параметрами?
- file:
    path: '{{ item }}'
  loop: '{{ directories_to_create }}'
- template:
    src: '{{ item }}.j2'
    dest: '/etc/{{ item }}'
  loop:
    - foo.conf
    - bar.conf
    - foobar.conf

Если же вы хотите сделать так, чтобы у вас было 100500 ролей, но каждая роль дёргала бы общую роль, которая бы предоставляла универсальный API для выполнения почти похожих действий, контролируемых при помощи передаваемых аргументов в функцию; плюс поддерживала бы наследование от базового класса и полиморфизм с декораторами, то вы хотите не Ансибл, а язык программирования.


И до тех пор, пока вы не поверите в утверждение, что Анисбл — не язык программирования, вы будете пытаться на нём программировать, обжигаться, ругаться и чувствовать большое страдание.

Я верю, что ansible не язык программирования. Теперь мне нужно найти разумное решение для задачи, когда нужно создать несколько директорий %main_dir% с контентом:
%main_dir%/
------ b/foo.conf
------ c/bar.conf
------- /foobar.conf

где каждый файл конфига генерируется со своими параметрами для каждой из %main_dir%.

Я не до конца понимаю "свои параметры", но выглядит это так:


- hosts: somegroup
  vars:
     configs:
        - dir: b
          file: foo.conf
          template: foo.conf.j2
          config: foo.conf
        - dir: c
          file: bar.conf
          template: bar.conf.j2
          config: bar.conf
  tasks:
    - file:
         path: '{{"/".join([main,item.dir] }}'
         state: directory
      loop: '{{ configs }}'
    - template:
         src: '{{ item.template }}'
         dest: '{{ "/".join([main,item.dir,item.config] }}'
      loop: '{{ configs }}'

name и прочие стили можете дописать сами.


Это лучше, чем include. А ещё лучше не сношать мозг и написать столько тасок, сколько надо выполнить, потому что вы получите халявные name для того, чтобы подсказать читающему, нафига вы это делаете.

Поясню, я хотел бы иметь указанную выше структуру в каждой из папок:
* /srv/test, при этом чтобы в темплейтах поле user имедо значение test, а password 'testpass';
* /srv/staging c user=staging и password='stagingPassword';
* /srv/prod с user=admin, password='prodP@sswo0rd'.

При этом мне не требуется каждый раз писать отдельный комментарий для файла foo.conf, вместо этого мне бы хотелось иметь возможность добавить файл newfoo.com один раз, но чтобы он появился правильно заполненным в каждой из папок (test, staging, prod), и возможность добавить новое окружение (например preprod) со своими параметрами.

Я уже объяснил принцип, детали реализации, как и глубокое понимание зачем вам так надо хотеть — это уже в ваших руках.


Код с include — это code smell. Ещё не совсем фатально, но требует пристального внимания. Как unsafe{} в Rust.

Да, возможно это code smell, но кажется это единственное, что решает проблему. То, что вы предложили на мой взгляд не решает и очень трудозатратно в поддержке, но в любом случае спасибо за интересную дискуссию.
На самом деле, самая большая боль ансибла — это Jinja2. Если бы они взяли mako, было бы сильно лучше.
а что не так с Jinja2?

Прочитайте пост. Проблема не совсем с jinja, а с конвертацией типов из jinja в ансибл.

Например то не так, что в Jinja придумали свой убогий недо-язык по мотивам питона. А mako использует внутри себя, фактически, тот же самый Python со всеми его возможностями.


И, скорее всего, при выборе Mako не было бы проблемы с конвертацией типов (см. коммент amarao).

Статья прям про Боль. Скажите, а будет ли статья про best practices, использование которых позволило бы снизить боль до разумных пределов? Например соглашения об именовании, договоренностей о том где объявлять переменные или скажем стандартных приемов, позволяющих выяснить объявлена переменная или нет.

Так это и есть статья про best practice. Пишите просто, пишите ясно, не пишите то, что нельзя написать просто и ясно. Конфликты по именам переменных — это настолько несущественная ерунда на фоне выкрутас, которые люди устраивают с include'ами и миллионом переопределений переменных из неожиданных мест, что ей можно пренебречь.


Обычно люди начинают страдать, когда у них роль без инклюда сталкивается с самой собой с инклюдом (а приоритеты разные! Изоляции нет!). А проблема в том, что был include_role, а не в разных приоритетах.

Мы в команде стараемся себе не позволять такого экстрима с инклудами как вы описываете, просто в силу того, что сложно. Но еще мы например пришли к ряду простых соглашений, которые немного упрощают жизнь, например, что все переменные ролей имеют префикс с названием роли или что мы используем `defaults` и не используем `vars` (в силу приоритетов). Хочется узнать есть ли у вас что-то похожее.

У нас не монорепа на всея компанию, так что сложность у нас частично управляется партиционированием. Мы используем git vendor для общих плейбук (мы называем их facilities), таки как базовые настройки системы, мониторинг и инфраструктура для интеграционных тестов. Сейчас как раз в одной из facilities оказалось, что переупотребили group_vars на уровне playbook, из-за чего страдают пользователи инвентори. Рефакторим.


В целом, главное правило анисбла — keep it simple. Если не получается simple, никакие соглашения не помогут. До определённого уровня изоляция хорошо работает на уровне запусков ansible (опять партиционирование).


Как-то так. Рядом соседний отдел страдает с chef'ом, и я хочу сказать, что у них проблем Ансибла нет, зато есть массовые проблемы из-за того, что инфраструктуру пытаются сопровождать как программу, с проблемами сопровождения сложного софта со сложными зависимостями, и это жрёт время нереально.

У нас вместо монорепы много ролей, но пока не договорились бывало, что запуск вида `-e target_ip=...` неожиданно затрагивал более одной роли. -С и --diff конечно спасают, но люди склонны совершать ошибки.
запуск вида -e target_ip=... неожиданно затрагивал более одной роли

Жёстко договориться об именовании переменных. Например, я делаю так (с dunder между):


role__some_variable

А то, что общее на несколько ролей/хостов свести к минимуму и задокументировать.

Есть такое. Чаще всего проблему можно уменьшить за счёт использования нескольких плейбук.

Можете описать ваш механизм git vendor?

разрешите вопрос как эксперту?
Хотя возможно он относится не к переменным, а к инвентори (предыдущей статье), впрочем до этой статьи я их не особо различал


Есть у меня две таски два play:


  1. запускает питон скрипт, который лезет в NMS, ищет неиспользуемые порты на всех коммутаторах и под каждый коммутатор создает host_vars со списком таких портов
  2. заходит на каждый коммутатор и гасит все порты из списка

Так вот если объединить эти два плея в один плейбук, то он не работает:
1-й play наполняет инвентори как пложено, но 2-й play этот свежий инвентори не видит и ничего не делает.
Если запускать в разных плейбуках, то все работает как полагается
Похоже inventory вычитывается в самом начале плейбука и больше не перечитывается


Так вот вопросы:
Есть возможность как то сказать ансиблу чтоб он перечитал Inventory?
Допустимо ли создавать такие плейбуки или это bad-design без вариантов?


p.s. в итоге я на ansible забил и ушел на питон целиком

А зачем inventory меняется?


Нужно проходить по каждому коммутатору из инвентори — будет 1 play по коммутаторам из (условно) 2 тасков:


1) проверили порты
2) сделали с нужными портами что-то

он не меняется. его изначально нет.
есть только хостнэймы свичей в ./hosts и creds-ы в group_vars
host_vars заполняется первым таском, который для кажого свича ходит за инфой к стороннему сервису

Первая таска, которая ходит за инфой к свитчу обычно называется 'setup' и она делает gather_facts. Вполне себе паттерн, хорошо работающий. Если это медленно, есть такая штука, как кеширование фактов.


А вот когда из нормального паттерна вы делаете самомодификацию инвентори, вот тут вот вам открывается цирк, и кони, и мотоциклисты на стенке.

ясно, спасибо
паттерн значит

Ага, понял, ну это, в целом, не для ансибла задача. Т.к. нельзя на свиче запускать код. Т.е. каждый хост в inventory — это машинка, куда можно по SSH зайти и запустить там модуль.


Но если надо, то можно просто на localhost запускать. Т.е. будет что-то такое:


- hosts: localhost
  gather_facts: false
  vars:
    switches:
      - sw1
      - sw2
      - sw3
  tasks:
    - command: "check_ports {{ item }}"
      loop: "{{ switches }}"
      register: switch_ports_status

    - command: "manage_ports {{ item.whatever }}"
      loop: "{{ switch_ports_status }}"

да для свичей код на локалхосте всегда
хотя некоторое оборудование уже начинает нативно поддерживать питонов и ансиблы на свичах. но пока экзотика


пример кода понятен разумеется
просто я не отличал до этого переменные через инвентори, факты и регистеред
потому и наворотил то, что наворотил
так то я register там тоже использую


  tasks:
    - name: cleanup old configurations
      local_action: file path="./tmp/{{ inventory_hostname }}.cfg" state=absent
      delegate_to: localhost

    - name: generate new configuruation
      template:
        src: ./template/hw-shut.j2
        dest: "./tmp/{{ inventory_hostname }}.cfg"
      when: 
        - interfaces is defined 
        - interfaces is not none
      register: task_1

    - name: merge the new configuration with existing one and save it
      napalm_install_config:
        provider: "{{ hwpv }}"
        config_file: "./tmp/{{ inventory_hostname }}.cfg"
        commit_changes: True
      when: task_1.changed

Хорошо что вопрос задал. Выступил лохом, конечно, но оно того стоило — разобрались.
Еще хорошо, что статью про мой тот велосипед забыл опубликовать на хабре, хотя она была готова еще год назад :)

Ох, ну совсем тяжко. Во-первых не идемпотентно. Во-вторых when.… changed называется handler. В третьих это надо было делать через setup и факты.


на самом деле надо было иметь просто модуль и/или юзать network_cli.

when.… changed называется handler

Вот не факт. В chef есть нотифаи :immediate, а в ансибле такого не завезли.


meta: flush_handlers тоже не всегда годится, т.к. нельзя отдельные хендлеры дёрнуть, только все разом в очереди.

А свитче не обязательно запускать код. Есть куча сетевых модулей.


А вот 'command' в таком месте — закопайте, пожалуйста. Не идемпотентно, транспорт на баше, etc.

Command это для примера, разумеется.


Не идемпотентно, транспорт на баше

Command не запускает шелл (shell запускает). А при желании, можно и запуск внешних скриптов сделать абсолютно идемпотентно.

Запуск скриптов можно сделать идемпотентно, но двухходовка query/command — грязный код. Иногда приходится, но грязно. А скрипты идемпотентными сами по себе вы не сделаете, потому что надо как-то репортить changed в Ansible, а у shell нет критериев для этого. Нормальный модуль просто отдаёт changed в выводе в конце, но если вы написали программу, которая так делает, то поздравляю, 80% модуля готово. Осталось написать приём параметров и положить в library/.

А скрипты идемпотентными сами по себе вы не сделаете, потому что надо как-то репортить changed в Ansible, а у shell нет критериев для этого.

Да ладно. Есть changed_when. Типа такого:


- command: foo
  register: foo_result
  changed_when: not foo_result.stdout|regex_search("\\n\s+No migrations to apply")
  notify: systemctl restart foo.target

Выглядит не очень — согласен. Но всё там идемпотентно и оно работает. Причёсывать и писать модуль django_migrate — ну вот хз. По каждому чиху модуль пилить? Разве что если есть время и перфекционизм.

Можно делать add_host, но лучше использовать как вам выше описали.

Специально для таких приключенцев есть group_host_vars плагин, про который целый раздел написан. А в целом — запись в host_vars из плейбуки это как самомодифицирующийся код, хорошо для малавари и спортивного программирования. В хорошем коде быть такого не должно.


Используйте факты, они для этого и придуманы.

про group_host_vars написано, что мы его не захотим. Зачем же Вы его мне советуете?
Ну и странно (для меня) inventory относить к коду.
Я как то привык относиться к inventory как к данным.
И нет ничего необычного и "приключенческого" когда одна функция пишет какие то данные, скажем в БД, а другая функция в том же коде эти данные потом читает и использует.
про гетфактс спасибо, но это будет сложней реализовать чем запускать плеи раздельно.
благо необходимость запускать этот велосипед требуется не чаще раза в год.

Записывая что-то в инвентори из плейбуки, которая с этой инвентори работает, вы делаете странное. Очень. Я посоветовал вам как этим странным жить дальше.


Ещё одна вещь, которую вы можете делать (не уверен, что это можно для коммутаторов) — можно иметь кастомные факты на целевом хосте (/etc/ansible/facts.d/).

Исходя из моего опыта лучшее что можно сделать с ансиблом для коммутаторов и прочих сетевых железяк — это перестать использовать ансибл для работы с ними :)
Тут возможно проблема не в самом ансибле (хотя вымораживает, что половина скриптов для версии 2.2 не работает в 2.9), а в качестве модулей под эти самые железяки написанные. Благо к 2020-му появилось множество самых разных интерфейсов и фреймворков для автоматизации моей деятельности, с более ожидаемыми "паттернами", нормальным дебагом и быстрее ансибла на порядки.


Тем не менее я очень благодарен Вам за ваши статьи и ответы на мои глупые вопросы.

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

не ну комплекс проблем с актуальностью и валидностью я предусмотрел :)
прям в доке к скрипту написал


перед запуском скрипта выполнить команду: rm -rf host_vars/

Ладно ладно я про паттерн понял уже. Это не ансибл странный, это у него паттерны

Генерация inventory в самом ansbile выглядит как экзотика, так что вполне возможно, что для вас проще будет пользоваться связкой python+fabric.
Нельзя использовать шаблоны в именах модулей, ключах словарей в переменных, именах переменных (которые по сути те же ключи).

Позвольте с этим не согласиться. Вот так работает. Но не уверен что стоит использовать.


- set_fact:
    var_{{ item.index }}: "{{ item.value }}"
  loop:
    - index: x1
      value: 'aaa'
    - index: x2
      value: 'bbb'
- debug: var=var_x1
- debug: var=var_x2

Может быть потому что факты — это переменные другого порядка :)


role/vars/ же используем для OS-dependent и для перевода человекочитаемых переменных в ansible-читаемые. типа предварительной фильтрации единого списка на два в зависимости от атрибута enabled/disabled


А вот зачем использовать vars: для отдельных тасков — это для меня совсем непонятно.

С праздником, однако!
Спасибо, годный материал, обязательно поковыряю в этом направлении.
Ну а в честь праздника предложения:


  • play ансибловый — это русская плюха. Нарисовал такой плюху, и, хрясь ей по серваку/группе/всем узлам! Без тестирования. :) :D
  • playbook, соответственно — набор плюх. А для граммарнаци и адептов английских терминов — плюхбук. :)
Что же использовать вместо include_role? meta?
  1. Переносить код в playbook'и. Если вам надо в пяти местах роль foo, вы в пяти местах указываете роль foo в списке ролей. Самый трушный путь.
  2. Если вы делаете include_role в цикле, то а) вы делаете очень вонючий код, б) чаще всего это можно переписать на отдельные циклы для одной таски в) аварийно есть include_tasks (который почти как include_role, но не тащит с собой прелюбопытнейшие дефолты include'женой роли).
  3. И как всегда, рефакторинг надо начинать со структуры входных данных. 90% случаев когда люди хотят странного, проистекает от того, что они принимают существующие структуры (инвентри и group_vars) как константу и хотят "дописать" не меняя существующее.
Я подключаю через include_role небольшие роли типа заббикс например. Если делать это в команде через плейбуки, то нужно явно знать какую роль подключать (если есть роль nginx, то нужно также подключить роль zabbix.nginx) или просто не знают об этом или забывают указывать.

Ну, изоляцию вы не напишите, а для проблемы, про которую вы говорите, мы просто делаем переиспользуемые плейбуки. Т.е. наш "common_monitoring" ставит всё, что нужно. Без дополнительных ролей, и на хостах, на которых нужно.


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


Когда вы из одной роли вызываете другую, то это уже code smell. Список ролей задаёт play, а не роль.

В момент, когда строковое значение передаётся как параметр 'msg' в модуль 'debug' (это не модуль, а action plugin, но для наших целей это не важно), строка {{ message }} прогоняется через шаблонизатор. Тот видит усы ({{ и }}) и заменяет их содержимым переменной message. Получается значение This is {{foobar }}. Дальше это значение прогоняется через шаблонизатор ещё раз получается This is {{ foo + "bar" }}. После ещё одного раунда тест становится This is foobar. После ещё одного раудна интерполяции текст не поменялся, т.е. интерполяция закончена. Получившаяся строка уходит в параметр msg модулю debug.

Что-то здесь не так. Я попробовал написать такое:


- debug:
     msg: '{{ message }}'
  vars:
    three_plus: 3 +
    message: '{% raw %}{{ {% endraw %}{{ three_plus }} 2 }}'

Согласно описанной в посте логике, значение message должно прогнаться через шаблонизатор ещё раз и в итоге выдать 5, но на практике получаем "msg": "{{ 3 + 2 }}" и никто ни на какие усы внимания не обращает.


Слишком доигрались с упрощёнными объяснениями?)


Или даже можно упростить пример:


- debug:
     msg: '{{ message }}'
  vars:
    message: '{% raw %}{{ 3 + 2 }}{% endraw %}'

Второго прогона шаблонизатора не случается и пятёрки тоже не случается.

raw возвращает unsafe-строки, unsafe-строки не прогоняются через шаблонизатор ещё раз.


Рекурсивная jinja — это не упрощение, оно так внутри устроено.

Ну тогда вот так:


- debug:
     msg: '{{ message }}'
  vars:
    ус1: "{"
    ус2: "{"
    message: '{{ ус1 }}{{ ус2 }} 3 + 2 }}'

Здесь тоже кто-то возвращает unsafe-строку?


Я не заглядывал во внутренности, но очень напрашивается бритва Оккама...

Нет, и вы показали любопытный пример, который я не мог разгадать и который вполне достоин ansible-bingo (утащил).


Для сравнения:


    - debug:
        var: '{{ message }}'
      vars:
        ус1: "{"
        ус2: "{"
        message: '{{ ус1 }}{{ ус2 }} 3 + 2 }}'

О, здесь бритва Оккама слегка подзатупилась) Но на первый взгляд похоже это особенности конкретно модуля debug:


Be aware that this option already runs in Jinja2 context and has an implicit {{ }} wrapping, so you should not be using Jinja2 delimiters unless you are looking for double interpolation.

implicit {{ }} это очень крутой wtf в ансибле.


    - name: Bingo 10
      debug:
      when: foo
      vars:
        foo: '{{ "n" + "o" }}'
      tags: [bingo10]
      # Enjoy your ADIS... Ansible.

судя по тому что, если сделать


        ус1: "{{"
        ус2: ""

и выдаёт "unexpected 'end of template"
то проверка на наличие "усов" и необходимость повторного прогона делается для каждого параметра отдельно, до подстановки, а не после на всю строку

Спасибо. Бесконечный источник веселья на Ансибл-пати во время Ансибл-бинго.


А, нет, это не источник wtf, это ожидаемое поведение. Если у вас незакрытая jinja, то это ошибка.

Как раз начал ковырять ansible, статьи сделали все немного понятнее, спасибо. Однако, после знакомства с "переменными" у меня только один вопрос: что надо было курить чтобы родить подобное? Отдельно, кстати, порадовался поведению private_role_vars = true.


Вооружённые этим знанием мы уже можем попытаться догадываться, что за фигня происходит в этом коде:

КМК, это просто (еще) один из косяков архитектуры, будь, например, оно в явном виде и проблем с понимаем было бы сильно меньше.


- name: This is loop
  loop: '{{ groups.all }} as item'
      tasks:
        - name: Do not do this
          file: path=hello.txt state=touch
          vars:
            ansible_host: '{{ hostvars[item].ansible_host }}'

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

Жаль они не знают про major версии где смело можно забить на всё что есть и сделать нормально.


include_role

Я пока пришел к мнению что если воспринимать их как вызовы функций со своими параметрами (которые в данном случае стоит класть в vars/, а не в defaults/ чтобы исключить влияние внешних переменных (но не всех, да) и для которых кстати вроде есть PR который позволит явно указать список параметров), то всё вроде вполне неплохо и ожидаемо (и всяко сильно лучше чем, например, private_role_vars = true и dependencies у ролей...), но надо помнить что оно по сути создает новую изолированную область и, например, все "dependencies" будут вызваны еще раз, что далеко не всегда желательно.


ЗЫ: Удивляет как настолько кривая система стала популярной.

Она популярна за те вещи, которые вы воспринимаете как данное и не замечаете. Восхитительно работающий транспорт, в котором вы про ssh вспоминаете, только чтобы заткнуть ругань про ключи, магический ansibollz, который "просто работает" на хост системе.


ЗЫ include_role не надо использовать, это preview и таковым оно останется навсегда.

Она популярна за те вещи, которые вы воспринимаете как данное и не замечаете

Наверное да, но вот если хочется чуть больше программирования, то это боль :( Жаль я не успел вовремя остановиться на нескольких тупых работающих плейбуках и решил добавить настроек… видимо придется еще и salt глянуть, там вроде это все погибче, правда что-то стандартные формулы не внушают доверия — та же mysql на убунте судя по всему кладет конфиг не туда куда должна… Блин, раньше я даже как-то не догадывался что у сисадминов все так же плохо как на frontend-е :D


ЗЫ include_role не надо использовать, это preview и таковым оно останется навсегда.

Правда? Очень жаль, если бы сделали параметры с проверкой (как у стандартных действий) и без их перекрытия через -e было бы очень удобно выносить повторяющие блоки. Банальный пример, у меня mysql через auth_socket и везде надо втыкать


check_implicit_admin: true
login_unix_socket: /var/run/mysqld/mysqld.sock

вместо этого было бы лучше обертку сделать.

Ансибл плохо подходит для программирования. Хотите логики — выносите в плагины, модули, внешние файлы.

Sign up to leave a comment.

Articles