
Привет, Хабр! Я Артём Клещев, технический писатель в СберТехе. Я пишу документацию к продукту Platform V DropApp — решению для управления контейнерными приложениями. Наша команда работает в парадигме Docs-as-Code.
Мы столкнулись с проблемой: при каждом изменении продукта нам нужно было менять документацию сразу в нескольких репозиториях — для каждого исполнения продукта. Но мы нашли решение, как оптимизировать процесс. И хотим поделиться рекомендациями по ведению единого источника в Docs-as-Code — будет полезно тем, кто хочет шаблонизировать документацию и сэкономить вре��я для творческих задач.
В статье покажу, как построить удобную архитектуру репозитория продукта с применением шаблонов и MyST-разметки в парадигме Docs-as-Code. Расскажу, как вместо поддержки нескольких разрозненных комплектов документации создать библиотеку шаблонов с общим контентом. Объяснять буду на примере разработки ФСТЭК-документации для нашего продукта Platform V DropApp. Всё, что вы прочитаете в статье, актуально для любых типов документации в различных исполнениях, с единым источником информации.
Надеюсь, что опыт нашей команды поможет вам избежать ошибок и лишних шагов. Кстати, планируется продолжение: в следующей статье расскажу, как нам удалось внедрить в процессы новый упрощённый метод ведения Git и значительно сократить длительность подготовки документации.
Поехали!
В чём была задача
Мы готовили наш продукт к внедрению в системы заказчиков. Для этого нам нужно было получить сертификацию ФСТЭК. У нас уже была готова стандартная версия документации (назовём её исполнение 1), которую мы передавали заказчикам, но не сертифицировали. Но нам требовалось подготовить ещё исполнение 2 — сертифицированную версию с настройками безопасности (внутренний комплект документации 2) и публичную версию (без конфиденциальной информации), доступную для чтения любым пользователям (внешний комплект документации 2).
Внутренний и внешний комплекты документации 2 частично содержали бы контент из документации исполнения 1. Поэтому при изменении одного комплекта пришлось бы менять вручную и два остальных. Так у нас появилась идея объединить все исполнения документации, чтобы не вносить изменения во все сразу.
Но это ещё не всё. Наш продукт Platform V DropApp состоит из нескольких компонентов. У каждого своя документация, но часть контента в них повторяется. Мы подумали: хорошо, если бы у нас был единый источник — шаблоны всех документов в единственном экземпляре. Редактировать и обновлять пришлось бы только шаблоны, а документы автоматически собирались бы на их основе. Это сэкономило бы нам кучу времени. Поэтому параллельной задачей стала подготовка единого источника документации.
Приступаем к решению
Мы начали с создания единого репозитория с использованием исходных, переиспользуемых файлов — шаблонов. Они помогают экономить время пользователя: если нужно скорректировать документацию, не придётся искать файлы по разным репозиториям и папкам, всё в одном месте.
Мы планировали «набить» шаблоны информацией для документов всех компонентов, а необходимую информацию по каждому компоненту отдельно «предъявлять» с помощью ссылок.
Подходящим инструментом для реализации идеи стала директива include. Она включает в себя:
блок-ссылку в публикуемом файле:

дополнительную разметку для использования директивы — разметочные комментарии в файле-шаблоне:

Принцип работы директивы очень прост:
В исходном файле-шаблоне комментариями размечаем контент (начинается после комментария
-start, заканчивается перед комментарием-end): «эта информация для такого-то файла такого-то компонента, а вот эта — для другого».В публикуемом файле размещаем директиву
includeв виде ссылки на файл шаблона и с указанием комментариев, контент между которыми появится именно в этом публикуемом файле.Публикуется только закомментированный контент с помощью инструмента для сборки и публикации документации в парадигме Docs-as-Code. Мы используем инструмент собственной разработки Platform V GetDocs, подробнее о нём можно почитать в этой статье. У вас, скорее всего, будет другой инструмент похожей направленности.
В одном файле может быть множество комментариев директивы include — например, потому что не весь контент нужен в определённом исполнении документа. Поэтому мы рекомендуем именовать комментарии следующим образом:
<!-- <some-text>-start --> — начало размечаемого контента; <!-- <some-text>-end --> — окончание размечаемого контента.
Для удобной навигации и указания, на что ссылается директива, можно использовать следующие наименования комментариев:
<!-- <1>-<2>-<3>-<4>-start -->
<1>— имя исходного документа шаблона, на который ссылается директива;<2>— имя раздела или файла исходного документа, на который ссылается директива;<3>— имя подраздела или пункта исходного документа, на который ссылается директива;<4>— порядковый номер части вставляемого контента (если контент разбивается на части).
Пример: <!-- about-OTRF-existing-content-2-start -->
где:
about— раздел документации «Описание»;OTRF— аббревиатура раздела или файла one-time-release-file;existing-content— имя подраздела или пункта исходного документа one-time-release-file;2— порядковый номер комментария директивы.
Используйте дефис между словами в комментарии, чтобы выделять отдельные части фразы между дефисами двойным кликом для редактирования.
Используйте нижние подчёркивания, чтобы фраза выделялась целиком при двойном к��ике. Удобно при копировании комментария для вставки в директиву.
Начальные зарисовки, или «Сделай нас единым»
Визуализация архитектурного процесса возникла сама собой. На схеме ниже — файловая структура единого репозитория документации, стрелками указано направление перемещения информации.

Стрелками показано потенциальное дублирование контента в комплектах документации разных исполнений.
Первоначальный план включал в себя:
регулярно обновляемые внешний и внутренний комплекты документации 2;
регулярно обновляемый комплект документации ядра (Исполнение 1);
указание связей и переносов средствами директивы
includeконтента документации;дополнительные вопросы, возникшие при фантазировании светлого будущего (указаны на схеме).
Но мы выявили несостыковки:
репозиторий единый, но работа в единой ветке для всех компонентов продукта не реализована (подробнее об этом — в следующей статье);
вносить изменения необходимо в каждый отдельный компонент, даже при наличии
include;без наглядного инструмента стороннему наблюдателю всё ещё не очевидно, над каким компонентом ведётся работа в репозитории;
асинхронизация изменений в релизных ветках, если работа ведётся в нескольких версиях параллельно;
неизвестно, есть ли возможность единовременной публикации различных или отдельных комплектов.
После многочисленных обсуждений мы решили:
вести изменения документации в одном месте под кодовым словом «Шаблоны» — «templates»;
наполнять контент директивой
includeв папках с документациями;заранее создавать pull request, чтобы сторонний пользователь (например, редактор или проверяющий) мог понять происходящее в репозитории;
вести работы в единой ветке, предположим, worker;
создавать релизные ветки из готового материала рабочей ветки worker с pull request в релизную ветку; и после слияния единого pull request больше не менять релизные ветки.
О первых двух пунктах плана расскажу в этой статье, а об остальных поговорим в следующей.
Итак, объединить все нужные нам комплекты — задача слишком масштабная. Поэтому мы сосредоточились на создании внешнего и внутреннего комплектов документации 2: в том же репозитории, что и исполнение 1, с единой веткой (но отличной от ветки исполнения 1) и с единым набором файлов для использования директивы include.
На основе предыдущих версий архитектуры мы создали версию «Внешний комплект документации 2 — Внутренний комплект документации 2».
Вот что нам было важно на этом этапе:
изменять документацию в одном месте в каталоге templates;
создать шаблон документации из внутреннего комплекта документации 2 (красные стрелки на схеме);
наполнять каталог внешнего и внутреннего комплектов документации 2 из каталога templates (зелёные стрелки на схеме);
наполнить директивой
includeконтент в каталогах с документациями;вести работы в единой ветке.

Работа с изображениями
Картинки при include
На следующем этапе мы столкнулись ещё с одной проблемой: оказалось, что при использовании директивы include ссылки на картинки из переиспользуемого файла не работают.
Проблема в том, что пути могут быть прописаны относительной ссылкой. Она указывает на ресурс относительно текущего местоположения, не содержит полного пути и может изменяться в зависимости от того, где используется.
Абсолютная ссылка содержит полный путь к файлу. Она всегда указывает на одно и то же местоположение, независимо от того, откуда была вызвана.
Для исправления этого недочёта применяется параметр :relative-images: директивы include.

Директива :relative-docs: делает то же самое, что и :relative-images:, только со ссылками на документы и файлы. Она обязательно должна иметь параметр суффикса, используемого в тексте ссылки. Рекомендуем использовать параметр: :relative-docs: ..
То же справедливо и для ссылок на файлы.
Расположение папки resources
Папку resources, в которой будут храниться большие файлы, манифесты и изображения для дальнейшего отображения при сборке, следует располагать в каталоге шаблонов templates. Это позволит:
вести учёт изображений в том же каталоге, где ведется работа над переиспользуемыми файлами — шаблонами;
сэкономить время при переносе контента на новую архитектуру;
вести единообразно все ссылочные материалы.
Pull request при переходе на новую архитектуру
Одним из основных недостатков при переходе на новую архитектуру может стать первый релизный pull request (на изображении ниже — ПР). В нашем случае он получился огромным из-за большого количества файлов:

Из-за многочисленных переносов и добавления файлов первый pull request похож на страшного монстра Франкенштейна. Придётся подробно инструктировать проверяющих (например, руководство или редактора) о смене архитектуры документации.
Однако все последующие единые pull requests в релизные ветки будут выглядеть привычно, с наглядным и понятным отображением изменений:

Нюансы директивы include
Include — это хорошо, но есть нюанс
В попытке использовать директиву include в списках, содержаниях и оглавлениях обнаружилась особенность: инструмент не годится для разделения списков. Директива не собирает отдельные пункты в единый список в публикуемом файле.



Какие выводы мы сделали:
применять
includeк спискам можно только начиная с первого уровня списка;использование
includeв index.md ломает сборку;includeразрывают список.
Параллельный include
Что, если нужно добавить абзац текста в одно исполнение документации, а в другое — только часть абзаца? Обратите внимание на параллельный include:



Include в include, или «Кощеева игла»
Рекомендуем также обратить внимание на ситуацию со ссылками при использовании include в include (по-другому — «кощеева игла»).
Возможна такая ситуация: публикуемый файл, который используется при сборке документации, ссылается на файл, в котором также есть директива include.



В такой ситуации сработает не всё:
-
соберётся и отобразится:
печатный контент (текст, таблицы, ссылки на подразделы в актуальном разделе и т. п.);
графические директивы (примечания, спойлеры и т. п.).
-
не соберётся и не отобразится:
ссылки (изображения, спойлеры с файлами, ссылки для скачивания и т. п. при отсутствии параметра
:relative-docs: .).
При сборке ситуации, указанной выше, изображение будет недоступно, так как ссылка окажется битой. Картинка не отображается при сборке, так как ссылка на неё расположена в файле, на который ведёт основная ссылка. Не стоит использовать вложенные ссылки для изображений. Это произойдёт из-за того, что MyST — расширение синтаксиса Markdown, на котором мы пишем документацию, — некорректно обрабатывает include в include в контексте ссылок.
Проблему поможет решить создание клона источника (фото, документа, в данном случае resources) в каталоге с публикуемым файлом. Поскольку ни то, ни другое не является целевым методом, рекомендуется максимально сократить ситуации «include в include». То есть по возможности исключать промежуточный файл со ссылкой на файл-источник.
Nothing else Frontmatter

Frontmatter — это блок YAML в начале документа, который позволяет указывать метаданные и параметры того, как должен вести себя или отображаться отдельный документ или даже раздел. Например, в нашем случае, такой блок проставляет инвентарные номера в колонтитул выходных форм в формате .docx:

Ситуация следующая:

-
Публикуемый файл со ссылкой на файл-источник с Frontmatter:

-
Файл-источник с Frontmatter:

При такой сборке переменные (метаданные и параметры Frontmatter) не будут работать. Frontmatter следует расположить в публикуемом файле:

Итоги
В этой статье я показал, как построить удобную архитектуру репозитория и значительно сократить длительность её подготовки. Подробно описал, как вместо нескольких комплектов документации сделать библиотеку шаблонов с общим контентом. Важно помнить, что вместо указанных инструментов можно использовать аналоги со схожей функциональностью.
Итак, вот что помогло лично мне:
Работа в едином репозитории вместо семи отдельных. Экономия времени — по несколько минут на каждом прыжке с репозитория на репозиторий, и таких прыжков может быть огромное количество за релиз.
Создание единого pull request для наглядности всех изменений релиза. Внесение изменений в одном месте экономит время проверки. Время зависит от количества промежуточных pull requests на релиз и составляет приблизительно десятки минут на каждый релизный цикл. И главное — не нужно искать старые pull requests для понимания изменений: всё хранится в одном месте.
Ведение документации в едином источнике шаблонов. Изменения для множества комплектов достаточно внести один раз. Работа ускоряется в зависимости от количества объединяемых источников, в нашем случае их пять. Экономия — около 30 % на каждый релизный цикл.
В следующей статье я расскажу, как можно удобно работать в единой ветке, сэкономить время на истории сохранений в Git и с лёгкостью выпускать множество релизов документации одним днём.
Оставайтесь на связи!