DevOps

Как получить доступ к переменной из rescue в Ansible block

Решения для использования refresh_token из rescue секции и избежания дублирования кода в Ansible playbook.

3 ответа 1 просмотр

Как получить доступ к переменной, определенной в секции rescue, из секции block в Ansible? В моем случае мне нужно использовать refresh_token, который обновляется в секции rescue, для выполнения последующих задач (API-вызовов). Как избежать дублирования кода в секции rescue после обновления токена?

В Ansible переменные, определенные в секции rescue, недоступны в основной части блока из-за порядка выполнения задач. Секция rescue выполняется только при сбое задачи в block, поэтому переменные становятся доступны только после завершения rescue. Для решения вашей проблемы с refresh_token и избежания дублирования кода существуют несколько подходов.


Содержание


Основы работы блоков и rescue в Ansible

Ansible блоки (blocks) являются мощным инструментом для группировки связанных задач и обработки ошибок. Блоки позволяют структурировать ваш ansible playbook более логично и управляемо.

Когда задача внутри блока завершается с ошибкой, Ansible выполняет задачи из секции rescue. После успешного выполнения rescue-задач, управление передается обратно в блок, но уже после точки сбоя. Это фундаментальное поведение объясняет, почему переменные, установленные в rescue, недоступны в последующих задачах внутри того же блока.

иконка Stack Overflow

Как отмечено в документации Ansible, “секция rescue выполняется только при сбое задачи в блоке”, что создает естественное ограничение для доступа к переменным между этими секциями.


Проблема доступа к переменным из rescue в block

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

Это происходит потому, что:

  1. Задача в блоке завершается с ошибкой
  2. Выполняются rescue-задачи (включая обновление refresh_token)
  3. Блок считается выполненным (с ошибкой)
  4. Переменные из rescue доступны только на следующем запуске play

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


Решения для работы с токенами

1. Использование register и failed_when

Первый подход использует переменную register для захвата результата и управление потоком выполнения:

yaml
- 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. Разделение на отдельные плейбуки

Более элегантное решение - разделить логику на отдельные плейбуки:

yaml
- 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
yaml
- 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

Третий подход использует циклы для повторных попыток с обновленным токеном:

yaml
- 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 с обработкой токена

yaml
---
- 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

yaml
---
- 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 }}"
yaml
# 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
yaml
# 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. Использование ролей для управления токенами

Создайте отдельную роль для управления токенами:

yaml
# 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. Настройка переменных по умолчанию

Используйте файлы переменных для управления токенами:

yaml
# vars/main.yml
api_url: "https://api.example.com"
refresh_url: "https://api.example.com/refresh"
default_token: "initial_token"

3. Использование обработчиков для уведомлений

yaml
- 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. Логирование и отладка

Добавьте логирование для отслеживания работы с токенами:

yaml
- 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. Использование условий для предотвращения дублирования

yaml
- 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

Источники

  1. Stack Overflow — Анализ проблемы доступа к переменным из rescue в block: https://stackoverflow.com/questions/79918689/access-fact-defined-inside-rescue-section-from-block-section
  2. Ansible Documentation — Официальная документация по работе с блоками и обработкой ошибок: https://docs.ansible.com/ansible/latest/user_guide/playbooks_blocks.html
  3. 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.

Иконка Stack Overflow
Ansible Documentation / Портал документации

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

Авторы
Ж
Пользователь
U
Инженер DevOps
Источники
Stack Overflow / Платформа вопросов и ответов
Платформа вопросов и ответов
Ansible Documentation / Портал документации
Портал документации
Проверено модерацией
НейроОтветы
Модерация