███╗ ███╗ █████╗ ██████╗██╗ ██╗██╗███╗ ██╗███████╗██████╗ ██╗ ██╗
████╗ ████║██╔══██╗██╔════╝██║ ██║██║████╗ ██║██╔════╝██╔══██╗╚██╗ ██╔╝
██╔████╔██║███████║██║ ███████║██║██╔██╗ ██║█████╗ ██████╔╝ ╚████╔╝
██║╚██╔╝██║██╔══██║██║ ██╔══██║██║██║╚██╗██║██╔══╝ ██╔══██╗ ╚██╔╝
██║ ╚═╝ ██║██║ ██║╚██████╗██║ ██║██║██║ ╚████║███████╗██║ ██║ ██║
╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝ ╚═╝
A Backstage-compatible API for discovering, managing, and rendering KCL-based Crossplane claim templates.
| Feature | Description |
|---|---|
| Template Discovery | Browse and search KCL-based Crossplane claim templates |
| Template Details | Get schema information including parameters, validation rules, and UI hints |
| Claim Rendering | Render claims with custom parameters using KCL |
| Homerun2 Notifications | Optional event notifications via homerun2 omni-pitcher on claim orders |
| Backstage Integration | Native support for Backstage Software Catalog |
| OCI Support | Load templates from OCI registries |
| Parameter Schema | Exposes parameter metadata (types, enums, patterns, defaults) for client-side validation |
| Interactive CLI | Terminal UI for rendering claims with forms, dropdowns, and live YAML preview |
Note: Authentication and authorization are not built into the API. These are expected to be handled by an upstream API gateway or service mesh.
┌──────────────────────────────────────────┐
│ HTTP Request │
├──────────────────────────────────────────┤
│ Middleware (CORS, logging, request ID, │
│ error/panic recovery) │
├──────────────────────────────────────────┤
│ API Handlers (list, get, order claims) │
├──────────────────────────────────────────┤
│ Application Layer │
│ - Parameter merging with defaults │
│ - Template loading (dir + profile) │
├──────────────────────────────────────────┤
│ KCL Rendering Engine │
│ - OCI-based (kcl run oci://...) │
│ - File-based (Go SDK) │
├──────────────────────────────────────────┤
│ External: OCI Registries (ghcr.io) │
└──────────────────────────────────────────┘
Tech stack: Go, Gorilla Mux, KCL SDK, Cobra, Charmbracelet (bubbletea/huh)
git clone https://github.com/stuttgart-things/claim-machinery-api.git
cd claim-machinery-api
go mod download
# Run API server
go run main.go server
# Or interactive CLI (default when no subcommand is given)
go run main.go renderThe server loads templates from tests/profile.yaml by default and listens on port 8080.
| Endpoint | Method | Description |
|---|---|---|
/api/v1/claim-templates |
GET | List all available claim templates |
/api/v1/claim-templates/{name} |
GET | Get template details with parameter schema |
/api/v1/claim-templates/{name}/order |
POST | Render a claim with custom parameters |
/health |
GET | Health check |
/version |
GET | Build version metadata |
/openapi.yaml |
GET | OpenAPI specification |
/docs |
GET | OpenAPI documentation (Redoc UI) |
curl http://localhost:8080/api/v1/claim-templatesResponse:
{
"apiVersion": "api.claim-machinery.io/v1alpha1",
"kind": "ClaimTemplateList",
"items": [
{
"apiVersion": "resources.stuttgart-things.com/v1alpha1",
"kind": "ClaimTemplate",
"metadata": {
"name": "volumeclaim",
"title": "Crossplane Volume Claim",
"description": "Creates a persistent volume claim via Crossplane",
"tags": ["storage", "crossplane"]
},
"spec": { "type": "volumeclaim", "source": "oci://...", "parameters": [...] }
}
]
}curl http://localhost:8080/api/v1/claim-templates/volumeclaimReturns the full ClaimTemplate object including all parameter definitions (type, enum, default, required, pattern, etc.).
curl -X POST http://localhost:8080/api/v1/claim-templates/volumeclaim/order \
-H "Content-Type: application/json" \
-d '{"parameters": {"namespace": "production", "storage": "100Gi"}}'Response:
{
"apiVersion": "api.claim-machinery.io/v1alpha1",
"kind": "OrderResponse",
"metadata": {
"name": "volumeclaim",
"timestamp": "2025-01-10T12:00:00Z"
},
"rendered": "apiVersion: sthings.io/v1alpha1\nkind: VolumeClaim\n..."
}Extract just the YAML:
curl -s -X POST http://localhost:8080/api/v1/claim-templates/volumeclaim/order \
-H "Content-Type: application/json" \
-d '{"parameters": {"namespace": "production", "storage": "100Gi"}}' | jq -r '.rendered'Passing an empty body {} renders with all default values.
The order request supports an optional author field for tracking who ordered the resource:
curl -X POST http://localhost:8080/api/v1/claim-templates/volumeclaim/order \
-H "Content-Type: application/json" \
-d '{"author": "jane.doe", "parameters": {"namespace": "production"}}'More Examples (HarborProject)
# With default parameters
curl -X POST http://localhost:8080/api/v1/claim-templates/harborproject/order \
-H "Content-Type: application/json" \
-d '{}'
# With custom parameters
curl -X POST http://localhost:8080/api/v1/claim-templates/harborproject/order \
-H "Content-Type: application/json" \
-d '{
"parameters": {
"projectName": "my-app-project",
"harborURL": "https://harbor.idp.kubermatic.sva.dev",
"storageQuota": 10737418240,
"harborInsecure": false,
"providerConfigRef": "default"
}
}'curl http://localhost:8080/health
# {"status":"healthy","timestamp":"2025-01-10T12:00:00Z"}
curl http://localhost:8080/version
# {"version":"dev","commit":"none","buildDate":"unknown"}claim-machinery-api server# Default when no subcommand is given
claim-machinery-api
# Or explicitly
claim-machinery-api renderFeatures: template selection, dynamic parameter forms, enum dropdowns, random value selection, default pre-fill, type/pattern validation, live YAML preview, and optional file save.
Both modes support the same configuration options:
# Custom templates directory
claim-machinery-api --templates-dir /path/to/templates
# Custom profile (or disable with "")
claim-machinery-api --template-profile-path /path/to/profile.yaml
# Profile from HTTP URL (downloaded at startup)
claim-machinery-api --template-profile-path https://example.com/profile.yaml
# Environment variables
export TEMPLATES_DIR=/path/to/templates
export TEMPLATE_PROFILE_PATH=/path/to/profile.yaml # local file
export TEMPLATE_PROFILE_PATH=https://example.com/profile.yaml # or HTTP URL
export ENABLE_TEMPLATES_DIR=true # enable loading from templates directory
export PORT=9090
export LOG_FORMAT=json # default: text
# Homerun2 notifications (optional)
export ENABLE_HOMERUN=true
export HOMERUN_URL=https://pitcher.example.com
export HOMERUN_AUTH_TOKEN=your-bearer-tokenPriority: Flag > Environment Variable > Default
New in v0.6.0:
TEMPLATE_PROFILE_PATHsupports HTTP/HTTPS URLs. The profile is downloaded at startup before parsing template entries.
Template Profile
Add additional templates via a profile file (merged with the templates directory):
cat <<EOF > profile.yaml
---
templates:
- https://raw.githubusercontent.com/stuttgart-things/kcl/refs/heads/main/crossplane/claim-xplane-volumeclaim/templates/volumeclaim-simple.yaml
- /tmp/template123.yaml
EOFexport TEMPLATE_PROFILE_PATH=/absolute/path/to/profile.yaml
go run main.goOr via CLI flag (overrides env):
go run main.go --template-profile-path /absolute/path/to/profile.yamlOr as container:
docker run --rm \
-v $PWD/tests/profile.yaml:/tmp/profile.yaml \
-e TEMPLATE_PROFILE_PATH=/tmp/profile.yaml \
-e TEMPLATES_DIR="/tmp" \
-p 8080:8080 \
ghcr.io/stuttgart-things/claim-machinery-api:v0.5.6Behavior:
- Profile entries (URLs/paths) are validated; unreachable entries trigger a warning and are skipped
- Templates from the profile and directory are merged; duplicates are deduplicated based on
metadata.name(profile takes precedence) - On startup, the API displays loaded sources and final template names
Request ID and Correlation
- Incoming
X-Request-IDheader is preserved; otherwise the server generates an ID - Response always includes the
X-Request-IDheader (CORS: exposed) - Logs (text/JSON) include
requestIdfor correlation - On panics, the server returns JSON with
{"error":"internal server error","requestId":"..."}and logs structured output
Debug Mode
Enable debug logging to see parameter processing:
DEBUG=1 go run main.goHomerun2 Notifications
When enabled, the API sends a notification to homerun2 omni-pitcher on every successful claim order. This provides visibility into who ordered which resource.
Enable:
export ENABLE_HOMERUN=true
export HOMERUN_URL=https://pitcher.example.com
export HOMERUN_AUTH_TOKEN=your-bearer-token # optionalBoth ENABLE_HOMERUN and HOMERUN_URL must be set for notifications to fire. This lets you keep the URL configured while toggling notifications on/off.
Example notification sent to pitcher:
{
"title": "Claim Order: postgresql",
"message": "Template 'PostgreSQL Database Claim' ordered successfully.\n\nParameters:\n instanceClass: db.t3.medium\n namespace: production",
"severity": "success",
"author": "jane.doe",
"system": "claim-machinery-api",
"tags": "claim-order,database,crossplane,postgresql",
"artifacts": "oci://ghcr.io/stuttgart-things/claim-xplane-volumeclaim:0.1.1"
}Notifications are sent asynchronously and never block the API response. On failure, a warning is logged but the order still succeeds.
Testing
go test ./...Legacy Test CLIs (tests/cli & tests/cli-api)
Note: These are legacy test tools. The integrated CLI via
claim-machinery-api renderis recommended.
Two interactive CLI tools are available in /tests for testing and development.
Local KCL CLI (tests/cli):
Renders templates directly using KCL (requires kcl CLI installed locally).
go build -o tests/cli/claim-cli ./tests/cli/
./tests/cli/claim-cliAPI-Connected CLI (tests/cli-api):
Connects to the running API server - no local KCL required.
# Start API first
go run main.go
# Then run CLI
go build -o tests/cli-api/claim-cli-api ./tests/cli-api/
./tests/cli-api/claim-cli-apiDagger Build Pipeline
This project uses Dagger for reproducible builds, tests, and container image creation.
Available functions:
| Function | Description |
|---|---|
build-and-test |
Compile binary and run integration tests |
build |
Build Go binary only |
build-image |
Build container image with ko (with optional Trivy scanning) |
scan-image |
Scan container images for vulnerabilities |
lint |
Run Go linting |
test |
Run Go tests |
Quick start:
# Run tests
dagger call -m .dagger build-and-test --src . --progress plain
# Build container image and push to ttl.sh
dagger call -m .dagger build-image \
--src . \
--repo ttl.sh/claim-machinery-api-test \
--push true \
--scan true \
--progress plain
# Scan existing image
dagger call -m .dagger scan-image \
--image-ref ttl.sh/my-app:latest \
--severity "HIGH,CRITICAL" \
export --path /tmp/scan-report.jsonFull documentation: .dagger/README.md
Task Automation
Common tasks are available via Taskfile:
# Interactive task selector
task do
# Build and push image
task build-push-image
# Scan an image
task scan-image
# Run API locally
task run-local-goSee Taskfile.yaml for all available tasks.
The release pipeline publishes a kustomize base as an OCI artifact (ghcr.io/stuttgart-things/claim-machinery-api-kustomize:<version>) and a container image (ghcr.io/stuttgart-things/claim-machinery-api:<version>).
Kustomize Overlay
A kustomize overlay example is provided in deployment/overlays/example/. Pull the base and customize:
oras pull ghcr.io/stuttgart-things/claim-machinery-api-kustomize:v0.5.6 \
-o deployment/kustomize-base
kubectl kustomize deployment/overlays/example/Flux (GitOps)
A Flux app definition is available in the stuttgart-things/flux repository at apps/claim-machinery-api/. It uses a two-layer Flux reconciliation with OCIRepository + Flux Kustomization (not Helm) and Gateway API HTTPRoute.
1. Create the GitRepository source:
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: stuttgart-things-flux
namespace: flux-system
spec:
interval: 1m0s
url: https://github.com/stuttgart-things/flux.git
ref:
tag: v1.1.02. Create the Kustomization:
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: claim-machinery-api
namespace: flux-system
spec:
interval: 1h
retryInterval: 1m
timeout: 5m
sourceRef:
kind: GitRepository
name: stuttgart-things-flux
path: ./apps/claim-machinery-api
prune: true
wait: true
postBuild:
substitute:
CLAIM_MACHINERY_API_NAMESPACE: claim-machinery
CLAIM_MACHINERY_API_VERSION: v0.6.0
GATEWAY_NAME: my-gateway
GATEWAY_NAMESPACE: default
HOSTNAME: claim-api
DOMAIN: example.sthings-vsphere.labul.sva.de
CLAIM_MACHINERY_PROFILE_PATH: /app/config/profile.yaml
TEMPLATE_PROFILES: |
templates:
- https://raw.githubusercontent.com/stuttgart-things/kcl/refs/heads/main/crossplane/claim-xplane-volumeclaim/templates/volumeclaim-simple.yaml
- https://raw.githubusercontent.com/stuttgart-things/kcl/refs/heads/main/crossplane/claim-xplane-harborproject/templates/harborproject-simple.yaml| Variable | Default | Purpose |
|---|---|---|
CLAIM_MACHINERY_API_NAMESPACE |
claim-machinery |
Target namespace |
CLAIM_MACHINERY_API_VERSION |
v0.6.0 |
OCI tag + container image tag |
GATEWAY_NAME |
(required) | Gateway parentRef name |
GATEWAY_NAMESPACE |
default |
Gateway parentRef namespace |
HOSTNAME |
(required) | HTTPRoute hostname prefix |
DOMAIN |
(required) | HTTPRoute domain suffix |
TEMPLATE_PROFILES |
(required) | Full profile.yaml content (template URL list) |
CLAIM_MACHINERY_PROFILE_PATH |
/app/config/profile.yaml |
Override to external HTTP URL |
How it works: The outer Kustomization reads ./apps/claim-machinery-api from the GitRepository, substitutes variables, and creates the Namespace + OCIRepository + inner Kustomization + HTTPRoute. The inner Kustomization (release.yaml) reconciles the OCI kustomize base from ghcr.io/stuttgart-things/claim-machinery-api-kustomize, patches out the Ingress (replaced by HTTPRoute), overrides the container image tag, and applies the resulting manifests.
| Document | Description |
|---|---|
| SPEC.md | Full technical specification |
| ROADMAP.md | Project roadmap and tracking |
| API Examples | API usage examples |
| Template Schema | Claim template specification |
| CI/CD Pattern | CI/CD pipeline stages, Dagger functions, Taskfile interface |
| OpenAPI Spec | OpenAPI / Swagger definition |
Apache 2.0