render config
dwe render config [service] рендерит конфигурационные файлы сервиса (например, .env,
env.php) из пакета шаблонов в hub-каталог каждого сервиса, воспроизводя любые
выпущенные сервисом секреты, собранные в долговечное хранилище сгенерированных значений. Конфиги —
чистые выходы рендера, выведенные из объединённой конфигурации плюс хранилища.
Содержание
Заголовок раздела «Содержание»- Обзор
- Подложка шаблонов: сокращение
${...} - Пространство имён
${generated.<name>} - Хранилище сгенерированных значений
- Декларация
generated: - Собирать, а не выпускать
- Разрешение пакета шаблонов
- Схема manifest
- Использование CLI
- Builtin-ы конвейера
- Поток деплоя
- Авто-рендер
dwe run - Reset и
--clear-generated - Миграция с копирования
configs: - Связанные справочники
Рендер конфигов пишет прямо в уже примонтированное дерево src/ (без
пофайлового bind-mount / машинерии mountpoint). Модель состоит из двух частей:
- Render — разрешить пакет шаблонов конфигов, отрендерить каждую запись manifest и
записать результат в hub-каталог сервиса (
svc.Dir), режим replace (перезапись). - Generated-once — opt-in механизм для выпущенных сервисом секретов (Laravel
APP_KEY, Magentocrypt.key, …). Сервис генерирует значение, записывая его в свой собственный файл; DWE считывает его обратно («harvest») в долговечное пер-сервисное хранилище (.dwe/generated.yml) и воспроизводит его при каждом последующем рендере через пространство имён${generated.<name>}.
Рендер конфигов — opt-in: сервис без разрешимого пакета конфигов — это
молчаливый no-op. Никакой обвязки scaffold-а dwe init нет — вы сами авторите пакет и
шаги конвейера.
Подложка шаблонов: сокращение ${...}
Заголовок раздела «Подложка шаблонов: сокращение ${...}»В отличие от render ide / ai / git — которые используют сырую подложку Go text/template {{ }}
(packcommon.TemplateData) — шаблоны конфигов используют сокращение ${...},
ту же форму, что уже применяется в правилах экспорта ${APP_*} / ${DB_*}.
Это намеренное расхождение, оправданное эргономикой конфигурационных файлов: авторы
конфигов ожидают паритета ${...} со значениями, на которые они ссылаются.
${X} компилируется в {{ resolve .Raw "X" }}, поэтому dot-путь ищется в
объединённой конфигурации (cfg.Raw) без префикса raw. — но только когда
голова X относится к известному пространству имён (корневой ключ
объединённой конфигурации либо одно из специальных пространств имён ниже);
нераспознанная голова остаётся литеральным ${...} вместо молчаливого
рендера в "":
APP_URL=${services.main.hosts.web}DB_HOST=${vars.databases.main.host}DB_DATABASE=${vars.databases.main.name}APP_KEY=${generated.app_key}- Пер-сервисные поля используют
${services.<name>...}(например,${services.main.ports.http}). Это раскрывает только курируемое подмножество, внедрённое вcfg.Raw["services"]—type/container/ каталоги /configs/ports/hosts/ … — а неrender/generated/ произвольные объединённые поля. Опущенное или не внедрённое поле рендерится как""(все резолверы${...}снисходительны — отсутствующий путь — это пустая строка, никогда не ошибка). - Произвольные значения живут под
vars:— ссылайтесь на них как${vars.<путь>}(например,${vars.databases.main}). Голый dot-путь верхнего уровня без префиксаvars.не резолвится: корень объединённой конфигурации — это строгий allowlist (project,services,vars, …), поэтому произвольный ключ вродеdatabasesне может появиться там напрямую. - Сгенерированные значения используют
${generated.<name>}(см. ниже).
Единичного связывания текущего сервиса ${service....} нет — ссылайтесь на
сервис по имени через ${services.<name>...}.
Пространство имён ${generated.<name>}
Заголовок раздела «Пространство имён ${generated.<name>}»${generated.<name>} разрешается в собранное значение для поля <name> текущего сервиса
из хранилища сгенерированных значений. При первом деплое хранилище
пусто, поэтому ${generated.app_key} рендерится как "" — сервис затем выпускает
настоящее значение, и DWE его собирает. При последующих рендерах сохранённое значение
воспроизводится дословно, поэтому секрет переживает run / редеплой, оставаясь при этом вне
git.
Отсутствующий ключ рендерится как "" (снисходительно, как и у любого другого резолвера
${...}). Пространство имён ограничено рендерящимся сервисом: оно читает
store[<current-service>], поэтому ${generated.app_key} в пакете main никогда не видит
crypt_key из magento.
Хранилище сгенерированных значений
Заголовок раздела «Хранилище сгенерированных значений»Хранилище лежит в .dwe/generated.yml (под gitignored runtime-каталогом .dwe/
— оно никогда не коммитится):
services: main: app_key: "base64:Xa3…==" magento: crypt_key: "241f4fa60be8f69638343cacc5a1a192"- Значения — строки (block scalars для многострочных секретов).
- Записи атомарны (temp-файл + rename), зеркаля журнал деплоя.
- Отсутствующий файл — это пустое хранилище (первый деплой). Повреждённый файл — это всплывающая ошибка — никогда не проглатывается молча, поэтому некорректное хранилище нельзя принять за «секретов пока нет».
- У хранилища нет
schema_version. - Создание/восстановление снапшота намеренно оставляет
.dwe/generated.ymlнетронутым (это отдельный файл от.dwe/deploy/state.yml).
Декларация generated:
Заголовок раздела «Декларация generated:»Сгенерированные поля декларируются в service.yml (пер-сервисное lifecycle-состояние),
а не в manifest — воспроизводимое значение может целиться в аргумент команды, а не
только в шаблон:
type: appdir: ./services/mainrender: config: template: laravel # optional pack pin; else convention + .localgenerated: app_key: file: src/.env # output file, relative to the service hub (svc.Dir) pattern: '^APP_KEY=(.*)$' # regex; capture group 1 = value| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
file | string | да | Выходной файл, в который сервис записывает значение, относительно hub-каталога сервиса (svc.Dir). Должен быть содержащимся относительным путём (без ..). |
pattern | string | да | Regex, применяемый построчно; capture group 1 — это собранное значение. Должен компилироваться и объявлять ≥1 capture group (валидируется dwe validate). |
Ключ карты (app_key) — это идентификатор ${generated.<name>}. dwe validate
отвергает невалидный regex, отсутствующую capture group, выходящий за пределы пути file или
имя поля, которое не является валидным идентификатором ${generated.<name>}.
Собирать, а не выпускать
Заголовок раздела «Собирать, а не выпускать»DWE никогда не генерирует секрет сам — движок герметичен (без crypto / случайности), а переиспользование собственного генератора сервиса формат-агностично со стороны DWE (DWE лишь считывает строку обратно). Сбор:
- Читает
<svc.Dir>/<file>. - Применяет
patternпострочно, беря capture group 1 первого совпадения. - Write-if-absent в хранилище, затем сохраняет атомарно, если что-то изменилось.
Ошибки всплывают точно, никогда не пропускаются молча: отсутствующий файл, паттерн, не совпадающий ни с одной строкой, паттерн без capture group и паттерн, который захватывает пустое значение — всё это жёсткие ошибки — поэтому полу-выпущенный секрет не может загрязнить хранилище. Write-if-absent означает, что редеплой — это no-op, как только значение сохранено.
pattern (regex, захватывающий одну строку) используется вместо enum type формата,
потому что DWE никогда не перечисляет форматы конфигов и никогда не пишет чужой формат —
он лишь читает одну строку из одного файла. Один и тот же pattern извлекает строку dotenv
APP_KEY= или значение PHP-массива 'crypt' => ['key' => '…'].
Разрешение пакета шаблонов
Заголовок раздела «Разрешение пакета шаблонов»Пакеты конфигов живут под workspace/templates/config/<pack>/ с тем же соглашением
оверрайда shadow-пакета <pack>.local/, что у ide/ai/git (см.
Локальные оверрайды). Порядок разрешения — используется первое
совпадение:
workspace/templates/config/<template>/, когда заданоrender.config.template— строго: запинённый пакет, которого не существует, — жёсткая ошибка (ловит опечатки).workspace/templates/config/<service-name>/.- Каждый предок в цепочке
extendsсервиса:workspace/templates/config/<ancestor>/. workspace/templates/config/default/.- Если ни одного нет, рендер конфигов пропускается (opt-in — без ошибки).
Симлинкнутые пакеты отвергаются; каталог пакета должен оставаться содержащимся внутри корня проекта.
Схема manifest
Заголовок раздела «Схема manifest»Пакеты конфигов управляются manifest-ом, используя общую схему manifest.yml (ту
же, что читают ide/ai/git, см.
Общая схема manifest), с двумя ограничениями
config-kind:
render: - from: env.tmpl to: src/.env| Аспект | Пакет конфигов |
|---|---|
| Корень назначения | hub-каталог сервиса (svc.Dir) |
Форма to | любой содержащийся относительный путь |
symlinks | отвергается — отрендеренные конфигурационные файлы пишутся на месте, никогда не симлинкуются |
to: src/... — это соглашение использования, а не захардкоженное соединение: to
интерпретируется относительно hub-каталога сервиса. Авторы целятся в дерево приложения
(уже dir-mounted в контейнер), записывая to: src/.... Назначения защищены
безопасностью путей — to, который выходит за hub-каталог или разрешается вне него через симлинк,
отвергается.
Использование CLI
Заголовок раздела «Использование CLI»dwe render config # render every enabled app service that resolves a pack (DeployOrder)dwe render config main # render only the `main` servicedwe render config main --harvest # harvest-only pass: read on-disk values into the store, NO render- Без аргумента каждый включённый app-сервис обрабатывается в
DeployOrder(детерминированно); сервис без пакета конфигов молча пропускается. Рендер конфигов работает только для app-сервисов — лишь app-сервисы могут объявлятьdir/render/generated, поэтому tool- / infra-сервисы не перебираются. - С явным
[service]аргумент валидируется (должен существовать, быть включённым и иметь hub-каталог); отсутствующий пакет всплывает предупреждением. --harvestпереключает на harvest-only проход (HarvestGenerated, без рендера) — для бутстрапа уже закоммиченных секретов существующего проекта в хранилище, прежде чем они перестанут коммититься.
Путь рендера по умолчанию read-only в отношении проектных локов: он не
запускает preflight и не захватывает локи, как и рендереры ide/ai/git.
--harvest изменяет общее хранилище сгенерированных значений, поэтому он
сначала захватывает проектные локи (как builtin harvest при деплое и reset --clear-generated), чтобы не затереть конкурентного писателя в хранилище.
Builtin-ы конвейера
Заголовок раздела «Builtin-ы конвейера»Три builtin-а движка управляют рендером конфигов внутри конвейеров deploy / reset (см. deploy builtins):
| Builtin | Назначение |
|---|---|
service_configs_render | Отрендерить пакет конфигов сервиса в его hub-каталог (режим replace), воспроизводя сохранённые сгенерированные значения |
service_configs_render_check | Проверить, что отрендеренные цели существуют; спаривание его как check: заставляет шаг рендера перезапускаться при каждом деплое |
service_generated_harvest | Собрать объявленные поля generated: сервиса в хранилище (write-if-absent) |
service_configs_render_check зеркалит service_configs_copy + service_configs_check:
его присутствие как check: дёргает рычаг hasCheck → Run, обходя
пропуск по action-hash, поэтому шаг рендера всегда перезапускается — правки шаблонов
и очистки хранилища поэтому всегда вступают в силу.
Поток деплоя
Заголовок раздела «Поток деплоя»Конвейер деплоя, который рендерит конфиги и собирает выпущенный сервисом секрет:
phases: - name: configs steps: - name: render-configs type: builtin cmd: service_configs_render with: service: main check: # presence forces re-run every deploy type: builtin cmd: service_configs_render_check with: service: main
- name: generate-app-key when: # gate: only when the store has no value yet type: builtin cmd: "generated-missing main app_key" type: dwe cmd: "shell main -- php artisan key:generate"
- name: harvest-app-key type: builtin cmd: service_generated_harvest with: service: mainПервый деплой: рендер пишет APP_KEY= (хранилище пусто) → гейт открыт → сервис
выпускает APP_KEY=base64:… → сбор его захватывает.
Последующие деплои: рендер воспроизводит сохранённое значение → гейт закрыт → генерация
пропускается → сбор — no-op. Рендер перезапускается при каждом деплое
через свой check:. Инвариант: хранилище пусто для ключа ⟺ значение перевыпускается.
Обратите внимание: шаг сбора намеренно не закрыт гейтом — service_generated_harvest
пропускает любое поле, которое уже есть в хранилище, вообще не читая его файл. Именно это
делает его no-op выше, и это не оптимизация, а несущее свойство: dwe reset run (без
--clear-generated) сохраняет хранилище, но стирает хаб сервиса, так что на следующем
деплое выпущенного файла уже нет, а полученное из него значение всё ещё авторитетно.
Сбор, настаивающий на перечитывании, уронил бы весь деплой из-за значения, которое у него
уже есть. Поэтому строгие ошибки ниже (нет файла, нет совпадения, пустой захват) относятся
только к полю, которого в хранилище ещё нет, — то есть ровно к случаю, когда плохое
чтение способно испортить хранилище.
Предикат generated-missing <svc> <field> (см.
conditions) читает
.dwe/generated.yml и истинен, когда поле отсутствует или хранилища нет.
Авто-рендер dwe run
Заголовок раздела «Авто-рендер dwe run»dwe run перерендеривает конфиги сервисов из хранилища после прохождения гейта
деплоя (и после post-pull перезагрузки конфигурации), но до lifecycle-фаз —
поэтому конфиги отражают текущие шаблоны и воспроизведённые секреты на момент run.
Он никогда не запускает generate/harvest на run.
Run-рендер не разрушителен, когда данных для воспроизведения нет: если развёрнутый
сервис объявляет ключи generated:, которых нет в хранилище, рендер этого сервиса
пропускается с подсказкой dwe deploy run, а не рендерит обнулённый
секрет. Поскольку рендер выполняется только после гейта, reset --clear-generated,
за которым следует dwe run, проваливает гейт до достижения рендера, поэтому секреты
никогда не обнуляются.
Reset и --clear-generated
Заголовок раздела «Reset и --clear-generated»reset сохраняет хранилище по умолчанию — секрет переживает reset. Передайте
--clear-generated, чтобы очистить его (с областью по --service / всё):
dwe reset run --clear-generated # clear the whole store on full resetdwe reset run --service main --clear-generated # clear only main's entriesХранилище очищается только после успешного полного reset, включая post-pipeline очистку журнала — никогда, если конвейер или мутация журнала провалились (иначе рассогласование deployed-журнал + пустое-хранилище заставило бы run-гейт доверять сервису без секретов). На TTY с непустым хранилищем интерактивный запрос спрашивает, очищать ли также сгенерированные значения (по умолчанию No). Ротация = очистка + редеплой.
Миграция с копирования configs:
Заголовок раздела «Миграция с копирования configs:»Механизм копирования (configs: / mountpoint в service.yml,
builtin-ы service_configs_copy / service_configs_check) продолжает работать, но
устарел — dwe validate выдаёт предупреждение, и одно runtime-уведомление срабатывает
на каждый шаг копирования. Чтобы мигрировать:
- Переместите каждый запечённый файл из
configs/services/<svc>/в шаблон подworkspace/templates/config/<pack>/, заменив литеральные значения ссылками${...}. - Объявите
render.config(optional pin) и любые поляgenerated:вservice.yml; уберите блокconfigs:/mountpoint. - Замените
service_configs_copy(+service_configs_check) наservice_configs_render(+service_configs_render_check) в конвейере, добавив generate-гейт + шагиservice_generated_harvestдля любых выпущенных сервисом секретов. - Забутстрапьте уже закоммиченный секрет в хранилище через
dwe render config <svc> --harvest, затем перестаньте его коммитить.
Связанные справочники
Заголовок раздела «Связанные справочники»- определения сервисов (
service.yml) — справочник полейrender.configиgenerated: - deploy builtins —
service_configs_render,service_configs_render_check,service_generated_harvest - conditions — предикат
generated-missing - render index — общая схема manifest, локальные оверрайды, разрешение пакета
- Шаблоны — синтаксис Go-шаблонов и render-контексты
- Запустите
dwe render config --help, чтобы увидеть актуальный CLI-интерфейс