Embedding files
Timoni modules can embed plain files (configs, scripts, dashboards, web assets)
using the CUE @embed() attribute.
At build time, the file contents are loaded into CUE values that templates can use
like any other value, for example to populate the data of a Kubernetes ConfigMap or Secret.
Embedding is the type-safe alternative to inlining file contents as CUE strings: the files keep their native extension, so they can be edited, linted and highlighted with the standard tooling for their format, while the module retains full control over how their contents end up in the generated Kubernetes objects.
Embedding text files
Assuming you want to ship an nginx configuration and a static HTML page with your module,
stored as plain files in the module's templates directory:
templates/
├── config.cue
├── configmap.cue
├── nginx.default.conf
└── index.html
In the templates/configmap.cue file, enable embedding with the file-level
@extern(embed) attribute, then embed the sibling files with @embed():
@extern(embed)
package templates
import (
timoniv1 "timoni.sh/core/v1alpha1"
)
_nginxConf: string @embed(file="nginx.default.conf", type=text)
_indexHTML: string @embed(file="index.html", type=text)
#ConfigMap: timoniv1.#ImmutableConfig & {
#config: #Config
#Kind: timoniv1.#ConfigMapKind
#Meta: #config.metadata
#Data: {
"nginx.default.conf": _nginxConf
"index.html": _indexHTML
}
}
The file path is relative to the directory of the CUE file containing the attribute.
The type=text argument tells CUE to load the file contents as a string; it is required
for file extensions CUE does not recognise, such as .conf and .html.
Combined with the #ImmutableConfig generator, any change to the
embedded files results in a ConfigMap with a new name suffix, which triggers a rolling
update of the workloads referencing it.
Templating the file contents
Embedded files are static, but their contents can be rendered with the instance
configuration using the CUE text/template package, which implements Go templating:
@extern(embed)
package templates
import (
"text/template"
timoniv1 "timoni.sh/core/v1alpha1"
)
_nginxConf: string @embed(file="nginx.default.conf", type=text)
#ConfigMap: timoniv1.#ImmutableConfig & {
#config: #Config
#Kind: timoniv1.#ConfigMapKind
#Meta: #config.metadata
#Data: {
"nginx.default.conf": template.Execute(_nginxConf, #config)
}
}
With the templates/nginx.default.conf file referencing the config values
using Go template expressions:
server {
listen 8080;
server_name {{ .metadata.name }};
}
For a complete example, see the
minimal module
which embeds and templates its nginx configuration and HTML index page.
Embedding structured data
When embedding .json, .yaml or .toml files without the type argument,
CUE parses the file contents into a structured value instead of a string.
The parsed value can be validated against a CUE schema and further transformed
at build time:
@extern(embed)
package templates
import "encoding/json"
// Validate the embedded dashboard against a schema.
_dashboard: #GrafanaDashboard @embed(file="dashboard.json")
#DashboardConfigMap: {
apiVersion: "v1"
kind: "ConfigMap"
// omitted for brevity
data: "dashboard.json": json.Marshal(_dashboard)
}
This goes beyond verbatim file inclusion: invalid files fail the build with a validation error instead of being applied to the cluster as-is.
Embedding multiple files
A glob pattern embeds a set of files as a struct, keyed by the file path relative to the CUE file:
@extern(embed)
package templates
import "path"
_dashboards: _ @embed(glob="dashboards/*.json", type=text)
#DashboardConfigMap: {
apiVersion: "v1"
kind: "ConfigMap"
// omitted for brevity
data: {for p, content in _dashboards {(path.Base(p)): content}}
}
Note that the struct keys contain the directory prefix, e.g. dashboards/latency.json.
Since Kubernetes does not allow / in ConfigMap keys, the comprehension above
uses path.Base to strip the directory from the keys.
Restrictions
CUE enforces the following rules for embedded files:
- Files must be located inside the module's root directory, referencing files
outside the module with
../or absolute paths is not allowed. - Hidden files (dot files) and files under
cue.modcannot be embedded, nor matched by glob patterns.
When using a timoni.ignore file, make sure its patterns do not exclude
embedded files, otherwise timoni mod push would strip them from the
module's OCI artifact and the build would fail on the consumer's side.