Telo Template Modules: Developer Guide
In Telo, a "template" is fundamentally a parameterized Module. By defining a strict contract and utilizing CEL-YAML directives, you can create reusable, encapsulated components that dynamically generate resources based on provided inputs.
1. Creating the Template Contract (kind: Kernel.Module)
The kind: Kernel.Module manifest acts as the public interface for your template. It dictates exactly what inputs the template requires and what outputs it exposes to the consumer.
- Naming: The template's
metadata.namemust be a kebab-case slug. - Inputs: Define
variables(standard configuration) andsecrets(sensitive data) using JSON Schema object properties. - Optionality: To make an input optional, you must explicitly define it with
default: null. - Outputs: Map internal resource properties to public outputs using the
exportsdictionary and CEL expressions.
Example: template-contract.yaml
kind: Kernel.Module
metadata:
name: secure-storage-template
variables:
type: object
properties:
bucketName:
type: string
enableReplication:
type: boolean
default: null
secrets:
type: object
properties:
accessKey:
type: string
exports:
primaryEndpoint: ${{ resources.MainBucket.endpointUrl }}
2. Implementing the Template Resources
The resources inside your template must be explicitly bound to the module's namespace and can use the injected inputs to shape their configuration.
- Namespace Binding: You must set
metadata.module: <module-slug>to bind the resource to your template. - Naming Rule: Resource names (
metadata.name) must not contain hyphens. - Dynamic Generation: Use the
$evaldirective to explicitly evaluate expressions at compile time, allowing you to dynamically set values based on the module'svariablesor conditionally render blocks using$if.
Example: template-resources.yaml
kind: Storage.Bucket
metadata:
name: MainBucket
module: secure-storage-template
spec:
# Using $eval to dynamically compute the name at compile time
name: $eval: ${{ variables.bucketName }}
credentials: ${{ secrets.accessKey }}
# Conditionally generating a property based on variables
$if: variables.enableReplication == true
$then:
replicationRegion: "eu-west-1"
3. Using the Template (kind: Kernel.Import)
To use the template in another module (like your application's Root Module), you instantiate it using the kind: Kernel.Import resource. The import acts as a local proxy that fulfills the template's contract.
- Instantiation: The
kind: Kernel.Importprovides the requiredvariablesandsecrets. Import aliases (metadata.name) must not contain hyphens. - Passing Environment Variables: If you are instantiating the template within the Root Module, you can securely pass host environment variables to the template's secrets using the
envobject. - Accessing Exports: Once instantiated, you can access the template's exported properties in your local resources using the
${{ imports.<ImportName>.<exportProperty> }}syntax.
Example: main-app.yaml
kind: Kernel.Module
metadata:
name: my-root-application
---
kind: Kernel.Import
metadata:
name: UserDataStorage
spec:
source: secure-storage-template
variables:
bucketName: "prod-user-data"
enableReplication: true
secrets:
# Injecting host environment variable (Root Module only)
accessKey: ${{ env.STORAGE_ACCESS_KEY }}
---
kind: Http.Api
metadata:
name: UserApi
spec:
# Utilizing the exported property from the instantiated template
storageUrl: ${{ imports.UserDataStorage.primaryEndpoint }}