Jsonnet requires validation functions to be written - with CUE it's built it and the result is much less verbose.
Jsonnet validation example (can be inlined with the schema, but gets verbose. Best split into a different file).
local std = std;
{
// Public entry: pass a config object `v`
webApp(v):
// -------- helpers --------
local has(o, k) = std.objectHas(o, k);
local orDefault(o, k, d) = if has(o, k) then o[k] else d;
local lower(x) = std.toLower(x);
local uniqueInts(xs) = std.sort(std.set(xs));
// Return a list of descriptive error strings about v (no throw yet)
local validate(v) =
local errs = [];
// required top-level fields
local errs = errs + (if has(v, 'name') && v.name != '' then [] else ['missing field: name']);
local errs = errs + (if has(v, 'image') && v.image != '' then [] else ['missing field: image']);
local errs = errs + (if has(v, 'endpoints') && std.isArray(v.endpoints) && std.length(v.endpoints) > 0
then [] else ['endpoints: must be a non-empty array']);
// short-circuit if endpoints absent/invalid (avoid cascading)
local eps = if has(v, 'endpoints') && std.isArray(v.endpoints) then v.endpoints else [];
// per-endpoint requireds
local errs =
errs + [
std.sprintf('endpoints[%d]: missing field: %s', i,
if !has(e, 'name') then 'name'
else if !has(e, 'host') then 'host'
else if !has(e, 'path') then 'path'
else if !has(e, 'port') then 'port'
else ''
)
for i in std.range(0, std.length(eps)-1)
for e in [eps[i]]
if !(has(e,'name') && has(e,'host') && has(e,'path') && has(e,'port'))
];
// per-endpoint value checks
local errs =
errs + [
std.sprintf('endpoints[%d]: name must be non-empty', i)
for i in std.range(0, std.length(eps)-1)
for e in [eps[i]]
if has(e,'name') && e.name == ''
] + [
std.sprintf('endpoints[%d]: path must start with "/"', i)
for i in std.range(0, std.length(eps)-1)
for e in [eps[i]]
if has(e,'path') && (std.length(e.path) == 0 || std.substr(e.path, 0, 1) != '/')
] + [
std.sprintf('endpoints[%d]: port must be 1..65535', i)
for i in std.range(0, std.length(eps)-1)
for e in [eps[i]]
if has(e,'port') && !(e.port >= 1 && e.port <= 65535)
] + [
std.sprintf('endpoints[%d]: type must be "ingress" or "managedAPI" (got %s)', i, e.type)
for i in std.range(0, std.length(eps)-1)
for e in [eps[i]]
if has(e,'type') && !(lower(e.type) == 'ingress' || lower(e.type) == 'managedapi')
];
// duplicate (host,path,type) guard
local keys = [ std.sprintf('%s|%s|%s', e.host, e.path, lower(orDefault(e,'type','ingress')))
for e in eps if has(e,'host') && has(e,'path') ];
local errs = errs + (if std.length(std.set(keys)) == std.length(keys) then [] else
['endpoints: duplicate (host,path,type) entries detected']);
// OK if zero errors
errs
;
// -------- run validation; throw if errors --------
local _errs = validate(v);
local _ = assert std.length(_errs) == 0 : 'webApp validation failed:\n- ' + std.join('\n- ', _errs);
// -------- safe, normalized view of v --------
local name = v.name;
local image = v.image;
local replicas = orDefault(v, 'replicas', 1);
local ingressClass = orDefault(v, 'ingressClass', 'nginx');
local serviceType = orDefault(v, 'serviceType', 'ClusterIP');
local gatewayName = orDefault(v, 'gatewayName', 'default-gateway');
local endpoints = v.endpoints;
// unique ports (ints)
local portsUnique = uniqueInts([ e.port for e in endpoints ]);
// -------- k8s objects --------
local deployment = {
apiVersion: 'apps/v1',
kind: 'Deployment',
metadata: {
name: name,
labels: { 'app.kubernetes.io/name': name },
},
spec: {
replicas: replicas,
selector: { matchLabels: { 'app.kubernetes.io/name': name } },
template: {
metadata: { labels: { 'app.kubernetes.io/name': name } },
spec: {
containers: [{
name: 'app',
image: image,
ports: [{ name: 'p' + std.toString(p), containerPort: p } for p in portsUnique],
}],
},
},
},
};
local service = {
apiVersion: 'v1',
kind: 'Service',
metadata: {
name: name,
labels: { 'app.kubernetes.io/name': name },
},
spec: {
type: serviceType,
selector: { 'app.kubernetes.io/name': name },
ports: [{ name: 'p' + std.toString(p), port: p, targetPort: p } for p in portsUnique],
},
};
local ingresses = [
{
apiVersion: 'networking.k8s.io/v1',
kind: 'Ingress',
metadata: {
name: name + '-' + e.name,
labels: { 'app.kubernetes.io/name': name },
annotations: { 'kubernetes.io/ingress.class': ingressClass },
},
spec: {
rules: [{
host: e.host,
http: {
paths: [{
path: e.path,
pathType: 'Prefix',
backend: { service: { name: name, port: { number: e.port } } },
}],
},
}],
// Optional TLS per endpoint if you include tlsSecretName in config
tls: if has(e, 'tlsSecretName') && e.tlsSecretName != '' then
[{ secretName: e.tlsSecretName, hosts: [ e.host ] }]
else null,
},
}
for e in endpoints
if !has(e,'type') || lower(e.type) == 'ingress'
];
local routes = [
{
apiVersion: 'gateway.networking.k8s.io/v1',
kind: 'HTTPRoute',
metadata: {
name: name + '-' + e.name,
labels: { 'app.kubernetes.io/name': name },
},
spec: {
parentRefs: [{ name: gatewayName }],
hostnames: [ e.host ],
rules: [{
matches: [{ path: { type: 'PathPrefix', value: e.path } }],
backendRefs: [{ name: name, port: e.port }],
}],
},
}
for e in endpoints
if has(e,'type') && lower(e.type) == 'managedapi'
];
// return flat list
[deployment, service] + ingresses + routes,
}#Endpoint: {
name: string & != ""
host: string & != ""
path: string & != "" & =~ `^/`
port: int & >= 1 & <= 65535
// default "ingress", allowed values "ingress" | "managedAPI"
type?: *"ingress" | "managedAPI"
// optional per-endpoint TLS for Ingress
tlsSecretName?: string & != ""
}
#App: {
name: string & != ""
image: string & != ""
// defaults / options
replicas?: *1 | int & >= 1
ingressClass?: *"nginx" | string
serviceType?: *"ClusterIP" | "NodePort" | "LoadBalancer"
gatewayName?: *"default-gateway" | string
// require a non-empty list: first element then "..." means ≥1 entries
endpoints: [ #Endpoint, ...#Endpoint ]
// ===== Derived / validation helpers =====
// canonicalized type for each endpoint (lowercased)
_canonType: [ for e in endpoints { strings.ToLower(e.type | "ingress") } ]
// de-dupe guard across (host,path,type)
_keys: [ for i, e in endpoints { e.host + "|" + e.path + "|" + _canonType[i] } ]
// Must not have duplicates
_noDupes: *true | bool & (len(list.Unique(_keys)) == len(_keys))
// Unique port set for containers/services
_uniquePorts: list.Unique([ for e in endpoints { e.port } ])
}