Copier + Jinja template-repository design skill: reference architecture, implementation workflow, validation checklist, and a full worked example template (questionnaire, extensions, resolvers, wrapper-first .scripts). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
157 lines
5.8 KiB
Markdown
157 lines
5.8 KiB
Markdown
# Implementation Workflow (Copier + Jinja Template Repository)
|
|
|
|
Follow this sequence when creating a new template-engine repository.
|
|
|
|
## Phase 1 — Define rendering contract
|
|
|
|
1. Define template scope (what downstream repository you generate).
|
|
2. Identify stable input schema sections (e.g. tenant/project/environments/resources).
|
|
3. Decide what is:
|
|
- static files,
|
|
- conditional files/folders,
|
|
- repeatable per-instance blocks.
|
|
|
|
Deliverable: first draft of `copier-answers.example.yaml`.
|
|
|
|
## Phase 2 — Bootstrap Copier skeleton
|
|
|
|
Create:
|
|
|
|
- `.copier/copier.yaml`
|
|
- `.copier/questionnaire/*.yml`
|
|
- `{{ _copier_conf.answers_file }}.jinja`
|
|
|
|
Use baseline:
|
|
|
|
- `_template_suffix: ".jinja"`
|
|
- modular `!include .copier/questionnaire/*.yml`
|
|
- explicit `_exclude` list
|
|
- `_answers_file: copier-answers.yml`
|
|
|
|
## Phase 3 — Create operational wrappers
|
|
|
|
Implement wrapper chain:
|
|
|
|
1. root `Taskfile.yml` includes `.scripts/copier/Taskfile.yml`
|
|
2. `.scripts/copier/Taskfile.yml` exposes `bootstrap-one`
|
|
3. `.scripts/copier/api/bootstrap-one.sh` remains thin
|
|
4. **confirm ENV var prefix with user** (or propose one) before implementing lib internals
|
|
5. `.scripts/copier/lib/` contains logic and answers merge helpers
|
|
|
|
Minimum behavior in lib:
|
|
|
|
- parse CLI args with both styles: `--arg value` and `--arg=value` (for `--copier-target-dir`, `--copier-force-copy`, `--copier-params-file`),
|
|
- prepare merged answers file,
|
|
- run `copier copy --trust --data-file ...` from a sibling temporary copy of the template repo, not from the live template repo folder,
|
|
- post-render sanitization/renames if needed.
|
|
|
|
Mandatory temp-copy bootstrap pattern from `iac-tmpl-aws-projects`:
|
|
|
|
- In `api/bootstrap-one.sh`:
|
|
1. derive the live template folder name from `basename "$PWD"`,
|
|
2. delete any old sibling `../tmp-${TEMPLATE_FOLDER}`,
|
|
3. copy the whole template repo into `../tmp-${TEMPLATE_FOLDER}`,
|
|
4. `cd` into the temp folder,
|
|
5. source `.scripts/loggers/lib/--index.sh` and `.scripts/copier/lib/--index.sh`,
|
|
6. execute `_copier_bootstrap_one`,
|
|
7. return to the original live template folder.
|
|
- In `lib/-bootstrap-one.sh`, before `copier copy`:
|
|
1. copy `.copier/copier.yaml` to root `copier.yaml` if the repo uses `.copier/copier.yaml`,
|
|
2. remove `.git`, `.devbox`, and `.venv` from the temp copy,
|
|
3. run Copier from the temp folder into the real target.
|
|
|
|
Do **not** create Git tags merely to silence Copier's local template version warning. The preferred local bootstrap hygiene is rendering from the sanitized temp copy. Use `--vcs-ref=HEAD` only when there is an explicit reason to render directly from a Git-backed live folder.
|
|
|
|
## Phase 4 — Add Jinja template layers
|
|
|
|
1. Add top-level templates (`iac/main.tf.jinja`, providers, backend, etc.).
|
|
2. Gate directories via folder-name expressions:
|
|
- `{{ 'dev' if flat.dev_is_enabled }}`
|
|
3. Gate module calls inside files via `{% if ... %}`.
|
|
|
|
Pattern:
|
|
|
|
- folder condition controls existence,
|
|
- inner condition controls file content clarity.
|
|
|
|
**Mandatory wrapped render-path rule:**
|
|
|
|
- For every optional module/resource, apply conditions at the **path level** (folder/file names), not only inside file contents.
|
|
- Preferred pattern (from `iac-tmpl-aws-projects`):
|
|
- `iac/{{ 'dev' if flat.dev_is_enabled }}/{{ 'compute' if flat.dev_compute_is_enabled }}/main.tf.jinja`
|
|
- Apply this systematically to all optional Jinja outputs so disabled features do not produce placeholder files/directories.
|
|
|
|
**Mandatory safe-variable rule:**
|
|
|
|
- Do not embed repeated normalization/sanitization logic in template paths or file bodies, e.g. avoid:
|
|
- `{{ project.name | lower | replace(' ', '-') | replace('_', '-') | replace('.', '-') }}`
|
|
- If a safe name is needed, calculate it once in the context hook and expose a named variable, e.g.:
|
|
- `flat.project_safe`
|
|
- `flat.environment_safe`
|
|
- `flat.component_safe_names`
|
|
- Use the named variable in templates:
|
|
- `applications/{{ flat.project_safe }}/main.yaml.jinja`
|
|
- If a one-off transformation is genuinely needed, prefer a small deterministic resolver over duplicating filter chains.
|
|
|
|
## Phase 5 — Add custom extensions (if complexity requires)
|
|
|
|
When logic gets repetitive, move it into extensions.
|
|
|
|
### Resolver extension examples
|
|
|
|
- normalize names: `resolve_safe_tf_module_name(name)`
|
|
- derive tags / regions / zones
|
|
|
|
### Context hook extension examples
|
|
|
|
- calculate `flat.*` shortcuts,
|
|
- calculate safe/normalized names such as `flat.project_safe` and `flat.environment_safe`,
|
|
- expand `[INDEX]` directories into per-instance folders.
|
|
|
|
Register in `.copier/copier.yaml` under `_jinja_extensions`.
|
|
|
|
## Phase 6 — Implement answers merge strategy
|
|
|
|
Use this exact order to avoid destructive updates:
|
|
|
|
1. `copier-answers-new.yml` create if missing,
|
|
2. copy `<target>/copier-answers.yml` if present,
|
|
3. merge params file into working answers file (`yq eval-all ...`).
|
|
|
|
Then run copier using merged file.
|
|
|
|
## Phase 7 — Handle template/self-repo conflicts
|
|
|
|
If template repo contains files that should not be copied 1:1 (e.g. self CI):
|
|
|
|
1. render as temp name (`.tmp-gitlab-ci.yml`),
|
|
2. rename in target during bootstrap.
|
|
|
|
## Phase 8 — Add docs and examples
|
|
|
|
Add/update:
|
|
|
|
- `README.md` with local setup and bootstrap command,
|
|
- `copier-answers.example.yaml` with realistic schema examples,
|
|
- optional `bootstrap-one-test` task for local maintainer testing.
|
|
|
|
## Phase 9 — Validate end-to-end
|
|
|
|
Run two critical scenarios:
|
|
|
|
1. fresh render into empty target,
|
|
2. re-render into existing target with existing `copier-answers.yml`.
|
|
|
|
Both must succeed without manual edits.
|
|
|
|
## Quick-start from this skill example
|
|
|
|
If you want to accelerate implementation, copy `example/` and then:
|
|
|
|
1. expand questionnaire files,
|
|
2. expand `flat.*` derivation logic in context hook,
|
|
3. add domain-specific resolver extensions,
|
|
4. extend `iac/**/*.jinja` resource tree,
|
|
5. keep bootstrap/answers flow unchanged unless you have a strong reason.
|
|
|