Spec-Zone.ru › Ansible

Руководство по Module Helper

Введение

Создание модуля для Ansible в основном описано в имеющейся документации. Однако значительная её часть — это шаблонный код, который приходится повторять каждый раз. Здесь на помощь приходит ModuleHelper: большая часть этого шаблонного кода уже написана.

Быстрый старт

См. пример из документации Ansible, написанный с использованием ModuleHelper. Но имейте в виду, что в нём представлены не все возможности MH:

from ansible_collections.community.general.plugins.module_utils.module_helper import ModuleHelper


class MyTest(ModuleHelper):
    module = dict(
        argument_spec=dict(
            name=dict(type='str', required=True),
            new=dict(type='bool', required=False, default=False),
        ),
        supports_check_mode=True,
    )

    def __run__(self):
        self.vars.original_message = ''
        self.vars.message = ''
        if self.check_mode:
            return
        self.vars.original_message = self.vars.name
        self.vars.message = 'goodbye'
        self.changed = self.vars['new']
        if self.vars.name == "fail me":
            self.do_raise("You requested this to fail")


def main():
    MyTest.execute()


if __name__ == '__main__':
    main()

Module Helper

Введение

ModuleHelper — это оболочка для стандартного AnsibleModule, предоставляющая дополнительные функции и удобства. Базовая структура модуля, использующего ModuleHelper, показана в разделе Быстрый старт выше, но в ней участвуют и другие элементы.

from ansible_collections.community.general.plugins.module_utils.module_helper import ModuleHelper


class MyTest(ModuleHelper):
    # behavior for module paramaters ONLY, see below for further information
    output_params = ()
    change_params = ()
    diff_params = ()
    facts_params = ()

    facts_name = None   # used if generating facts, from parameters or otherwise

    module = dict(
        argument_spec=dict(...),
        # ...
    )

После импорта класса ModuleHelper необходимо объявить собственный класс, расширяющий его.

См. также

Существует вариант под названием StateModuleHelper, который дополняет возможности MH. Подробнее см. ниже в разделе StateModuleHelper.

Проще всего задать модуль, создав переменную класса module со словарём, содержащим точные аргументы, которые будут переданы в качестве параметров в AnsibleModule. Если вы предпочитаете самостоятельно создавать объект AnsibleModule, просто присвойте его переменной класса module. MH также принимает параметр module в конструкторе. Если этот параметр используется, он переопределяет переменную класса. Параметр также может быть типа dict или AnsibleModule.

Помимо определения модуля, можно использовать другие переменные для управления поведением MH. Эти переменные следует задавать в самом начале класса; их назначение объясняется в этом документе.

Основная логика MH выполняется в методе ModuleHelper.run(), который выглядит так:

@module_fails_on_exception
def run(self):
    self.__init_module__()
    self.__run__()
    self.__quit_module__()
    output = self.output
    if 'failed' not in output:
        output['failed'] = False
    self.module.exit_json(changed=self.has_changed(), **output)

Модуль должен реализовать метод ModuleHelper.__run__(); большинство модулей смогут выполнять свои действия, реализовав только этот метод MH. Однако в некоторых случаях может понадобиться выполнить действия до или после основных задач. В таких случаях следует реализовать соответственно ModuleHelper.__init_module__() и ModuleHelper.__quit_module__().

Обратите внимание: результат формируется методом self.output, который является методом @property. По умолчанию это свойство собирает все переменные, помеченные для вывода, и возвращает их значения в словаре. Кроме того, стандартный self.output также обрабатывает facts Ansible и режим diff. Также обратите внимание: статус изменений формируется методом self.has_changed(), обычно вычисляемым на основе переменных, помеченных для отслеживания изменений их содержимого.

См. также

Подробнее см. в разделах Параметры, переменные и вывод и Обработка изменений ниже.

См. также

Подробнее о декораторе @module_fails_on_exception см. ниже.

Пример из раздела Быстрый старт можно записать и так:

def __init_module__(self):
    self.vars.original_message = ''
    self.vars.message = ''

def __run__(self):
    if self.check_mode:
        return
    self.vars.original_message = self.vars.name
    self.vars.message = 'goodbye'
    self.changed = self.vars['new']

def __quit_module__(self):
    if self.vars.name == "fail me":
        self.do_raise("You requested this to fail")

Обратите внимание, что здесь нет вызовов module.exit_json() и module.fail_json(): если модуль завершается с ошибкой, выбросьте исключение. Для этого можно использовать вспомогательный метод self.do_raise() или выбросить исключение обычным способом в Python. Если исключение не выброшено, модуль считается успешно выполненным.

См. также

Подробнее об исключениях см. ниже в разделе Исключения.

В модулях Ansible должна быть функция main() и стандартная проверка для '__main__'. При использовании MH это должно выглядеть так:

def main():
    MyTest.execute()


if __name__ == '__main__':
    main()

Метод класса execute() — это всего лишь сокращённая запись для:

m = MyTest()
m.run()

При необходимости в execute() можно передать параметр AnsibleModule.

Параметры, переменные и вывод

Все параметры автоматически становятся переменными в атрибуте self.vars, имеющем тип VarDict. Использование self.vars позволяет получить единый механизм доступа к параметрам, а также предоставить переменные в качестве возвращаемых значений модуля. Как описано в Руководстве по VarDict, с переменными в VarDict связаны метаданные. Один из атрибутов этих метаданных помечает переменную для вывода, и MH использует его для формирования возвращаемых значений модуля.

Примечание

Класс VarDict появился в community.general 7.1.0 как часть самого ModuleHelper. Однако его выделили в отдельную утилиту, описанную в Руководстве по VarDict, а старую реализацию удалили в community.general 11.0.0.

В некоторых фрагментах кода всё ещё могут встречаться переменные класса use_old_vardict и mute_vardict_deprecation, использовавшиеся при переходе на новую реализацию. Начиная с community.general 11.0.0 они больше не применяются, поэтому их можно безопасно удалить из кода.

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

class MyTest(ModuleHelper):
    output_params = ('state', 'name')
    ...

Важно

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

Ещё одна полезная функция MH, основанная на использовании VarDict, — автоматическое отслеживание изменений при установке метаданных change=True. Чтобы включить эту функцию для параметров модуля, снова перечислите их в переменной класса change_params.

class MyTest(ModuleHelper):
    # example from community.general.xfconf
    change_params = ('value', )
    ...

Важно

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

См. также

Подробнее об этом см. ниже в разделе Обработка изменений.

Аналогично, чтобы использовать режим diff Ansible, можно задать для параметров модуля метаданные diff=True и diff_params. В этом случае MH автоматически сформирует вывод diff для переменных, значения которых изменились.

class MyTest(ModuleHelper):
    diff_params = ('value', )

    def __run__(self):
        # example from community.general.gio_mime
        self.vars.set_meta("handler", initial_value=gio_mime_get(self.runner, self.vars.mime_type), diff=True, change=True)

Важно

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

Кроме того, если модуль должен возвращать факты, а не значения, используйте для параметров модуля метаданные fact=True и fact_params. Также необходимо указать facts_name, например:

class VolumeFacts(ModuleHelper):
    facts_name = 'volume_facts'

    def __init_module__(self):
        self.vars.set("volume", 123, fact=True)

В результате будет сформирован факт Ansible, например:

- name: Obtain volume facts
  some.collection.volume_facts:
    # parameters

- name: Print volume facts
  debug:
    msg: Volume fact is {{ ansible_facts.volume_facts.volume }}

Важно

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

Важно

Если facts_name не задан, модуль не формирует факты.

Обработка изменений

В MH есть много способов указать на изменение при выполнении модуля. Рассмотрим их:

Отслеживание изменений переменных

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

# using __init_module__() as example, it works the same in __run__() and __quit_module__()
def __init_module__(self):
    # example from community.general.ansible_galaxy_install
    self.vars.set("new_roles", {}, change=True)

    # example of "hidden" variable used only to track change in a value from community.general.gconftool2
    self.vars.set('_value', self.vars.previous_value, output=False, change=True)

    # enable change-tracking without assigning value
    self.vars.set_meta("new_roles", change=True)

    # if you must forcibly set an initial value to the variable
    self.vars.set_meta("new_roles", initial_value=[])
    ...

Если конечное значение любой переменной, помеченной change, отличается от её исходного значения, MH вернёт changed=True.

Указание изменений с помощью changed

Чтобы указать на изменение непосредственно в коде, используйте свойство self.changed в MH. Обратите внимание: в MH это метод @property, имеющий и геттер, и сеттер. По умолчанию это скрытое поле имеет значение False.

Итоговое изменение

Итоговый результат работы модуля определяется методом self.has_changed() и вычисляется как логическое ИЛИ между self.changed и изменением, рассчитанным на основе self.vars.

Исключения

В MH вместо вызова module.fail_json() можно просто выбросить исключение. Выходные переменные собираются так же, как и при успешном выполнении. Однако при желании можно задать выходные переменные специально для этого исключения.

from ansible_collections.community.general.plugins.module_utils.module_helper import ModuleHelperException

def __init_module__(self):
    if not complex_validation():
        self.do_raise("Validation failed!")

    # Or passing output variables
    awesomeness = calculate_awesomeness()
    if awesomeness > 1000:
        self.do_raise("Over awesome, I cannot handle it!", update_output={"awesomeness": awesomeness})
        # which is just a convenience shortcut for
        raise ModuleHelperException("...", update_output={...})

Все исключения, производные от Exception, перехватываются и преобразуются в вызов fail_json(). Однако при необходимости можно самостоятельно вызвать self.module.fail_json() — это сработает, но имейте в виду, что в этом случае выходные переменные не будут обработаны автоматически.

Внутри do_raise() просто выбрасывает ModuleHelperException. Если для кода нужна специализированная обработка ошибок, лучше всего расширить этот класс и выбрасывать его при необходимости.

StateModuleHelper

Во многих модулях используется параметр state, который определяет конкретное действие модуля, например state=present или state=absent для установки или удаления пакетов. Используя StateModuleHelper, можно написать код по образцу фрагмента из gconftool2 ниже:

from ansible_collections.community.general.plugins.module_utils.module_helper import StateModuleHelper

class GConftool(StateModuleHelper):
    ...
    module = dict(
        ...
    )

    def __init_module__(self):
        self.runner = gconftool2_runner(self.module, check_rc=True)
        ...

        self.vars.set('previous_value', self._get(), fact=True)
        self.vars.set('value_type', self.vars.value_type)
        self.vars.set('_value', self.vars.previous_value, output=False, change=True)
        self.vars.set_meta('value', initial_value=self.vars.previous_value)
        self.vars.set('playbook_value', self.vars.value, fact=True)

    ...

    def state_absent(self):
        with self.runner("state key", output_process=self._make_process(False)) as ctx:
            ctx.run()
            self.vars.set('run_info', ctx.run_info, verbosity=4)
        self.vars.set('new_value', None, fact=True)
        self.vars._value = None

    def state_present(self):
        with self.runner("direct config_source value_type state key value", output_process=self._make_process(True)) as ctx:
            ctx.run()
            self.vars.set('run_info', ctx.run_info, verbosity=4)
        self.vars.set('new_value', self._get(), fact=True)
        self.vars._value = self.vars.new_value

Обратите внимание: метод __run__() реализован в StateModuleHelper, поэтому вам нужно реализовать только методы state_<state_value>. В приведённом выше примере у community.general.gconftool2 только два состояния: present и absent, поэтому state_present() и state_absent().

Если управляющий параметр называется не state, как в модуле community.general.jira, просто сообщите об этом SMH:

class JIRA(StateModuleHelper):
    state_param = 'operation'

    def operation_create(self):
        ...

    def operation_search(self):
        ...

Наконец, если модуль вызывается с state=somevalue, а метод state_somevalue не реализован, SMH попытается вызвать метод с именем __state_fallback__(). По умолчанию этот метод выбрасывает ValueError, указывая, что метод не найден. Разумеется, этот метод можно переопределить, задав реализацию по умолчанию, как в community.general.locale_gen:

def __state_fallback__(self):
    if self.vars.state_tracking == self.vars.state:
        return
    if self.vars.ubuntu_mode:
        self.apply_change_ubuntu(self.vars.state, self.vars.name)
    else:
        self.apply_change(self.vars.state, self.vars.name)

У этого модуля есть только состояния present и absent, и для обоих используется код резервного метода.

Примечание

Имя резервного метода не изменится, даже если задать другое значение state_param.

Другие удобства

Делегирование AnsibleModule

Перечисленные ниже свойства и методы MH без изменений делегируются базовому экземпляру AnsibleModule в self.module:

  • check_mode
  • get_bin_path()
  • warn()
  • deprecate()

Кроме того, MH также делегирует:

  • diff_mode в self.module._diff
  • verbosity в self.module._verbosity

Начиная с community.general 10.3.0, MH также делегирует метод debug в self.module. Если в каком-либо существующем модуле уже определён атрибут debug, появится предупреждение с просьбой переименовать его. Начиная с выпуска community.general 12.0.0 делегирование будет выполняться безусловно и переопределит любые существующие методы или свойства подклассов.

Декораторы

Следующие декораторы следует использовать только внутри класса ModuleHelper.

@cause_changes

Этот декоратор управляет тем, будет ли результат выполнения метода указывать на изменение в выводе модуля. Если метод завершился без выброса исключения, считается, что он выполнен успешно; в противном случае он считается завершившимся с ошибкой.

Декоратор принимает параметр when, которому можно присвоить одно из трёх значений: success, failure и always. Также есть два устаревших параметра — on_success и on_failure, которые будут объявлены устаревшими, поэтому не используйте их. Значение changed в выводе модуля будет установлено в True:

  • when="success", если метод завершился без выброса исключения.
  • when="failure", если метод выбросил исключение.
  • when="always" независимо от того, выбросил метод исключение или нет.
from ansible_collections.community.general.plugins.module_utils.module_helper import cause_changes

# adapted excerpt from the community.general.jira module
class JIRA(StateModuleHelper):
    @cause_changes(when="success")
    def operation_create(self):
        ...

Если when имеет другое значение или параметры не указаны, декоратор не оказывает никакого эффекта.

@module_fails_on_exception

Если метод, использующий этот декоратор, выбрасывает исключение, декоратор перехватывает его текстовое сообщение и использует его при вызове self.module.fail_json(). В большинстве случаев этот декоратор не нужен, поскольку ModuleHelper.run() уже использует его.

@check_mode_skip

Если модуль выполняется в режиме проверки, этот декоратор не позволит выполнить метод. В этом случае возвращаемое значение — None.

from ansible_collections.community.general.plugins.module_utils.module_helper import check_mode_skip

# adapted excerpt from the community.general.locale_gen module
class LocaleGen(StateModuleHelper):
    @check_mode_skip
    def __state_fallback__(self):
        ...

@check_mode_skip_returns

Этот декоратор похож на предыдущий, но позволяет разработчику управлять возвращаемым значением метода при выполнении в режиме проверки. Он используется с одним из двух параметров. Первый — callable; в режиме проверки возвращаемым значением будет callable(self, *args, **kwargs), где self — это экземпляр ModuleHelper, а объединение args и kwargs содержит все параметры, переданные методу.

Другой вариант — использовать параметр value; в этом случае в режиме проверки метод вернёт value.

Ссылки

  • Руководство разработчика Ansible
  • Создание модуля
  • Возврат фактов Ansible
  • Руководство по VarDict

Добавлено в версии 3.1.0.

© 2012–2018 Michael DeHaan
© 2018–2025 Red Hat, Inc.
Licensed under the GNU General Public License version 3.
https://docs.ansible.com/projects/ansible/latest/collections/community/general/docsite/guide_modulehelper.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API