Как получить доступ к переменной из rescue в Ansible block
Решения для использования refresh_token из rescue секции и избежания дублирования кода в Ansible playbook.
Как получить доступ к переменной, определенной в секции rescue, из секции block в Ansible? В моем случае мне нужно использовать refresh_token, который обновляется в секции rescue, для выполнения последующих задач (API-вызовов). Как избежать дублирования кода в секции rescue после обновления токена?
В Ansible переменные, определенные в секции rescue, недоступны в основной части блока из-за порядка выполнения задач. Секция rescue выполняется только при сбое задачи в block, поэтому переменные становятся доступны только после завершения rescue. Для решения вашей проблемы с refresh_token и избежания дублирования кода существуют несколько подходов.
Содержание
- Основы работы блоков и rescue в Ansible
- Проблема доступа к переменным из rescue в block
- Решения для работы с токенами
- Практические примеры реализации
- Рекомендации по оптимизации
Основы работы блоков и rescue в Ansible
Ansible блоки (blocks) являются мощным инструментом для группировки связанных задач и обработки ошибок. Блоки позволяют структурировать ваш ansible playbook более логично и управляемо.
Когда задача внутри блока завершается с ошибкой, Ansible выполняет задачи из секции rescue. После успешного выполнения rescue-задач, управление передается обратно в блок, но уже после точки сбоя. Это фундаментальное поведение объясняет, почему переменные, установленные в rescue, недоступны в последующих задачах внутри того же блока.
Как отмечено в документации Ansible, “секция rescue выполняется только при сбое задачи в блоке”, что создает естественное ограничение для доступа к переменным между этими секциями.
Проблема доступа к переменным из rescue в block
Основная сложность заключается в том, что когда задача в блоке завершается с ошибкой (например, из-за устаревшего refresh_token), управление передается в rescue. После обновления токена в rescue, вы не можете использовать этот обновленный токен для выполнения последующих задач в том же блоке.
Это происходит потому, что:
- Задача в блоке завершается с ошибкой
- Выполняются rescue-задачи (включая обновление refresh_token)
- Блок считается выполненным (с ошибкой)
- Переменные из rescue доступны только на следующем запуске play
В результате вы не можете просто использовать обновленный токен в последующих задачах блока, что приводит к необходимости дублирования кода.
Решения для работы с токенами
1. Использование register и failed_when
Первый подход использует переменную register для захвата результата и управление потоком выполнения:
- name: Основные задачи с обработкой токена
block:
- name: Выполнение API-вызова с токеном
uri:
url: "https://api.example.com/data"
method: GET
headers:
Authorization: "Bearer {{ refresh_token }}"
validate_certs: no
register: api_result
failed_when: api_result.status == 401 # Токен устарел
- name: Последующие задачи с обновленным токеном
debug:
msg: "Выполняем задачи с новым токеном"
# Здесь вы не можете использовать обновленный токен!
rescue:
- name: Обновление refresh_token
uri:
url: "https://api.example.com/refresh"
method: POST
body_format: json
body:
refresh_token: "{{ refresh_token }}"
register: refresh_result
changed_when: refresh_result.status == 200
- name: Повторное выполнение основных задач
include_tasks: main_tasks.yml
vars:
refresh_token: "{{ refresh_result.json.new_token }}"
2. Разделение на отдельные плейбуки
Более элегантное решение - разделить логику на отдельные плейбуки:
- name: Основной плейбук
hosts: target
tasks:
- name: Выполнение с проверкой токена
include_tasks: execute_with_token.yml
vars:
refresh_token: "{{ refresh_token }}"
- name: Последующие задачи
debug:
msg: "Выполняем после успешного API-вызова"
when: api_result.status == 200
- name: execute_with_token.yml
tasks:
- name: API-вызов с токеном
uri:
url: "https://api.example.com/data"
method: GET
headers:
Authorization: "Bearer {{ refresh_token }}"
validate_certs: no
register: api_result
failed_when: api_result.status == 401
- name: Обновление токена при необходимости
block:
- name: Проверка статуса ответа
fail:
msg: "Токен устарел, требуется обновление"
when: api_result.status == 401
rescue:
- name: Обновление refresh_token
uri:
url: "https://api.example.com/refresh"
method: POST
body_format: json
body:
refresh_token: "{{ refresh_token }}"
register: refresh_result
changed_when: refresh_result.status == 200
- name: Повторный вызов с новым токеном
uri:
url: "https://api.example.com/data"
method: GET
headers:
Authorization: "Bearer {{ refresh_result.json.new_token }}"
validate_certs: no
register: api_result
vars:
refresh_token: "{{ refresh_result.json.new_token }}"
3. Использование циклов с условием retries
Третий подход использует циклы для повторных попыток с обновленным токеном:
- name: API-вызов с автоматическим обновлением токена
block:
- name: Основной API-вызов
uri:
url: "https://api.example.com/data"
method: GET
headers:
Authorization: "Bearer {{ refresh_token }}"
validate_certs: no
register: api_result
failed_when: api_result.status == 401
- name: Логика для обновленного токена
debug:
msg: "Успешное выполнение с токеном"
when: api_result.status == 200
rescue:
- name: Обновление refresh_token
uri:
url: "https://api.example.com/refresh"
method: POST
body_format: json
body:
refresh_token: "{{ refresh_token }}"
register: refresh_result
changed_when: refresh_result.status == 200
- name: Установка нового токена
set_fact:
refresh_token: "{{ refresh_result.json.new_token }}"
- name: Повторный вызов из родительского блока
meta: end_play
when: refresh_result.status == 200
always:
- name: Проверка результата
debug:
msg: "API-вызов завершен со статусом: {{ api_result.status | default('не выполнен') }}"
Практические примеры реализации
Пример 1: Минимальный playbook с обработкой токена
---
- name: Пример работы с refresh_token
hosts: localhost
gather_facts: false
vars:
refresh_token: "initial_token_123"
tasks:
- name: Основные задачи с обработкой
block:
- name: API-вызов
uri:
url: "https://httpbin.org/status/401"
method: GET
headers:
Authorization: "Bearer {{ refresh_token }}"
register: api_result
failed_when: api_result.status == 401
- name: Эта задача не выполнится
debug:
msg: "Успешное выполнение"
rescue:
- name: Обновление токена
uri:
url: "https://httpbin.org/status/200"
method: POST
register: refresh_result
changed_when: true
- name: Установка нового токена
set_fact:
refresh_token: "new_token_456"
- name: Повторное выполнение
debug:
msg: "Токен обновлен, но нельзя использовать в этом же блоке"
# Здесь нельзя использовать refresh_token в других задачах блока!
always:
- name: Финальный результат
debug:
msg: "Токен после rescue: {{ refresh_token }}"
Пример 2: Переиспользование задач через include_tasks
---
- name: Пример с переиспользованием задач
hosts: localhost
gather_facts: false
vars:
refresh_token: "initial_token"
tasks:
- name: Выполнение API-операций
block:
- name: Первая попытка
include_tasks: api_call.yml
vars:
api_url: "https://api.example.com/data"
token: "{{ refresh_token }}"
- name: Вторая операция
debug:
msg: "Вторая операция требует обновленный токен"
# Здесь нельзя использовать обновленный токен!
rescue:
- name: Обновление токена
include_tasks: refresh_token.yml
vars:
old_token: "{{ refresh_token }}"
- name: Повторное выполнение с новым токеном
include_tasks: api_call.yml
vars:
api_url: "https://api.example.com/data"
token: "{{ refreshed_token }}"
# api_call.yml
- name: Выполнение API-вызова
uri:
url: "{{ api_url }}"
method: GET
headers:
Authorization: "Bearer {{ token }}"
validate_certs: no
register: api_result
failed_when: api_result.status == 401
- name: Логика успешного вызова
debug:
msg: "API вызов успешный"
when: api_result.status == 200
# refresh_token.yml
- name: Обновление токена
uri:
url: "https://api.example.com/refresh"
method: POST
body_format: json
body:
refresh_token: "{{ old_token }}"
register: refresh_result
- name: Установка обновленного токена
set_fact:
refreshed_token: "{{ refresh_result.json.new_token }}"
Рекомендации по оптимизации
1. Использование ролей для управления токенами
Создайте отдельную роль для управления токенами:
# roles/token_management/tasks/main.yml
- name: Проверка токена
uri:
url: "{{ api_url }}"
method: GET
headers:
Authorization: "Bearer {{ token }}"
validate_certs: no
register: api_result
failed_when: api_result.status == 401
- name: Обновление токена при необходимости
block:
- name: Проверка необходимости обновления
fail:
msg: "Токен устарел"
when: api_result.status == 401
rescue:
- name: Обновление refresh_token
uri:
url: "{{ refresh_url }}"
method: POST
body_format: json
body:
refresh_token: "{{ token }}"
register: refresh_result
- name: Установка нового токена
set_fact:
token: "{{ refresh_result.json.new_token }}"
- name: Повторный вызов
uri:
url: "{{ api_url }}"
method: GET
headers:
Authorization: "Bearer {{ token }}"
validate_certs: no
register: api_result
2. Настройка переменных по умолчанию
Используйте файлы переменных для управления токенами:
# vars/main.yml
api_url: "https://api.example.com"
refresh_url: "https://api.example.com/refresh"
default_token: "initial_token"
3. Использование обработчиков для уведомлений
- name: API-вызов с обработкой токена
uri:
url: "{{ api_url }}"
method: GET
headers:
Authorization: "Bearer {{ token }}"
validate_certs: no
register: api_result
failed_when: api_result.status == 401
notify: refresh_token
handlers:
- name: refresh_token
block:
- name: Обновление токена
uri:
url: "{{ refresh_url }}"
method: POST
body_format: json
body:
refresh_token: "{{ token }}"
register: refresh_result
changed_when: true
- name: Установка нового токена
set_fact:
token: "{{ refresh_result.json.new_token }}"
4. Логирование и отладка
Добавьте логирование для отслеживания работы с токенами:
- name: Логирование токена
debug:
msg: "Используем токен: {{ token | truncate(10) }}"
when: token is defined
- name: API-вызов
uri:
url: "{{ api_url }}"
method: GET
headers:
Authorization: "Bearer {{ token }}"
validate_certs: no
register: api_result
failed_when: api_result.status == 401
- name: Логирование результата
debug:
msg: "Статус API: {{ api_result.status }}"
5. Использование условий для предотвращения дублирования
- name: Выполнение с обработкой ошибок
block:
- name: API-вызов
uri:
url: "{{ api_url }}"
method: GET
headers:
Authorization: "Bearer {{ token }}"
validate_certs: no
register: api_result
failed_when: api_result.status == 401
- name: Успешные задачи
debug:
msg: "Задачи с текущим токеном"
when: api_result.status == 200
rescue:
- name: Обновление токена
uri:
url: "{{ refresh_url }}"
method: POST
body_format: json
body:
refresh_token: "{{ token }}"
register: refresh_result
changed_when: refresh_result.status == 200
- name: Установка нового токена
set_fact:
token: "{{ refresh_result.json.new_token }}"
- name: Повторное выполнение только при необходимости
include_tasks: retry_api_call.yml
when:
- refresh_result.status == 200
- api_result.status == 401
Источники
- Stack Overflow — Анализ проблемы доступа к переменным из rescue в block: https://stackoverflow.com/questions/79918689/access-fact-defined-inside-rescue-section-from-block-section
- Ansible Documentation — Официальная документация по работе с блоками и обработкой ошибок: https://docs.ansible.com/ansible/latest/user_guide/playbooks_blocks.html
- Ansible Documentation — Руководство по использованию register и переменных: https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html
Заключение
Проблема доступа к переменным из секции rescue в блоке Ansible возникает из-за порядка выполнения задач. Для работы с refresh_token и избежания дублирования кода рекомендуется использовать разделение на отдельные плейбуки или задачи, переиспользование логики через include_tasks, а также создание отдельных ролей для управления токенами. Оптимальным решением является создание модульной структуры, где каждый аспект работы с токенами вынесен в отдельные компоненты, что обеспечивает переиспользование кода и упрощает поддержку вашего ansible playbook.
В Ansible существует фундаментальное ограничение: переменные, установленные в секции rescue, недоступны в секции block из-за порядка выполнения задач. Rescue-блоки выполняются только после сбоя задачи в block, поэтому переменные становятся доступны только после завершения rescue. Для избежания дублирования кода после обновления токена рекомендуется использовать отдельные задачи или переиспользование логики через include_tasks. Пример минимального плейбука показывает, что переменная, установленная в rescue, становится доступной только после выполнения rescue-задач, но не может быть использована в последующих задачах внутри block.


Официальная документация Ansible объясняет, что блоки (blocks) предназначены для группировки задач и обработки ошибок. Секция rescue выполняется только при сбое задачи в блоке. Переменные, установленные в rescue, имеют ограниченную область видимости и недоступны в основной части блока. Для управления токенами и избежания дублирования кода рекомендуется использовать отдельные задачи или переиспользование через include_tasks. Ansible предлагает гибкие механизмы для обработки ошибок, но требует понимания порядка выполнения задач для эффективного использования.
