Skip to content

Commit 3d8b153

Browse files
committed
docs: [#17] add comprehensive guide for adding static Ansible playbooks
This documentation improvement addresses the gap that led to the initial PR failure. Copilot missed the requirement to register static playbooks in the copy_static_templates method. Changes: - docs/contributing/templates.md: Added complete section on adding static Ansible playbooks with step-by-step guide, common mistakes, and checklist - docs/technical/template-system-architecture.md: Enhanced two-phase processing explanation with explicit registration requirements - .github/copilot-instructions.md: Added critical rule #5 about playbook registration to guide AI assistants The new documentation ensures that both human developers and AI assistants understand the static template registration requirement, preventing runtime 'playbook not found' errors.
1 parent b5d4e59 commit 3d8b153

File tree

3 files changed

+214
-7
lines changed

3 files changed

+214
-7
lines changed

.github/copilot-instructions.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,15 @@ These principles should guide all development decisions, code reviews, and featu
8787

8888
4. **Before working with Tera templates**: Read [`docs/contributing/templates.md`](../docs/contributing/templates.md) for correct variable syntax - use `{{ variable }}` not `{ { variable } }`. Tera template files have the `.tera` extension.
8989

90-
5. **When handling errors in code**: Read [`docs/contributing/error-handling.md`](../docs/contributing/error-handling.md) for error handling principles. Prefer explicit enum errors over anyhow for better pattern matching and user experience. Make errors clear, include sufficient context for traceability, and ensure they are actionable with specific fix instructions.
90+
5. **When adding new Ansible playbooks**: Read [`docs/contributing/templates.md`](../docs/contributing/templates.md) for the complete guide. **CRITICAL**: Static playbooks (without `.tera` extension) must be registered in `src/infrastructure/external_tools/ansible/template/renderer/mod.rs` in the `copy_static_templates` method, otherwise they won't be copied to the build directory and Ansible will fail with "playbook not found" error.
9191

92-
6. **Understanding expected errors**: Read [`docs/contributing/known-issues.md`](../docs/contributing/known-issues.md) for known issues and expected behaviors. Some errors that appear red in E2E test output (like SSH host key warnings) are normal and expected - not actual failures.
92+
6. **When handling errors in code**: Read [`docs/contributing/error-handling.md`](../docs/contributing/error-handling.md) for error handling principles. Prefer explicit enum errors over anyhow for better pattern matching and user experience. Make errors clear, include sufficient context for traceability, and ensure they are actionable with specific fix instructions.
9393

94-
7. **Before making engineering decisions**: Document significant architectural or design decisions as Architectural Decision Records (ADRs) in `docs/decisions/`. Read [`docs/decisions/README.md`](../docs/decisions/README.md) for the ADR template and guidelines. This ensures decisions are properly documented with context, rationale, and consequences for future reference.
94+
7. **Understanding expected errors**: Read [`docs/contributing/known-issues.md`](../docs/contributing/known-issues.md) for known issues and expected behaviors. Some errors that appear red in E2E test output (like SSH host key warnings) are normal and expected - not actual failures.
9595

96-
8. **When organizing code within modules**: Follow the module organization conventions in [`docs/contributing/module-organization.md`](../docs/contributing/module-organization.md). Use top-down organization with public items first, high-level abstractions before low-level details, and important responsibilities before secondary concerns like error types.
96+
8. **Before making engineering decisions**: Document significant architectural or design decisions as Architectural Decision Records (ADRs) in `docs/decisions/`. Read [`docs/decisions/README.md`](../docs/decisions/README.md) for the ADR template and guidelines. This ensures decisions are properly documented with context, rationale, and consequences for future reference.
97+
98+
9. **When organizing code within modules**: Follow the module organization conventions in [`docs/contributing/module-organization.md`](../docs/contributing/module-organization.md). Use top-down organization with public items first, high-level abstractions before low-level details, and important responsibilities before secondary concerns like error types.
9799

98100
## 🧪 Build & Test
99101

docs/contributing/templates.md

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,190 @@ instance_name = "{{ instance_name }}"
9292
```
9393

9494
After applying the fix, manually correct any existing formatting issues in your `.tera` files by removing the spaces inside the curly braces.
95+
96+
## 📦 Adding New Ansible Playbooks
97+
98+
When adding new Ansible playbooks to the project, you need to understand the difference between **static playbooks** and **dynamic templates**, and follow the correct registration process.
99+
100+
### Static vs Dynamic Playbooks
101+
102+
#### Static Playbooks (No Tera Variables)
103+
104+
Static playbooks are standard Ansible YAML files that don't require variable substitution:
105+
106+
- **No `.tera` extension** - Just `.yml`
107+
- **No Tera variables** - No `{{ variable }}` syntax needed
108+
- **Direct copy** - Copied as-is from `templates/ansible/` to `build/` directory
109+
- **Examples**: `install-docker.yml`, `wait-cloud-init.yml`, `configure-security-updates.yml`
110+
111+
#### Dynamic Playbooks (With Tera Variables)
112+
113+
Dynamic playbooks need runtime variable substitution:
114+
115+
- **`.tera` extension** - Named like `inventory.ini.tera`
116+
- **Contains Tera variables** - Uses `{{ ansible_host }}`, `{{ username }}`, etc.
117+
- **Rendered during execution** - Variables replaced at runtime
118+
- **Examples**: Ansible inventory files with instance IPs
119+
120+
### Adding a Static Ansible Playbook
121+
122+
Follow these steps when adding a new static playbook:
123+
124+
#### Step 1: Create the Playbook File
125+
126+
Create your playbook in `templates/ansible/`:
127+
128+
```bash
129+
# Example: Adding a new security configuration playbook
130+
templates/ansible/configure-security-updates.yml
131+
```
132+
133+
Write standard Ansible YAML with no Tera variables:
134+
135+
```yaml
136+
---
137+
- name: Configure automatic security updates
138+
hosts: all
139+
become: true
140+
tasks:
141+
- name: Install unattended-upgrades package
142+
ansible.builtin.apt:
143+
name: unattended-upgrades
144+
state: present
145+
update_cache: true
146+
```
147+
148+
#### Step 2: Register in Template Copy List ⚠️ CRITICAL
149+
150+
**This is the step that's easy to miss!**
151+
152+
Add your playbook filename to the array in `src/infrastructure/external_tools/ansible/template/renderer/mod.rs`:
153+
154+
```rust
155+
// Find the copy_static_templates method
156+
async fn copy_static_templates(
157+
&self,
158+
template_manager: &TemplateManager,
159+
destination_dir: &Path,
160+
) -> Result<(), ConfigurationTemplateError> {
161+
// ... existing code ...
162+
163+
// Copy all playbook files
164+
for playbook in &[
165+
"update-apt-cache.yml",
166+
"install-docker.yml",
167+
"install-docker-compose.yml",
168+
"wait-cloud-init.yml",
169+
"configure-security-updates.yml", // 👈 ADD YOUR PLAYBOOK HERE
170+
] {
171+
self.copy_static_file(template_manager, playbook, destination_dir)
172+
.await?;
173+
}
174+
175+
tracing::debug!(
176+
"Successfully copied {} static template files",
177+
6 // 👈 UPDATE THE COUNT: ansible.cfg + N playbooks
178+
);
179+
180+
Ok(())
181+
}
182+
```
183+
184+
**Why This is Required:**
185+
186+
- The template system uses a **two-phase approach** (see `docs/technical/template-system-architecture.md`)
187+
- **Phase 1**: Static file copying - requires explicit registration
188+
- **Phase 2**: Dynamic rendering - automatic for `.tera` files
189+
- Without registration, your playbook **will not be copied** to the build directory
190+
- Ansible will fail with: `[ERROR]: the playbook: your-playbook.yml could not be found`
191+
192+
#### Step 3: Update the File Count
193+
194+
In the same method, update the debug log count:
195+
196+
```rust
197+
tracing::debug!(
198+
"Successfully copied {} static template files",
199+
6 // ansible.cfg + 5 playbooks 👈 Update this comment
200+
);
201+
```
202+
203+
#### Step 4: Test Your Changes
204+
205+
Run E2E tests to verify the playbook is copied correctly:
206+
207+
```bash
208+
# Run E2E config tests (faster, tests configuration only)
209+
cargo run --bin e2e-config-tests
210+
211+
# Or run full E2E tests
212+
cargo run --bin e2e-tests-full
213+
```
214+
215+
If you forgot Step 2, you'll see this error:
216+
217+
```text
218+
[ERROR]: the playbook: your-playbook.yml could not be found
219+
```
220+
221+
#### Step 5: Use the Playbook in Your Code
222+
223+
Create a step that executes your playbook:
224+
225+
```rust
226+
// In src/application/steps/system/your_step.rs
227+
pub struct YourStep {
228+
ansible_client: Arc<dyn AnsibleClient>,
229+
}
230+
231+
impl YourStep {
232+
pub async fn execute(&self) -> Result<(), YourStepError> {
233+
self.ansible_client
234+
.run_playbook("your-playbook.yml")
235+
.await
236+
.map_err(YourStepError::AnsibleExecution)?;
237+
238+
Ok(())
239+
}
240+
}
241+
```
242+
243+
### Common Mistakes
244+
245+
❌ **Forgetting to register the playbook** in `copy_static_templates`
246+
247+
- Error: Playbook not found during execution
248+
- Fix: Add playbook name to the array
249+
250+
❌ **Forgetting to update the file count** in debug log
251+
252+
- Error: Confusing logs during debugging
253+
- Fix: Update the count comment
254+
255+
❌ **Using `.tera` extension for static playbooks**
256+
257+
- Error: Unnecessary complexity
258+
- Fix: Only use `.tera` if you need variable substitution
259+
260+
❌ **Adding dynamic variables without `.tera` extension**
261+
262+
- Error: Variables not resolved, literal `{{ variable }}` in output
263+
- Fix: Rename to `.yml.tera` and handle in rendering phase
264+
265+
### Quick Checklist
266+
267+
When adding a static Ansible playbook:
268+
269+
- [ ] Create `.yml` file in `templates/ansible/`
270+
- [ ] Write standard Ansible YAML (no Tera variables)
271+
- [ ] Add filename to `copy_static_templates` array in `src/infrastructure/external_tools/ansible/template/renderer/mod.rs`
272+
- [ ] Update file count in debug log
273+
- [ ] Run E2E tests to verify
274+
- [ ] Create application step to execute the playbook
275+
- [ ] Verify playbook appears in `build/` directory during execution
276+
277+
### Related Documentation
278+
279+
- **Architecture**: [`docs/technical/template-system-architecture.md`](../technical/template-system-architecture.md) - Understanding the two-phase template system
280+
- **Tera Syntax**: This document (above) - When you DO need dynamic templates with variables
281+
- **Testing**: [`docs/e2e-testing.md`](../e2e-testing.md) - How to run E2E tests to validate your changes

docs/technical/template-system-architecture.md

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,17 @@ The system operates through two levels of indirection to balance portability wit
4040
### Static Templates
4141

4242
- **Processing**: Direct file copy from templates to build directory
43-
- **Examples**: Infrastructure definitions, playbooks
43+
- **Examples**: Infrastructure definitions, Ansible playbooks (`install-docker.yml`, `configure-security-updates.yml`)
4444
- **Use Case**: Configuration files that don't need variable substitution
45+
- **Registration**: **Must be explicitly registered** in the template renderer's copy list
46+
- **Guide**: See [`docs/contributing/templates.md`](../contributing/templates.md#-adding-new-ansible-playbooks) for adding new static Ansible playbooks
4547

4648
### Dynamic Templates (Tera)
4749

4850
- **Processing**: Variable substitution using Tera templating engine
49-
- **File Suffix**: `.tera` extension (e.g., `variables.tfvars.tera`)
50-
- **Use Case**: Configuration files requiring runtime parameters
51+
- **File Suffix**: `.tera` extension (e.g., `variables.tfvars.tera`, `inventory.ini.tera`)
52+
- **Use Case**: Configuration files requiring runtime parameters (IPs, usernames, paths)
53+
- **Registration**: Automatically discovered by `.tera` extension
5154

5255
## 🔧 Key Components
5356

@@ -63,6 +66,21 @@ The system operates through two levels of indirection to balance portability wit
6366
- **Ansible Renderer**: Processes configuration management templates
6467
- Handle the template → build directory rendering process
6568

69+
**Two-Phase Processing:**
70+
71+
1. **Phase 1 - Static File Copying**:
72+
73+
- Files without `.tera` extension are copied as-is
74+
- **Requires explicit registration** in the renderer's copy list
75+
- Example: `install-docker.yml` must be added to `copy_static_templates` array
76+
77+
2. **Phase 2 - Dynamic Template Rendering**:
78+
- Files with `.tera` extension are processed for variable substitution
79+
- Automatically discovered, no manual registration needed
80+
- Example: `inventory.ini.tera``inventory.ini` with resolved variables
81+
82+
⚠️ **Common Pitfall**: Forgetting to register static files in Phase 1 will cause "file not found" errors at runtime.
83+
6684
### Template Engine
6785

6886
- Tera-based templating for dynamic content

0 commit comments

Comments
 (0)