Примерно месяц назад у меня был выбор: писать ли модуль для puppet "в стол" (то есть, для внутренней инфраструктуры) или делать его универсальным, открывать исходники и публиковать его на puppet forge. Конечно, быстрее и проще было бы набросать быстро под себя 2-3 класса и на том успокоиться, но вот опыт, который был получен в процессе публикации модуля, является ценным и хочется им поделиться. В рунете никакой информации по использованию puppet development kit (далее PDK) нет, так что можно считать это своеобразным туториалом.


О чём статья


В процессе разработки модуля (а точнее, двух) я открыл для себя PDK, который сильно облегчает как разработку, так и сопровождение модулей. А именно:


  • Автоматическое форматирование metadata.json при обновлении последнего
  • Генерация конфигурации для различных систем CI, которые умеют следующее:
    • Проверка ruby кода линтером rubocop
    • Запуск юнит тестов
    • При определённых условиях — автоматическая заливка рабочего кода на puppet forge
  • Генерация документации на основе тегов в коментариях при помощи yard
  • Плашка [PDK] для модуля на puppet forge. Мелочь, а приятно!

Всех заинтересованных прошу под кат!


В качестве примеров


Если хочется в процессе чтения посмотреть и пощупать, что же имеется в виду, можно открыть один из двух (или оба) упомянутых модуля: clickhouse и xmlsimple. Оба они были разработаны при помощи PDK и других инструментов, описаных в статье.


Содержание



Что же такое PDK


Из официальной документации:


Create a complete module with classes, defined types, and tasks, and test and validate your work as you go. PDK provides a complete module structure, templates for classes, defined types, and tasks, and a testing infrastructure. You can validate and test your module against various operating systems and multiple Puppet versions.

В моём вольном переводе:


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

Звучит неплохо? Что ж, так оно и есть на самом деле. До момента, когда я начал работу над модулем, который было решено писать сразу для open source, я не подозревал о данном инструменте, а теперь я намерен перевести на использование PDK всю внутреннюю инфраструктуру.


Опишу, как его поставить, и какие инструменты и команды он в себе содержит.


Установка


Официальная страница установки. По этой ссылке Вы почти гарантированно найдёте правильный способ установить PDK на свой хост. Если же по какой-то причине не повезло и Вашей ОС там нету, всегда есть окольный путь в виде:


gem install pdk

По сути, PDK — это всего лишь гем, и ставится он именно так.


Содержимое PDK


В целом, PDK — не более чем набор гемов для облегчения разработки модулей. Содержит он следующие инструменты:


Утилита Описание
metadata-json-lint Проверяет соответствие metadata.json на соответствие стайл-гайдам puppet
pdk Инструмент для генерации и тестирования модулей и их содержимого (классов, типов и т.д.) из командной строки
puppet-lint Проверяет код puppet на соответствие стайл-гайдов Puppet Language
puppet-syntax Проверят корректность синтаксиса манифестов
puppetlabs_spec_helper Предоставляет классы, методы и таски Rake для spec-тестов кода puppet
rspec-puppet Тестирует поведение puppet во время компиляции манифестов в каталог ресурсов (?)
rspec-puppet-facts Позволяет запускать rspec-puppet с заданными пользователем puppet-facts

Создаём модуль


PDK установлен, теперь можно и поиграться. Простейшая команда pdk help отобразит доступные команды. Предположим, что мы находимся в папке, где у Вас находятся все остальные модули. Тогда давайте создадим новый:


$ pdk new module --template-url=https://github.com/puppetlabs/pdk-templates.git
***
We need to create the metadata.json file for this module, so we're going to ask you 5 questions.
***
[Q 1/5] If you have a name for your module, add it here.
--> dummy
[Q 2/5] If you have a Puppet Forge username, add it here.
--> felixoid
[Q 3/5] Who wrote this module?
--> Mikhail f. Shiryaev
[Q 4/5] What license does this module code fall under?
--> MIT
[Q 5/5] What operating systems does this module support?
--> RedHat based Linux, Debian based Linux, Windows

Metadata will be generated based on this information, continue? Yes
pdk (INFO): Module 'dummy' generated at path '/tmp/dummy', from template 'https://github.com/puppetlabs/pdk-templates.git'.

Утилита задаёт вопросы для заполнения файла metadata.json, и на выходе имеем ровно то, что и указано: модуль и вспомогательные файлы, составленные по шаблонам из гита.


Небольшая ремарка — темлейты меняются достаточно часто, в том числе в последнее время были поправлены некоторые критичные баги. Поэтому лучше использовать не умолчания из установленного PDK, а последнюю версию. Правда, имеется обратная сторона: при использовании аргумента --template-url PDK добавляет этот параметр в файл ~.pdk/cache/answers.json и, судя по задержкам при дальнейшем выполнении любой из команд pdk, пытается их скачать. Так что либо убирайте этот параметр из answers.json, либо не используйте его при создании модуля и изменяйте в metadata.json.


Пройдёмся по дальнейшим действиям, которые можно выполнить при помощи PDK.


new class


$ pdk new class dummy::class
pdk (INFO): Creating '/tmp/dummy/manifests/class.pp' from template.
pdk (INFO): Creating '/tmp/dummy/spec/classes/class_spec.rb' from template.
$ cat manifests/class.pp
# A description of what this class does
#
# @summary A short summary of the purpose of this class
#
# @example
#   include dummy::class
class dummy::class {
}
$ cat spec/classes/class_spec.rb
require 'spec_helper'

describe 'dummy::class' do
  on_supported_os.each do |os, os_facts|
    context "on #{os}" do
      let(:facts) { os_facts }

      it { is_expected.to compile }
    end
  end
end

Эта команда создаёт 2 файла: непосредственно манифест для класса и spec-файл для его тестирования. На тегах для документации я попозже остановлюсь немного подробнее.


new defined_type


$ pdk new defined_type type
pdk (INFO): Creating '/tmp/dummy/manifests/type.pp' from template.
pdk (INFO): Creating '/tmp/dummy/spec/defines/type_spec.rb' from template.

Всё то же самое: манифест для типа ресурса и spec-файл.


new provider & task


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


Генерация документации при помощи puppet-strings


Я не очень понимаю, почему puppet strings не является частью инструментария PDK, однако се ля ви. Если при разработке Вы грамотно расставили теги для yard, то есть 2 основных пути предоставить документацию пользователю:


  • Сгенерировать её в виде HTML/Markdown/JSON и положить рядом с кодом. Выполняется это командой puppet string generate [--format FORMAT], где формат может быть опущен или принимать значение json/markdown.
    • За стандарт документации принято иметь в корне репозитория файл REFERENCE.md, который генерируется командой puppet strings generate --format markdown.
  • Опубликовать для репозитория с кодом (при условии, что он находится на github) github-pages. Это сделать достаточно просто, понадобится 3 команды:
    # удаляем Gemfile.lock, который мог создать PDK
    rm -f Gemfile.lock
    # Устанавливаем все гемы из Gemfile при помощи bundle
    bundle install --path vendor/bundle
    # Генерируем непосредственно gh-pages при помощи rake-task
    bundle exec rake strings:gh_pages:update

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


Разумеется, всё это можно автоматизировать и добавить в виде этапа CI. Это был бы идеальный вариант. У меня пока руки не дошли, но в бэклоге пылится. Если вдруг кто-то имеет, что сказать на эту тему — буду благодарен. Как мысли: хотя бы добавить проверку, меняется ли REFERENCE.md после запуска puppet-strings. И если да — считать тесты проваленными.


Настройка шаблонов


Документация по шаблонам находится в репозитории pdk-templates. Если кратко, то всё конфигурируется при помощи файла .sync.yml в корневой директории модуля, а применяются изменения при помощи команды pdk update. Каждый из параметров этого файла — это имя иного файла в директории модуля, который необходимо изменить тем или иным образом. Большинство параметров для каждого из шаблонов мне пришлось подбирать "наощупь", просматривая исходные коды, зачастую — методом проб и ошибок. Документация тут порою сильно отстаёт. К сожалению, больше сказать-то почти и нечего, кроме как дать ссылку на пример из собственного репозитория.


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


  • Gemfile: добавлены два гема как зависимости в различных группах: pdk в группе development; xml-simple в группе dependencies. При запуске тестов не устанавливается группа system_tests, поэтому зависимость я добавляю в другую группу.
  • spec/spec_helper.rb: изменён метод мокинга, добавлен порог минимального покрытия тестами, ниже которого тесты считаются проваленными.
  • .travis.yml: этот файл долго шлифовался, так как используется для проверки кодовой базы и загрузки готового модуля на puppet-forge. Изменения:
    • Пользователь и зашифрованный пароль для заливки модуля на puppet-forge. Подробнее про деплой в puppet-forge при помощи Трэвис можно почитать тут.
    • Создана очерёдность тесты > деплой с запуском последнего только при успешных тестах.
    • Добавлен этап деплоя модуля на puppet-forge при условии, что CI запущен из тега, начинающегося с символа "v".
  • Rakefile: добавлены некоторые исключения для линтера.

Запуск различных CI


Здесь всё достаточно просто. Сразу же после генерации модуля при помощи PDK доступен запуск валидации в appveyor, travis и gitlab-ci. Для запуска тестов всё готово прямо из коробки, для тюнинга же используется всё тот же .sync.yml. У меня особых предпочтений нету, поэтому и рекомендовать я ничего не буду. Просто используйте то, что удобнее.


Бонус: пишем юнит-тесты для классов, типов и функций


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


Итак, у нас есть модуль с манифестами и библиотекой, которые, в свою очередь, содержат классы, типы и функции (также не забываем про таски и провайдеры, но в этой части у меня экспертиза отсутствует). Так как любой код существует с целью меняться, то неплохо бы, очевидно, обложить его тестами, чтобы удостовериться в 2 вещах:


  • Изменения не ломают текущее поведение (или поведение меняется вместе с тестами)
  • Ваши манифесты выполняют ровно то, что ожидается, и применяют все ресурсы согласно ожиданиям

Puppetlabs предоставляет расширение для фреймворка rspec под названием puppet-rspec. Ссылки на документацию по тестированию классов, типов и функций. Не поленитесь посмотреть внимательнее, есть и другие секции.


Начать его использовать достаточно просто, даже не зная ruby. Если классы или типы были созданы, как было показано выше, при помощи pdk new <thing>, то *_spec.rb-файл тоже уже имеется. Итак, предположим, что у нас есть класс dummy::class. Для его тестирования должен быть создан файл spec/classes/class_spec.rb со следующим содержанием:


require 'spec_helper'

describe 'dummy::class' do
  on_supported_os.each do |os, os_facts|
    context "on #{os}" do
      let(:facts) { os_facts }

      it { is_expected.to compile }
    end
  end
end

Проверить можно, запустив pdk test unit из корневой директории модуля.


Это практически всё, что нам нужно. Теперь осталось дополнить class_spec.rb необходимыми is_expected с соответствующими условиями. Например, проверить, что класс содержит ресурс file {'/file/path': } с определёнными параметрами, можно таким образом:


it do
  is_expected.to contain_file('/file/path').with(
    'ensure' => 'file', 'mode' => '0644'
  )
end

Можно задать параметры класса, используя let(:params) { {'param1' => 'value'} }, есть возможность провести тесты при различных входных условиях, поместив каждый it внутрь выделенных секций context 'some description' {}. Проверять можно как зависимости между ресурсами, так и между классами: если подразумевается, например, что объявление класса содержит inherits, то можно добавить проверку is_expected.to contain_class('parent_class_name'). Необходимо проверить поведение в различных ОС? Тоже возможно: просто указываем в отдельном контексте необходимые факты:


context 'with Debian' do
  let(:facts) do
    {
      os: {
        architecture: 'amd64',
        distro: {
          codename: 'stretch',
          id: 'Debian',
          release: {
            full: '9.6',
            major: '9',
            minor: '6',
          },
        },
        family: 'Debian',
        name: 'Debian',
        release: {
          full: '9.6',
          major: '9',
          minor: '6',
        },
        selinux: {
          enabled: false,
        },
      },
      osfamily: 'Debian',
    }
  end

  it { is_expected.to something }
end

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


Вместо вывода


Как уже могло быть понятно из общей интонации статьи, я сильно воодушевлён тем, насколько компания Puppet облегчила работу с модулями и манифестами благодаря PDK. Рутинные действия автоматизированы, всюду, где можно, используются шаблоны, из коробки доступны конфиги для популярных CI. Может показаться, что это некий overhead, и использование может не принести ожидаемых плодов, но оно определённо того стоит. Если сравнивать, как разрабатывать модули без и с PDK, то для меня это выглядит так:


Разработка без бороды PDK Разработка с PDK

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


Да пребудет с нами атоматизация!

Комментарии (0)