Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- Custom MiniJinja template filters: `sha256`, `base64_encode`, `base64_decode` available in all templates (render and seed spec files)
- `sha256` filter with optional `mode` parameter (`"hex"` default, `"bytes"` for byte array output)
- `base64_encode` / `base64_decode` filters for standard Base64 encoding and decoding with error handling for invalid input
- Filters are chainable: e.g. `{{ "data" | sha256 | base64_encode }}`
- `src/template_funcs.rs` dedicated module for template utility functions, designed for easy extension
- `docs/templating.md` documenting all template filters with usage patterns, chaining examples, and error reference
- `examples/template-functions/config.tmpl` example demonstrating sha256 and base64 filters
- Unit tests for sha256 (hex, bytes, empty, invalid mode), base64 (encode, decode, roundtrip, invalid input), and template integration (filter chaining)

### Changed
- Clarified that seed phases with only `create_if_missing` can omit the `seed_sets` field entirely (`seed_sets` defaults to empty via `#[serde(default)]`); updated integration test YAML specs accordingly

Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ rand = "0.8"
ureq = { version = "2", features = ["tls"], default-features = false }
rustls = { version = "0.23", default-features = false, features = ["ring", "logging", "std", "tls12"] }
minijinja = "2"
sha2 = "0.10"
base64 = "0.22"
rusqlite = { version = "0.31", features = ["bundled"], optional = true }
postgres = { version = "0.19", optional = true }
mysql = { version = "25", optional = true }
Expand Down
101 changes: 101 additions & 0 deletions docs/templating.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Template Functions

Initium extends the MiniJinja template engine with utility filters for hashing and encoding. These filters are available in all templates — both `render` templates and `seed` spec files.

## Available Filters

### `sha256`

Compute the SHA-256 hash of a string.

```jinja
{{ "hello" | sha256 }}
{# → 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 #}
```

**Parameters:**

| Parameter | Type | Default | Description |
| --------- | ------ | ------- | ---------------------------------------- |
| `mode` | string | `"hex"` | Output format: `"hex"` or `"bytes"` |

**Modes:**

- `"hex"` (default) — returns a lowercase hex string (64 characters).
- `"bytes"` — returns an array of 32 byte values (integers 0–255).

```jinja
{{ "hello" | sha256("hex") }}
{# → 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 #}

{{ "hello" | sha256("bytes") }}
{# → [44, 242, 77, ...] (32 integers) #}
```

### `base64_encode`

Encode a string to Base64 (standard alphabet with padding).

```jinja
{{ "hello world" | base64_encode }}
{# → aGVsbG8gd29ybGQ= #}
```

### `base64_decode`

Decode a Base64 string back to its original value. Returns an error if the input is not valid Base64 or the decoded bytes are not valid UTF-8.

```jinja
{{ "aGVsbG8gd29ybGQ=" | base64_decode }}
{# → hello world #}
```

## Chaining Filters

Filters can be chained to compose operations:

```jinja
{# SHA-256 hash then Base64-encode the hex digest #}
{{ "secret" | sha256 | base64_encode }}

{# Base64 encode then decode (roundtrip) #}
{{ "data" | base64_encode | base64_decode }}

{# Hash an environment variable value #}
{{ env.API_KEY | sha256 }}
```

## Use Cases

### Content Fingerprinting

Generate a checksum for a config value to detect changes:

```jinja
checksum: {{ env.CONFIG_DATA | sha256 }}
```

### Encoding Secrets

Base64-encode a value for Kubernetes secret manifests:

```jinja
data:
password: {{ env.DB_PASSWORD | base64_encode }}
```

### Verifying Integrity

Decode and verify Base64-encoded content:

```jinja
decoded_cert: {{ env.B64_CERT | base64_decode }}
```

## Error Handling

| Error | Cause |
| -------------------------------- | --------------------------------------------------------- |
| `sha256: unsupported mode '…'` | Mode parameter is not `"hex"` or `"bytes"` |
| `base64_decode: invalid input` | Input string is not valid Base64 |
| `base64_decode: not valid UTF-8` | Decoded bytes are not a valid UTF-8 string |
10 changes: 10 additions & 0 deletions examples/template-functions/config.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{# Example: using sha256 and base64 template filters #}

[checksums]
config_hash = {{ env.CONFIG_DATA | sha256 }}

[secrets]
db_password = {{ env.DB_PASSWORD | base64_encode }}

[chained]
password_hash_b64 = {{ env.DB_PASSWORD | sha256 | base64_encode }}
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod render;
mod retry;
mod safety;
mod seed;
mod template_funcs;

use clap::{Parser, Subcommand};

Expand Down
1 change: 1 addition & 0 deletions src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ pub fn template_render(input: &str) -> Result<String, String> {
let env_map: std::collections::HashMap<String, String> = env::vars().collect();
let mut jinja_env = minijinja::Environment::new();
jinja_env.set_undefined_behavior(minijinja::UndefinedBehavior::Lenient);
crate::template_funcs::register(&mut jinja_env);
jinja_env
.add_template("t", input)
.map_err(|e| format!("parsing template: {}", e))?;
Expand Down
1 change: 1 addition & 0 deletions src/seed/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ fn render_template(content: &str) -> Result<String, String> {
let env_map: std::collections::HashMap<String, String> = std::env::vars().collect();
let mut jinja_env = minijinja::Environment::new();
jinja_env.set_undefined_behavior(minijinja::UndefinedBehavior::Lenient);
crate::template_funcs::register(&mut jinja_env);
jinja_env
.add_template("seed", content)
.map_err(|e| format!("parsing seed template: {}", e))?;
Expand Down
Loading