sherpack-convert
Helm chart to Sherpack pack converter - Transform Go templates into elegant Jinja2 syntax.
Overview
sherpack-convert provides automated conversion of Helm charts to Sherpack packs. Rather than simply replicating Go template's function-based syntax, it transforms templates into idiomatic Jinja2 patterns that are more readable and maintainable.
Philosophy
Jinja2 elegance over Helm compatibility. Instead of creating 1:1 mappings of Go template quirks, we convert to natural Jinja2 patterns:
| Helm (Go template) | Sherpack (Jinja2) |
|---|---|
{{ index .Values.list 0 }} |
{{ values.list[0] }} |
{{ add 1 2 }} |
{{ 1 + 2 }} |
{{ ternary "a" "b" .X }} |
{{ "a" if x else "b" }} |
{{ printf "%s-%s" a b }} |
{{ a ~ "-" ~ b }} |
{{ coalesce .A .B "c" }} |
{{ a or b or "c" }} |
{{ include "chart.name" . }} |
{{ chart_name() }} |
{{- if .Values.x -}} |
{%- if values.x -%} |
Quick Start
use Path;
use ;
// Simple conversion
let result = convert?;
println!;
println!;
// With options
let result = convert_with_options?;
Conversion Process
1. Chart.yaml → Pack.yaml
# Helm Chart.yaml
apiVersion: v2
name: my-app
version: 1.0.0
appVersion: "2.0"
description: My application
type: application
dependencies:
- name: postgresql
version: "12.x.x"
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled
Converts to:
# Sherpack Pack.yaml
apiVersion: sherpack/v1
kind: application
metadata:
name: my-app
version: 1.0.0
appVersion: "2.0"
description: My application
dependencies:
- name: postgresql
version: "12.x.x"
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled
2. Template Conversion
Variables
{# Sherpack #}
{{ values.image.tag }}
{{ release.name }}
{{ pack.name }}
{{ capabilities.kubeVersion }}
Conditionals
...
...
...
{# Sherpack #}
{%- if values.ingress.enabled %}
...
{%- elif values.service.enabled %}
...
{%- else %}
...
{%- endif %}
Loops
- host:
:
{# Sherpack #}
{%- for host in values.hosts %}
- host: {{ host }}
{%- endfor %}
{%- for key, value in values.labels %}
{{ key }}: {{ value }}
{%- endfor %}
With Blocks
nodeSelector:
{# Sherpack #}
{%- if values.nodeSelector %}
nodeSelector:
{{ values.nodeSelector | toyaml | nindent(2) }}
{%- endif %}
3. Macro Conversion (Three-Pass System)
Helm's define/include pattern is converted to Jinja2 macros:
app.kubernetes.io/name:
app.kubernetes.io/instance:
{# Sherpack _helpers.tpl #}
{% macro mychart_fullname() %}
{{- (release.name ~ "-" ~ pack.name)[:63] | trimSuffix("-") -}}
{% endmacro %}
{% macro mychart_labels() %}
app.kubernetes.io/name: {{ mychart_name() }}
app.kubernetes.io/instance: {{ release.name }}
{% endmacro %}
Three-Pass Conversion:
- Pass 1: Extract all macro definitions (
defineblocks) - Pass 2: Build dependency graph between macros
- Pass 3: Generate import statements and convert calls
4. Filter/Function Mapping
| Helm | Sherpack | Notes |
|---|---|---|
toYaml |
toyaml |
Lowercase in Sherpack |
toJson |
tojson |
|
b64enc |
b64encode |
Full name |
b64dec |
b64decode |
|
indent N |
indent(N) |
Function syntax |
nindent N |
nindent(N) |
|
quote |
quote |
Same |
squote |
squote |
Same |
upper |
upper |
Same |
lower |
lower |
Same |
title |
title |
Same |
trim |
trim |
Same |
trimPrefix |
trimPrefix |
Same |
trimSuffix |
trimSuffix |
Same |
default X |
default(X) |
Function syntax |
required MSG |
required(MSG) |
|
printf FMT args... |
Native ~ or format |
|
ternary A B C |
A if C else B |
Native Jinja2 |
coalesce A B C |
A or B or C |
Native Jinja2 |
list A B C |
[A, B, C] |
Native Jinja2 |
dict K1 V1 K2 V2 |
{"K1": V1, "K2": V2} |
Native Jinja2 |
add A B |
A + B |
Native operators |
sub A B |
A - B |
|
mul A B |
A * B |
|
div A B |
A / B |
|
mod A B |
A % B |
|
and A B |
A and B |
|
or A B |
A or B |
|
not A |
not A |
|
eq A B |
A == B |
|
ne A B |
A != B |
|
lt A B |
A < B |
|
le A B |
A <= B |
|
gt A B |
A > B |
|
ge A B |
A >= B |
|
empty X |
not X |
|
len X |
X | length |
|
first X |
X | first |
|
last X |
X | last |
|
has KEY OBJ |
OBJ | has(KEY) |
|
hasKey OBJ KEY |
OBJ | has(KEY) |
Reordered |
keys OBJ |
OBJ | keys |
|
values OBJ |
OBJ | values |
|
include NAME CTX |
NAME() |
Macro call |
Unsupported Features
Some Helm features are intentionally not converted because they are anti-patterns in GitOps:
Cryptographic Functions
Why: Generates different output each time → non-deterministic manifests. Alternative: Use cert-manager or external-secrets.
Random Functions
Why: Different on every render → drift in GitOps. Alternative: Pre-generate values or use external-secrets.
Files API
Why: Complex file system operations during templating.
Alternative: Embed content in values.yaml or create ConfigMaps.
DNS/Network Lookups
Why: Runtime cluster dependency → non-deterministic. Alternative: Use explicit values or DNS-based discovery at runtime.
Lookup Function
Why: Queries live cluster state → breaks helm template.
Conversion: Returns empty dict {} (same as helm template).
Warning System
The converter produces detailed warnings for patterns that need attention:
use ;
let result = convert?;
for warning in &result.warnings
Warning Categories
| Category | Description |
|---|---|
UnsupportedFunction |
Function cannot be converted |
PartialConversion |
Converted but may need review |
DeprecatedPattern |
Helm pattern not recommended |
ComplexExpression |
May need manual adjustment |
MacroDependency |
Cross-chart macro reference |
API Reference
Core Types
/// Conversion options
/// Conversion result
/// A converted file
Low-Level API
For more control over the conversion process:
use ;
// Create converter
let converter = new;
// Convert single template
let jinja2 = converter.convert_template?;
// Parse Go template to AST
let ast = parse?;
// Transform AST to Jinja2
let output = transform?;
Architecture
sherpack-convert/
├── src/
│ ├── lib.rs # Public API
│ ├── parser.rs # Go template parser (pest)
│ ├── ast.rs # Abstract syntax tree
│ ├── transformer.rs # AST → Jinja2 transformer
│ ├── converter.rs # High-level conversion logic
│ ├── chart.rs # Chart.yaml → Pack.yaml
│ └── error.rs # Error types
├── src/go_template.pest # PEG grammar for Go templates
Parser (pest)
The Go template parser is built using pest with a PEG grammar:
template = { (text | action)* }
action = { "{{" ~ whitespace_control? ~ inner ~ whitespace_control? ~ "}}" }
inner = { comment | range | if_block | with_block | define | include | ... }
Transformer
The transformer walks the AST and generates Jinja2:
Testing
The converter includes comprehensive snapshot tests:
# Run all tests
# Update snapshots
Example test:
Dependencies
pest/pest_derive- PEG parser generatorphf- Perfect hash maps for filter/function lookupsherpack-core- Core typeswalkdir- Directory traversalregex- Pattern matchingmiette- Error reporting
License
MIT OR Apache-2.0