Eval Tutorial#

This tutorial covers using the scafctl eval command group to test CEL expressions, render Go templates, and validate solution files — all without running a full solution.

Overview#

The eval commands are developer tools for quick iteration:

  • eval cel — Evaluate CEL expressions with inline or file-based data
  • eval template — Render Go templates against JSON/YAML data

For solution file validation, use the separate scafctl lint command (covered in Section 3 below).

┌──────────────┐         ┌──────────────┐         ┌──────────────┐
│  Expression  │  ────►  │  scafctl     │  ────►  │   Result     │
│  or Template │         │    eval      │         │   (stdout)   │
└──────────────┘         └──────────────┘         └──────────────┘

These commands are especially useful when writing or debugging resolver expressions, action templates, and solution configurations.

1. Evaluating CEL Expressions#

Basic Usage#

Evaluate a simple expression:

scafctl eval cel "1 + 2"
# 3
scafctl eval cel "1 + 2"
# 3

String operations:

scafctl eval cel '"hello".upperAscii()'
# HELLO
scafctl eval cel '"hello".upperAscii()'
# HELLO

Using Inline Data#

Pass JSON data to the expression via --data:

scafctl eval cel '_.name + " is " + string(_.age)' \
  --data '{"name": "Alice", "age": 30}'
# Alice is 30
scafctl eval cel '_.name + " is " + string(_.age)' `
  --data '{"name": "Alice", "age": 30}'
# Alice is 30

The data is available under _, just like in solution resolvers.

Using Data Files#

For larger datasets, use --data-file:

# Create a data file
cat > data.json << 'EOF'
{
  "items": [
    {"name": "item-a", "active": true},
    {"name": "item-b", "active": false},
    {"name": "item-c", "active": true}
  ]
}
EOF

# Filter active items
scafctl eval cel '_.items.filter(i, i.active).map(i, i.name)' --data-file data.json
# ["item-a", "item-c"]
# Create a data file
@'
{
  "items": [
    {"name": "item-a", "active": true},
    {"name": "item-b", "active": false},
    {"name": "item-c", "active": true}
  ]
}
'@ | Set-Content -Path data.json

# Filter active items
scafctl eval cel '_.items.filter(i, i.active).map(i, i.name)' --data-file data.json
# ["item-a", "item-c"]

Output Formats#

Use -o to control output format:

# JSON output
scafctl eval cel '{"greeting": "hello", "count": 42}' -o json

# YAML output
scafctl eval cel '{"greeting": "hello", "count": 42}' -o yaml
# JSON output
scafctl eval cel '{"greeting": "hello", "count": 42}' -o json

# YAML output
scafctl eval cel '{"greeting": "hello", "count": 42}' -o yaml

Built-in Functions#

Test scafctl’s custom CEL extension functions:

# String functions
scafctl eval cel '"hello-world".toCamelCase()'
# helloWorld

scafctl eval cel '"hello-world".toPascalCase()'
# HelloWorld

# List the available CEL functions
scafctl eval cel 'true' --list-functions
# String functions
scafctl eval cel '"hello-world".toCamelCase()'
# helloWorld

scafctl eval cel '"hello-world".toPascalCase()'
# HelloWorld

# List the available CEL functions
scafctl eval cel 'true' --list-functions

Tip: Use eval cel to prototype resolver expressions before adding them to a solution. This is much faster than running the entire solution each time.

2. Evaluating Go Templates#

Basic Usage#

Render a template with inline data:

scafctl eval template '{{.name}} has {{.count}} items' \
  --data '{"name": "project", "count": 5}'
# project has 5 items
scafctl eval template '{{.name}} has {{.count}} items' `
  --data '{"name": "project", "count": 5}'
# project has 5 items

Template Files#

For multi-line templates, use --template-file:

cat > greeting.tmpl << 'EOF'
Hello, {{.name}}!
You have {{len .items}} items:
{{range .items}}- {{.}}
{{end}}
EOF

scafctl eval template --template-file greeting.tmpl \
  --data '{"name": "Alice", "items": ["task-1", "task-2", "task-3"]}'
@'
Hello, {{.name}}!
You have {{len .items}} items:
{{range .items}}- {{.}}
{{end}}
'@ | Set-Content -Path greeting.tmpl

scafctl eval template --template-file greeting.tmpl `
  --data '{"name": "Alice", "items": ["task-1", "task-2", "task-3"]}'

Data Files#

Combine template files with data files:

scafctl eval template --template-file config.tmpl --data-file values.json
scafctl eval template --template-file config.tmpl --data-file values.json

Writing Output to File#

Save rendered output to a file:

scafctl eval template --template-file config.tmpl \
  --data-file values.json \
  --output generated-config.yaml
scafctl eval template --template-file config.tmpl `
  --data-file values.json `
  --output generated-config.yaml

Tip: Use eval template to preview scaffold/render actions before running the full solution workflow.

3. Validating Solution Files with scafctl lint#

While eval cel and eval template test individual expressions and templates, use the scafctl lint command to validate entire solution files against the schema and lint rules.

Note: scafctl lint is a separate top-level command, not part of eval. It’s included here because validation is a natural part of the expression development workflow.

Basic Validation#

Check a solution file for schema errors and lint violations:

scafctl lint -f solution.yaml
scafctl lint -f solution.yaml

Get structured validation results:

scafctl lint -f solution.yaml -o json
scafctl lint -f solution.yaml -o json

Filter by Severity#

Only show findings at or above a minimum severity level:

# Show only warnings and errors (skip info)
scafctl lint -f solution.yaml --severity warning
# Show only warnings and errors (skip info)
scafctl lint -f solution.yaml --severity warning

Quick Validation in Scripts#

Use quiet mode for CI/CD pipelines — returns exit code only (0 = clean, non-zero = errors):

if scafctl lint -f solution.yaml -o quiet; then
  echo "Valid!"
else
  echo "Validation failed"
  exit 1
fi
scafctl lint -f solution.yaml -o quiet
if ($LASTEXITCODE -eq 0) { Write-Host "Valid!" } else { Write-Host "Validation failed"; exit 1 }

Exploring Lint Rules#

List all available lint rules:

scafctl lint rules
scafctl lint rules

Get a detailed explanation of a specific rule:

scafctl lint explain <rule-id>
scafctl lint explain <rule-id>

See the Linting Tutorial for a deep dive into lint rules and custom validation workflows.

4. Common Workflows#

Prototyping Resolver Expressions#

When building a new resolver, iterate quickly with eval cel:

# 1. Start with simple expression
scafctl eval cel '"Hello, " + _.name' --data '{"name": "World"}'

# 2. Add complexity
scafctl eval cel '_.items.filter(i, i.enabled).map(i, i.name).join(", ")' \
  --data-file real-data.json

# 3. Once working, copy to solution YAML
# 1. Start with simple expression
scafctl eval cel '"Hello, " + _.name' --data '{"name": "World"}'

# 2. Add complexity
scafctl eval cel '_.items.filter(i, i.enabled).map(i, i.name).join(", ")' `
  --data-file real-data.json

# 3. Once working, copy to solution YAML

Testing Template Rendering#

Preview template output before integrating into actions:

# Test with known data
scafctl eval template --template-file deploy.tmpl --data '{"env": "staging", "replicas": 3}'

# Test with real resolver output (from a snapshot)
scafctl eval template --template-file deploy.tmpl --data-file snapshot.json
# Test with known data
scafctl eval template --template-file deploy.tmpl --data '{"env": "staging", "replicas": 3}'

# Test with real resolver output (from a snapshot)
scafctl eval template --template-file deploy.tmpl --data-file snapshot.json

Pre-commit Validation#

Add to your pre-commit hooks or CI pipeline:

# Validate all solution files in a directory
find . -name 'solution.yaml' -exec scafctl lint -f {} -o quiet \;
Get-ChildItem -Recurse -Filter 'solution.yaml' | ForEach-Object { scafctl lint -f $_.FullName -o quiet }

Next Steps#