Привет, Хабр! Я Артём Клещев, технический писатель в СберТехе. Я пишу документацию к продукту 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. Она включает в себя:

  • блок-ссылку в публикуемом файле: 

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

Принцип работы директивы очень прост:

  1. В исходном файле-шаблоне комментариями размечаем контент (начинается после комментария -start, заканчивается перед комментарием -end): «эта информация для такого-то файла такого-то компонента, а вот эта — для другого».

  2. В публикуемом файле размещаем директиву include в виде ссылки на файл шаблона и с указанием комментариев, контент между которыми появится именно в этом публикуемом файле.

  3. Публикуется только закомментированный контент с помощью инструмента для сборки и публикации документации в парадигме 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
Выходной файл с директивами
Выходной файл с директивами
Провал задуманного на стадии публикации. Содержание не отображается корректно из-за того, что директива при обработке вставляет не только контент, но и обрамляет его пустыми строками. А это нарушает список.
Провал задуманного на стадии публикации. Содержание не отображается корректно из-за того, что директива при обработке вставляет не только контент, но и обрамляет его пустыми строками. А это нарушает список.

Какие выводы мы сделали:

  • применять include к спискам можно только начиная с первого уровня списка;

  • использование include в index.md ломает сборку;

  • include разрывают список.

Параллельный include

Что, если нужно добавить абзац текста в одно исполнение документации, а в другое — только часть абзаца? Обратите внимание на параллельный include:

Исходный файл разбиения директивой include
Исходный файл разбиения директивой include
Выходной файл с директивами
Выходной файл с директивами
Публикация. На выходе видна разница: основной используемый контент из шаблона, а дополнительный (фраза «разделяющая строка») — добавленный контент вне шаблона.
Публикация. На выходе видна разница: основной используемый контент из шаблона, а дополнительный (фраза «разделяющая строка») — добавленный контент вне шаблона.

Include в include, или «Кощеева игла»

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

Возможна такая ситуация: публикуемый файл, который используется при сборке документации, ссылается на файл, в котором также есть директива include.

Публикуемый файл со ссылкой на промежуточный файл
Публикуемый файл со ссылкой на промежуточный файл
Промежуточный файл со ссылкой на файл-источник
Промежуточный файл со ссылкой на файл-источник
Файл-источник
Файл-источник

В такой ситуации сработает не всё:

  • соберётся и отобразится:

    • печатный контент (текст, таблицы, ссылки на подразделы в актуальном разделе и т. п.);

    • графические директивы (примечания, спойлеры и т. п.).

  • не соберётся и не отобразится:

    • ссылки (изображения, спойлеры с файлами, ссылки для скачивания и т. п. при отсутствии параметра :relative-docs: .).

При сборке ситуации, указанной выше, изображение будет недоступно, так как ссылка окажется битой. Картинка не отображается при сборке, так как ссылка на неё расположена в файле, на который ведёт основная ссылка. Не стоит использовать вложенные ссылки для изображений. Это произойдёт из-за того, что MyST — расширение синтаксиса Markdown, на котором мы пишем документацию, — некорректно обрабатывает include в include в контексте ссылок. 

Проблему поможет решить создание клона источника (фото, документа, в данном случае resources) в каталоге с публикуемым файлом. Поскольку ни то, ни другое не является целевым методом, рекомендуется максимально сократить ситуации «include в include». То есть по возможности исключать промежуточный файл со ссылкой на файл-источник.

Nothing else Frontmatter

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

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

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

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

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

Итоги

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

Итак, вот что помогло лично мне:

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

  • Создание единого pull request для наглядности всех изменений релиза. Внесение изменений в одном месте экономит время проверки. Время зависит от количества промежуточных pull requests на релиз и составляет приблизительно десятки минут на каждый релизный цикл. И главное — не нужно искать старые pull requests для понимания изменений: всё хранится в одном месте.

  • Ведение документации в едином источнике шаблонов. Изменения для множества комплектов достаточно внести один раз. Работа ускоряется в зависимости от количества объединяемых источников, в нашем случае их пять. Экономия — около 30 % на каждый релизный цикл.

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

Оставайтесь на связи!

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