Примерно месяц назад у меня был выбор: писать ли модуль для 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 |
---|---|
Пробуйте, ставьте, облегчайте себе и коллегам жизнь. Я буду рад ответить на потенциальные вопросы.
Да пребудет с нами атоматизация!