Initial commit
This commit is contained in:
577
commands/create-jinja2-template.md
Normal file
577
commands/create-jinja2-template.md
Normal file
@@ -0,0 +1,577 @@
|
||||
---
|
||||
description: Create Jinja2 templates for Ansible
|
||||
argument-hint: Optional template requirements
|
||||
---
|
||||
|
||||
# Jinja2 Template Creation
|
||||
|
||||
You are creating Jinja2 templates for Ansible using the jinja2-developer agent, which coordinates with domain specialists for accurate technical content.
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Identify Template Requirements
|
||||
|
||||
Determine what template is needed:
|
||||
- **Configuration files**: Application configs, server configs
|
||||
- **Scripts**: Shell scripts, service files
|
||||
- **Web content**: HTML, nginx configs, Apache configs
|
||||
- **Network configs**: FRR, netplan, interfaces
|
||||
- **Database configs**: PostgreSQL, MySQL, MongoDB
|
||||
- **System files**: /etc files, systemd units
|
||||
|
||||
### 2. Gather Template Information
|
||||
|
||||
If not specified, ask for:
|
||||
- **Template purpose**:
|
||||
- What file is being generated
|
||||
- Target application or service
|
||||
- Configuration goals
|
||||
- **Domain/technology**:
|
||||
- Web server (Nginx, Apache, Caddy)
|
||||
- Database (PostgreSQL, MySQL, Redis)
|
||||
- Network (FRR, BGP, OSPF, interfaces)
|
||||
- Application (custom app config)
|
||||
- System (systemd, cron, logrotate)
|
||||
- **Variables needed**:
|
||||
- Required variables (no defaults)
|
||||
- Optional variables (with defaults)
|
||||
- Variable types and validation
|
||||
- **Target platform**:
|
||||
- Operating system
|
||||
- Software versions
|
||||
- Environment (dev/staging/production)
|
||||
- **Domain specialist needed**:
|
||||
- Which specialist agent to consult
|
||||
- What expertise is required
|
||||
|
||||
### 3. Identify Domain Expert
|
||||
|
||||
**CRITICAL**: Before developing templates with technical content, identify specialist:
|
||||
|
||||
**Network configurations**:
|
||||
- FRR routing configs → `frr-config-generator`
|
||||
- Netplan configs → `netplan-config-generator`
|
||||
- Network interfaces → `interfaces-config-generator`
|
||||
|
||||
**Web servers**:
|
||||
- Nginx configs → Web server specialist
|
||||
- Apache configs → Web server specialist
|
||||
- HAProxy configs → Load balancer specialist
|
||||
|
||||
**Databases**:
|
||||
- PostgreSQL configs → Database specialist
|
||||
- MySQL configs → Database specialist
|
||||
- MongoDB configs → Database specialist
|
||||
|
||||
**Applications**:
|
||||
- Custom app configs → Application specialist
|
||||
- Kubernetes manifests → K8s specialist
|
||||
|
||||
**System**:
|
||||
- Systemd units → System specialist
|
||||
- Security configs → Security specialist
|
||||
|
||||
### 4. Consult Domain Specialist
|
||||
|
||||
**Ask user which specialist to consult**, then:
|
||||
|
||||
Launch appropriate specialist agent:
|
||||
```
|
||||
"Generate [technology] configuration for [purpose].
|
||||
Requirements:
|
||||
- [List specific requirements]
|
||||
- Target: [platform/version]
|
||||
- Environment: [dev/staging/prod]
|
||||
- Best practices for [specific concerns]"
|
||||
```
|
||||
|
||||
### 5. Create Template
|
||||
|
||||
Launch **jinja2-developer** with specialist guidance:
|
||||
```
|
||||
"Create Jinja2 template for [file/purpose].
|
||||
Technology: [web server/database/network/etc.]
|
||||
Specialist consulted: [agent-name]
|
||||
Specialist recommendations: [guidance from specialist]
|
||||
|
||||
Variables:
|
||||
- Required: [list]
|
||||
- Optional: [list with defaults]
|
||||
|
||||
Template should:
|
||||
- Include ansible_managed header
|
||||
- Incorporate specialist's configuration recommendations
|
||||
- Use proper Jinja2 syntax
|
||||
- Validate inputs where possible
|
||||
- Support different environments
|
||||
- Include helpful comments
|
||||
- Document all variables"
|
||||
```
|
||||
|
||||
### 6. Review Template
|
||||
|
||||
Check generated template for:
|
||||
- **Syntax**: Valid Jinja2 syntax
|
||||
- **Variables**: All variables defined and used correctly
|
||||
- **Defaults**: Sensible default values with `| default()`
|
||||
- **Validation**: Input checking where possible
|
||||
- **Comments**: Clear documentation
|
||||
- **ansible_managed**: Included in header
|
||||
- **Specialist guidance**: Properly incorporated
|
||||
- **Platform-specific**: OS/version conditionals if needed
|
||||
|
||||
### 7. Create Example Task
|
||||
|
||||
Generate example Ansible task to use the template:
|
||||
|
||||
```yaml
|
||||
- name: Template [file description]
|
||||
ansible.builtin.template:
|
||||
src: [template-name].j2
|
||||
dest: /path/to/destination
|
||||
owner: [user]
|
||||
group: [group]
|
||||
mode: '0644'
|
||||
validate: '[validation-command %s]' # If available
|
||||
backup: yes
|
||||
notify: [Handler name]
|
||||
tags: config
|
||||
```
|
||||
|
||||
### 8. Test Template
|
||||
|
||||
Validate template works:
|
||||
|
||||
```bash
|
||||
# Test template rendering
|
||||
ansible-playbook test-template.yml -i localhost, --connection=local
|
||||
|
||||
# Check rendered output
|
||||
cat /tmp/rendered-template
|
||||
|
||||
# Validate syntax (if validator available)
|
||||
nginx -t -c /tmp/rendered-template # For nginx
|
||||
postgresql --check /tmp/rendered-template # For PostgreSQL
|
||||
```
|
||||
|
||||
## Template Development Patterns
|
||||
|
||||
### Basic Configuration Template
|
||||
|
||||
**Example: Application Config**
|
||||
|
||||
```jinja2
|
||||
{#
|
||||
Template: app_config.ini.j2
|
||||
Purpose: Application configuration file
|
||||
Specialist: None (simple key-value config)
|
||||
|
||||
Required Variables:
|
||||
app_name: Application name
|
||||
app_port: Port to listen on
|
||||
|
||||
Optional Variables:
|
||||
app_debug: Enable debug mode (default: false)
|
||||
app_log_level: Logging level (default: info)
|
||||
app_workers: Number of workers (default: 4)
|
||||
#}
|
||||
|
||||
# {{ ansible_managed }}
|
||||
# Application Configuration
|
||||
|
||||
[general]
|
||||
name = {{ app_name }}
|
||||
port = {{ app_port }}
|
||||
environment = {{ app_environment | default('production') }}
|
||||
|
||||
{% if app_debug | default(false) %}
|
||||
debug = true
|
||||
log_level = debug
|
||||
{% else %}
|
||||
debug = false
|
||||
log_level = {{ app_log_level | default('info') }}
|
||||
{% endif %}
|
||||
|
||||
[performance]
|
||||
workers = {{ app_workers | default(4) }}
|
||||
timeout = {{ app_timeout | default(30) }}
|
||||
keepalive = {{ app_keepalive | default(5) }}
|
||||
|
||||
[database]
|
||||
host = {{ db_host }}
|
||||
port = {{ db_port | default(5432) }}
|
||||
name = {{ db_name }}
|
||||
user = {{ db_user }}
|
||||
{% if db_ssl_enabled | default(true) %}
|
||||
ssl_mode = require
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
### Network Configuration Template
|
||||
|
||||
**Example: FRR BGP (with specialist guidance)**
|
||||
|
||||
```jinja2
|
||||
{#
|
||||
Template: frr_bgp.conf.j2
|
||||
Purpose: FRR BGP routing configuration
|
||||
Specialist: frr-config-generator (consulted for BGP best practices)
|
||||
|
||||
Required Variables:
|
||||
bgp_local_asn: Local AS number
|
||||
bgp_router_id: BGP router ID
|
||||
bgp_peer_ip: Peer IP address
|
||||
bgp_peer_asn: Peer AS number
|
||||
|
||||
Optional Variables:
|
||||
bgp_peer_description: Peer description (default: 'BGP Peer')
|
||||
bgp_peer_password: Peer authentication password
|
||||
bgp_network: Network to advertise
|
||||
bgp_max_prefix: Maximum prefixes (default: 1000)
|
||||
#}
|
||||
|
||||
# {{ ansible_managed }}
|
||||
# FRR BGP Configuration
|
||||
# Specialist guidance: frr-config-generator
|
||||
|
||||
router bgp {{ bgp_local_asn }}
|
||||
bgp router-id {{ bgp_router_id }}
|
||||
bgp log-neighbor-changes
|
||||
no bgp default ipv4-unicast
|
||||
|
||||
{# Peer configuration from specialist recommendations #}
|
||||
neighbor {{ bgp_peer_ip }} remote-as {{ bgp_peer_asn }}
|
||||
neighbor {{ bgp_peer_ip }} description {{ bgp_peer_description | default('BGP Peer') }}
|
||||
|
||||
{% if bgp_peer_password is defined %}
|
||||
neighbor {{ bgp_peer_ip }} password {{ bgp_peer_password }}
|
||||
{% endif %}
|
||||
|
||||
{# Address family configuration #}
|
||||
address-family ipv4 unicast
|
||||
{% if bgp_network is defined %}
|
||||
network {{ bgp_network }}
|
||||
{% endif %}
|
||||
|
||||
neighbor {{ bgp_peer_ip }} activate
|
||||
neighbor {{ bgp_peer_ip }} prefix-list {{ bgp_prefix_list_in | default('PL-IN') }} in
|
||||
neighbor {{ bgp_peer_ip }} prefix-list {{ bgp_prefix_list_out | default('PL-OUT') }} out
|
||||
neighbor {{ bgp_peer_ip }} maximum-prefix {{ bgp_max_prefix | default(1000) }} 80
|
||||
|
||||
{% if bgp_default_originate | default(false) %}
|
||||
neighbor {{ bgp_peer_ip }} default-originate
|
||||
{% endif %}
|
||||
exit-address-family
|
||||
!
|
||||
|
||||
{# Prefix lists for route filtering #}
|
||||
{% if bgp_allowed_prefixes is defined %}
|
||||
ip prefix-list {{ bgp_prefix_list_in | default('PL-IN') }} seq 5 deny 0.0.0.0/0 le 32
|
||||
{% for prefix in bgp_allowed_prefixes %}
|
||||
ip prefix-list {{ bgp_prefix_list_in | default('PL-IN') }} seq {{ loop.index * 10 + 10 }} permit {{ prefix }}
|
||||
{% endfor %}
|
||||
!
|
||||
{% endif %}
|
||||
|
||||
{% if bgp_advertised_prefixes is defined %}
|
||||
{% for prefix in bgp_advertised_prefixes %}
|
||||
ip prefix-list {{ bgp_prefix_list_out | default('PL-OUT') }} seq {{ loop.index * 10 }} permit {{ prefix }}
|
||||
{% endfor %}
|
||||
ip prefix-list {{ bgp_prefix_list_out | default('PL-OUT') }} seq 1000 deny 0.0.0.0/0 le 32
|
||||
!
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
### Web Server Template
|
||||
|
||||
**Example: Nginx Virtual Host**
|
||||
|
||||
```jinja2
|
||||
{#
|
||||
Template: nginx_vhost.conf.j2
|
||||
Purpose: Nginx virtual host configuration
|
||||
Specialist: Web server specialist (for SSL and security hardening)
|
||||
|
||||
Required Variables:
|
||||
vhost_domain: Domain name
|
||||
vhost_root: Document root path
|
||||
|
||||
Optional Variables:
|
||||
vhost_port: HTTP port (default: 80)
|
||||
vhost_ssl: Enable SSL (default: false)
|
||||
vhost_ssl_cert: SSL certificate path
|
||||
vhost_ssl_key: SSL key path
|
||||
vhost_locations: Custom location blocks
|
||||
#}
|
||||
|
||||
# {{ ansible_managed }}
|
||||
# Nginx Virtual Host: {{ vhost_domain }}
|
||||
|
||||
server {
|
||||
listen {{ vhost_port | default(80) }};
|
||||
server_name {{ vhost_domain }} {% if vhost_aliases is defined %}{{ vhost_aliases | join(' ') }}{% endif %};
|
||||
|
||||
{% if vhost_ssl | default(false) %}
|
||||
# SSL Configuration (from specialist recommendations)
|
||||
listen 443 ssl http2;
|
||||
ssl_certificate {{ vhost_ssl_cert }};
|
||||
ssl_certificate_key {{ vhost_ssl_key }};
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 10m;
|
||||
|
||||
# Security headers
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
{% endif %}
|
||||
|
||||
root {{ vhost_root }};
|
||||
index index.html index.htm {% if vhost_php | default(false) %}index.php{% endif %};
|
||||
|
||||
# Logging
|
||||
access_log /var/log/nginx/{{ vhost_domain }}_access.log;
|
||||
error_log /var/log/nginx/{{ vhost_domain }}_error.log;
|
||||
|
||||
# Default location
|
||||
location / {
|
||||
try_files $uri $uri/ {% if vhost_php | default(false) %}=404{% else %}/index.html{% endif %};
|
||||
}
|
||||
|
||||
{% if vhost_php | default(false) %}
|
||||
# PHP-FPM configuration
|
||||
location ~ \.php$ {
|
||||
try_files $uri =404;
|
||||
fastcgi_pass unix:/var/run/php/php-fpm.sock;
|
||||
fastcgi_index index.php;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
include fastcgi_params;
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
{% if vhost_locations is defined %}
|
||||
# Custom locations
|
||||
{% for location in vhost_locations %}
|
||||
location {{ location.path }} {
|
||||
{% if location.proxy_pass is defined %}
|
||||
proxy_pass {{ location.proxy_pass }};
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
{% elif location.alias is defined %}
|
||||
alias {{ location.alias }};
|
||||
{% elif location.return is defined %}
|
||||
return {{ location.return }};
|
||||
{% endif %}
|
||||
|
||||
{% if location.auth_basic is defined %}
|
||||
auth_basic "{{ location.auth_basic }}";
|
||||
auth_basic_user_file {{ location.auth_basic_file }};
|
||||
{% endif %}
|
||||
}
|
||||
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
# Deny access to hidden files
|
||||
location ~ /\. {
|
||||
deny all;
|
||||
access_log off;
|
||||
log_not_found off;
|
||||
}
|
||||
}
|
||||
|
||||
{% if vhost_ssl | default(false) and vhost_redirect_http | default(true) %}
|
||||
# Redirect HTTP to HTTPS
|
||||
server {
|
||||
listen {{ vhost_port | default(80) }};
|
||||
server_name {{ vhost_domain }};
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
### Systemd Service Template
|
||||
|
||||
**Example: Systemd Unit File**
|
||||
|
||||
```jinja2
|
||||
{#
|
||||
Template: app_service.j2
|
||||
Purpose: Systemd service unit file
|
||||
Specialist: System specialist (for hardening and best practices)
|
||||
|
||||
Required Variables:
|
||||
service_name: Service name
|
||||
service_exec_start: Start command
|
||||
service_user: User to run as
|
||||
|
||||
Optional Variables:
|
||||
service_description: Service description
|
||||
service_working_directory: Working directory
|
||||
service_environment: Environment variables
|
||||
service_restart: Restart policy (default: on-failure)
|
||||
service_restart_sec: Restart delay (default: 10)
|
||||
#}
|
||||
|
||||
# {{ ansible_managed }}
|
||||
# Systemd Service: {{ service_name }}
|
||||
|
||||
[Unit]
|
||||
Description={{ service_description | default(service_name + ' Service') }}
|
||||
After=network.target {% if service_after is defined %}{{ service_after | join(' ') }}{% endif %}
|
||||
|
||||
{% if service_requires is defined %}
|
||||
Requires={{ service_requires | join(' ') }}
|
||||
{% endif %}
|
||||
|
||||
[Service]
|
||||
Type={{ service_type | default('simple') }}
|
||||
User={{ service_user }}
|
||||
{% if service_group is defined %}
|
||||
Group={{ service_group }}
|
||||
{% endif %}
|
||||
|
||||
{% if service_working_directory is defined %}
|
||||
WorkingDirectory={{ service_working_directory }}
|
||||
{% endif %}
|
||||
|
||||
{% if service_environment is defined %}
|
||||
{% for key, value in service_environment.items() %}
|
||||
Environment="{{ key }}={{ value }}"
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
ExecStart={{ service_exec_start }}
|
||||
{% if service_exec_reload is defined %}
|
||||
ExecReload={{ service_exec_reload }}
|
||||
{% endif %}
|
||||
|
||||
{% if service_exec_stop is defined %}
|
||||
ExecStop={{ service_exec_stop }}
|
||||
{% else %}
|
||||
KillMode={{ service_kill_mode | default('mixed') }}
|
||||
KillSignal={{ service_kill_signal | default('SIGTERM') }}
|
||||
{% endif %}
|
||||
|
||||
Restart={{ service_restart | default('on-failure') }}
|
||||
RestartSec={{ service_restart_sec | default(10) }}
|
||||
|
||||
# Security hardening (from specialist recommendations)
|
||||
{% if service_harden | default(true) %}
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem={{ service_protect_system | default('strict') }}
|
||||
ProtectHome={{ service_protect_home | default('true') }}
|
||||
ReadWritePaths={{ service_writable_paths | default(['/var/log/' + service_name, '/var/lib/' + service_name]) | join(' ') }}
|
||||
{% endif %}
|
||||
|
||||
# Resource limits
|
||||
{% if service_limit_nofile is defined %}
|
||||
LimitNOFILE={{ service_limit_nofile }}
|
||||
{% endif %}
|
||||
{% if service_limit_nproc is defined %}
|
||||
LimitNPROC={{ service_limit_nproc }}
|
||||
{% endif %}
|
||||
|
||||
# Logging
|
||||
StandardOutput={{ service_stdout | default('journal') }}
|
||||
StandardError={{ service_stderr | default('journal') }}
|
||||
SyslogIdentifier={{ service_name }}
|
||||
|
||||
[Install]
|
||||
WantedBy={{ service_wanted_by | default('multi-user.target') }}
|
||||
```
|
||||
|
||||
## Output Format
|
||||
|
||||
### Template Delivery
|
||||
|
||||
**Template**: [template-name].j2
|
||||
**Purpose**: [description]
|
||||
**Specialist Consulted**: [agent-name or "None"]
|
||||
**Technology**: [web server/database/network/etc.]
|
||||
|
||||
**Variables**:
|
||||
|
||||
Required:
|
||||
- `variable_name`: Description, type, example
|
||||
- `another_var`: Description, type, example
|
||||
|
||||
Optional (with defaults):
|
||||
- `optional_var`: Description (default: value)
|
||||
- `another_optional`: Description (default: value)
|
||||
|
||||
**Example Ansible Task**:
|
||||
```yaml
|
||||
- name: Template [file description]
|
||||
ansible.builtin.template:
|
||||
src: [template-name].j2
|
||||
dest: /path/to/file
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
validate: '[command %s]' # If applicable
|
||||
backup: yes
|
||||
notify: [Handler name]
|
||||
tags: config
|
||||
```
|
||||
|
||||
**Example Variables**:
|
||||
```yaml
|
||||
# In defaults/main.yml or group_vars
|
||||
variable_name: "value"
|
||||
another_var: 8080
|
||||
optional_var: "custom_value"
|
||||
```
|
||||
|
||||
**Testing**:
|
||||
```bash
|
||||
# Render template locally
|
||||
ansible-playbook test.yml -i localhost, --connection=local
|
||||
|
||||
# Validate rendered config
|
||||
[validation-command] /tmp/rendered-file
|
||||
|
||||
# Deploy to test environment
|
||||
ansible-playbook playbook.yml -i inventory/dev --check
|
||||
ansible-playbook playbook.yml -i inventory/dev
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
**Template Design**:
|
||||
- Always include `{{ ansible_managed }}` header
|
||||
- Use `| default()` for all optional variables
|
||||
- Add comments explaining non-obvious logic
|
||||
- Group related configuration sections
|
||||
- Use whitespace control (`{%-` and `-%}`) for clean output
|
||||
|
||||
**Variable Management**:
|
||||
- Namespace variables with role/template name
|
||||
- Document all variables in template header
|
||||
- Provide sensible defaults
|
||||
- Validate input where possible with `assert`
|
||||
|
||||
**Security**:
|
||||
- Never hardcode secrets in templates
|
||||
- Use Ansible Vault for sensitive variables
|
||||
- Set appropriate file permissions in task
|
||||
- Validate rendered configs before deploying
|
||||
|
||||
**Testing**:
|
||||
- Test rendering with example variables
|
||||
- Validate syntax of rendered output
|
||||
- Test in development before production
|
||||
- Use `validate` parameter when available
|
||||
|
||||
**Documentation**:
|
||||
- Document purpose in template header
|
||||
- List all required and optional variables
|
||||
- Include example values
|
||||
- Note specialist consultation if applicable
|
||||
- Explain complex logic with comments
|
||||
Reference in New Issue
Block a user