16 KiB
description, argument-hint
| description | argument-hint |
|---|---|
| Create Jinja2 templates for Ansible | 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:
- 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:
# 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
{#
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)
{#
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
{#
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
{#
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, exampleanother_var: Description, type, example
Optional (with defaults):
optional_var: Description (default: value)another_optional: Description (default: value)
Example Ansible Task:
- 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:
# In defaults/main.yml or group_vars
variable_name: "value"
another_var: 8080
optional_var: "custom_value"
Testing:
# 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
validateparameter 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