Skip to content

Template Actions

Template actions render Jinja2 templates with full workflow context, enabling dynamic file generation for configurations, documentation, and code files.

Configuration

[[actions]]
name = "action-name"
type = "template"
source_path = "template-file-or-directory"
destination_path = "output-location"

Path Schemes

Template actions use path schemes for source_path and destination_path. See the Path Schemes guide for details on all available schemes.

Fields

source_path (required)

Path to template file or directory. Can be:

  • Single .j2 template file
  • Directory containing .j2 files (recursively rendered)

Type: ResourceUrl (string path)

Conventions:

  • Template files should use .j2 extension
  • Directory paths should NOT include wildcards
  • Relative to working directory by default
  • Usually prefixed with workflow:/// for workflow resources

destination_path (required)

Output location for rendered templates.

Type: ResourceUrl (string path)

Behavior:

  • For single file: Output file path
  • For directory: Output directory (structure mirrored)
  • Parent directories created automatically
  • Existing files overwritten

Template Context

Templates have access to all workflow context variables:

Variable Type Description
workflow Workflow Current workflow configuration
imbi_project ImbiProject Complete Imbi project data
github_repository GitHubRepository GitHub repo data (if applicable)
working_directory Path Workflow execution directory
starting_commit str Initial Git commit SHA

Examples

Single File Template

Workflow config:

[[actions]]
name = "generate-readme"
type = "template"
source_path = "workflow:///templates/README.md.j2"
destination_path = "repository:///README.md"

Template (templates/README.md.j2):

# {{ imbi_project.name }}

{{ imbi_project.description }}

## Project Information

- **Type**: {{ imbi_project.project_type }}
- **Namespace**: {{ imbi_project.namespace }}
- **Imbi URL**: {{ imbi_project.imbi_url }}

{% if github_repository %}
## Repository

- **GitHub**: {{ github_repository.html_url }}
- **Default Branch**: {{ github_repository.default_branch }}
{% endif %}

## Installation

```bash
pip install {{ imbi_project.slug }}

Generated by {{ workflow.configuration.name }} on {{ now() }}

### Directory Template

**Workflow config:**
```toml
[[actions]]
name = "render-configs"
type = "template"
source_path = "workflow:///templates/configs"
destination_path = "repository:///config/"

Directory structure:

workflow/templates/configs/
├── app.yaml.j2
├── database.yaml.j2
└── logging.yaml.j2

Result:

repository/config/
├── app.yaml
├── database.yaml
└── logging.yaml

GitHub Actions Workflow Template

Workflow config:

[[actions]]
name = "generate-ci-workflow"
type = "template"
source_path = "workflow:///ci-template.yml.j2"
destination_path = "repository:///.github/workflows/ci.yml"

Template:

name: CI

on:
  push:
    branches: [ {{ github_repository.default_branch }} ]
  pull_request:
    branches: [ {{ github_repository.default_branch }} ]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      {% if imbi_project.facts.get('Programming Language', '').startswith('Python') %}
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '{{ imbi_project.facts['Programming Language'].split()[-1] }}'

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -e .[dev]

      - name: Run tests
        run: pytest tests/ -v
      {% elif imbi_project.facts.get('Programming Language') == 'JavaScript' %}
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18'

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test
      {% endif %}

Dockerfile Template

Workflow config:

[[actions]]
name = "generate-dockerfile"
type = "template"
source_path = "workflow:///Dockerfile.j2"
destination_path = "repository:///Dockerfile"

Template:

{% set python_version = imbi_project.facts.get('Programming Language', 'Python 3.12').split()[-1] %}
FROM python:{{ python_version }}-slim

LABEL maintainer="{{ imbi_project.namespace }}"
LABEL project="{{ imbi_project.slug }}"

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

{% if imbi_project.project_type == 'api' %}
CMD ["uvicorn", "{{ imbi_project.slug }}.main:app", "--host", "0.0.0.0", "--port", "8000"]
{% elif imbi_project.project_type == 'consumer' %}
CMD ["python", "-m", "{{ imbi_project.slug }}.consumer"]
{% else %}
CMD ["python", "-m", "{{ imbi_project.slug }}"]
{% endif %}

Jinja2 Template Features

Variables

{{ imbi_project.name }}
{{ imbi_project.slug }}
{{ workflow.configuration.name }}

Conditionals

{% if imbi_project.project_type == 'api' %}
  API-specific content
{% elif imbi_project.project_type == 'consumer' %}
  Consumer-specific content
{% else %}
  Default content
{% endif %}

Loops

{% for env in imbi_project.environments %}
- {{ env.name }}: {{ env.url }}
{% endfor %}

Filters

{{ imbi_project.name | upper }}
{{ imbi_project.slug | replace('-', '_') }}
{{ imbi_project.description | truncate(100) }}

Tests

{% if github_repository is defined %}
  Has GitHub repository
{% endif %}

{% if imbi_project.facts %}
  Has facts defined
{% endif %}

Comments

{# This is a comment - won't appear in output #}
{{ imbi_project.name }}  {# inline comment #}

Common Patterns

Configuration File Generation

[[actions]]
name = "generate-app-config"
type = "template"
source_path = "workflow:///app.yaml.j2"
destination_path = "repository:///config/app.yaml"

Template:

application:
  name: {{ imbi_project.slug }}
  type: {{ imbi_project.project_type }}

{% if imbi_project.environments %}
environments:
{% for env in imbi_project.environments %}
  {{ env.name }}:
    url: {{ env.url }}
    enabled: true
{% endfor %}
{% endif %}

database:
  host: ${DB_HOST}
  port: ${DB_PORT}
  name: {{ imbi_project.slug | replace('-', '_') }}

Multi-File Template Directory

[[actions]]
name = "generate-all-configs"
type = "template"
source_path = "workflow:///templates"
destination_path = "repository:///config/"

Template directory:

workflow/templates/
├── app.yaml.j2
├── database.yaml.j2
├── logging.yaml.j2
└── monitoring.yaml.j2

Documentation Generation

[[actions]]
name = "generate-docs"
type = "template"
source_path = "workflow:///docs"
destination_path = "repository:///docs/"

Template:

# {{ imbi_project.name }} Documentation

## Overview
{{ imbi_project.description }}

## Quick Start

### Installation
```bash
pip install {{ imbi_project.slug }}

Usage

from {{ imbi_project.slug | replace('-', '_') }} import main

main()

API Reference

{% if imbi_project.project_type == 'api' %} The API is available at: https://{{ imbi_project.slug }}.example.com/api {% endif %}

Contributing

Contributions welcome! See CONTRIBUTING.md in the repository for details.


Generated from template by {{ workflow.configuration.name }}

## Advanced Templates

### Accessing Nested Data

```jinja2
{# Access project facts #}
{% if 'Programming Language' in imbi_project.facts %}
Language: {{ imbi_project.facts['Programming Language'] }}
{% endif %}

{# Access GitHub repository details #}
{% if github_repository %}
Stars: {{ github_repository.stargazers_count }}
Forks: {{ github_repository.forks_count }}
{% endif %}

Template Inheritance

Base template (base.j2):

# {{ imbi_project.name }}

{% block content %}
Default content
{% endblock %}

---
Generated by {{ workflow.configuration.name }}

Child template (readme.j2):

{% extends "base.j2" %}

{% block content %}
## Description
{{ imbi_project.description }}

## Installation
pip install {{ imbi_project.slug }}
{% endblock %}

Macros

{% macro render_environment(env) %}
## {{ env.name | title }}
- URL: {{ env.url }}
- Active: {{ env.active | default(true) }}
{% endmacro %}

# Environments

{% for env in imbi_project.environments %}
{{ render_environment(env) }}
{% endfor %}

Custom Filters

{# Convert kebab-case to snake_case #}
{{ imbi_project.slug | replace('-', '_') }}

{# Convert to SCREAMING_SNAKE_CASE #}
{{ imbi_project.slug | replace('-', '_') | upper }}

{# Truncate long descriptions #}
{{ imbi_project.description | truncate(100, True, '...') }}

Path Resolution

Both source and destination support ResourceUrl schemes:

# Workflow templates to repository
[[actions]]
type = "template"
source_path = "workflow:///templates/"
destination_path = "repository:///config/"

# Specific file paths
[[actions]]
type = "template"
source_path = "workflow:///README.md.j2"
destination_path = "repository:///README.md"

# Extracted data with templates
[[actions]]
type = "template"
source_path = "workflow:///process-extracted.j2"
destination_path = "extracted:///processed/output.txt"

Error Handling

Template actions raise errors for:

  • Missing source: Source file/directory doesn't exist
  • Template syntax errors: Invalid Jinja2 syntax
  • Undefined variables: Referenced variables not in context
  • I/O errors: Permission denied, disk full, etc.

Handling Undefined Variables

Strict mode (default - raises error):

{{ undefined_variable }}  {# Raises error #}

Graceful fallback:

{{ undefined_variable | default('fallback value') }}
{{ undefined_variable | default('') }}

Check before use:

{% if undefined_variable is defined %}
  {{ undefined_variable }}
{% endif %}

Implementation Notes

  • Templates rendered using Jinja2 with StrictUndefined by default
  • Context variables automatically passed via context.model_dump() in prompts.render()
  • .j2 extension NOT automatically removed from output filenames
  • Directory rendering is recursive via source_path.rglob('*')
  • Existing files overwritten without warning
  • Parent directories created automatically with mkdir(parents=True, exist_ok=True)
  • File permissions NOT explicitly preserved (uses default umask)
  • Template context is immutable during rendering
  • All Jinja2 built-in filters and tests available
  • Custom template functions: extract_image_from_dockerfile, extract_package_name_from_pyproject, get_component_version, python_init_file_path, read_file, compare_semver

Implementation Location: src/imbi_automations/actions/template.py:24-101

Best Practices

  1. Use .j2 extension: Makes templates easily identifiable
  2. Provide defaults: Use | default() for optional values
  3. Check existence: Test is defined before accessing optional data
  4. Document templates: Add comments explaining template logic
  5. Test templates: Verify output with sample data before production
  6. Version control: Keep templates in workflow resources
  7. Validate output: Use shell actions to validate generated configs