Update Timoni CUE schemas

Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
This commit is contained in:
Stefan Prodan
2023-12-17 15:53:22 +02:00
parent 03f8ad0251
commit adf8157da6
11 changed files with 304 additions and 56 deletions
+1 -1
View File
@@ -97,4 +97,4 @@ swagger:
.PHONY: timoni-build
timoni-build:
@timoni build podinfo ./timoni/podinfo -f ./timoni/podinfo/test_values.cue
@timoni build podinfo ./timoni/podinfo -f ./timoni/podinfo/debug_values.cue
@@ -3,7 +3,10 @@
package v1alpha1
import "strings"
import (
"encoding/base64"
"strings"
)
// Image defines the schema for OCI image reference used in Kubernetes PodSpec container image.
#Image: {
@@ -22,6 +25,10 @@ import "strings"
// Spec: https://github.com/opencontainers/image-spec/blob/main/descriptor.md#digests.
digest!: string
// PullPolicy defines the pull policy for the image.
// By default, it is set to IfNotPresent.
pullPolicy: *"IfNotPresent" | "Always" | "Never"
// Reference is the image address computed from repository, tag and digest
// in the format [REPOSITORY]:[TAG]@[DIGEST].
reference: string
@@ -42,3 +49,53 @@ import "strings"
reference: "\(repository):latest"
}
}
// ImagePullSecret is a generator for Kubernetes Secrets of type kubernetes.io/dockerconfigjson.
// Spec: https://kubernetes.io/docs/concepts/configuration/secret/#docker-config-secrets.
#ImagePullSecret: {
// Metadata is the Kubernetes object's metadata generated by Timoni.
meta=metadata: #Metadata
// Registry is the hostname of the container registry in the format [HOST[:PORT_NUMBER]].
registry!: string
// Username is the username used to authenticate to the container registry.
username!: string
// Password is the password used to authenticate to the container registry.
password!: string
// Optional suffix used to generate the Secret name.
suffix: *"" | string
let auth = base64.Encode(null, username+":"+password)
// The object is a read-only struct that contains the generated
// Kubernetes Secret of type kubernetes.io/dockerconfigjson.
object: {
apiVersion: "v1"
kind: "Secret"
type: "kubernetes.io/dockerconfigjson"
metadata: {
name: meta.name + suffix
namespace: meta.namespace
labels: meta.labels
if meta.annotations != _|_ {
annotations: meta.annotations
}
}
stringData: {
".dockerconfigjson": #"""
{
"auths": {
"\#(registry)": {
"username": "\#(username)",
"password": "\#(password)",
"auth": "\#(auth)"
}
}
}
"""#
}
}
}
@@ -0,0 +1,27 @@
// Copyright 2023 Stefan Prodan
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import "strings"
// InstanceName defines the schema for the name of a Timoni instance.
// The instance name is used as a Kubernetes label value and must be 63 characters or less.
#InstanceName: string & =~"^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$" & strings.MinRunes(1) & strings.MaxRunes(63)
// InstanceNamespace defines the schema for the namespace of a Timoni instance.
// The instance namespace is used as a Kubernetes label value and must be 63 characters or less.
#InstanceNamespace: string & =~"^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$" & strings.MinRunes(1) & strings.MaxRunes(63)
// InstanceOwnerReference defines the schema for Kubernetes labels used to denote ownership.
#InstanceOwnerReference: {
#Name: "instance.timoni.sh/name"
#Namespace: "instance.timoni.sh/namespace"
}
// InstanceModule defines the schema for the Module of a Timoni instance.
#InstanceModule: {
url: string & =~"^((oci|file)://.*)$"
version: *"latest" | string
digest?: string
}
@@ -5,6 +5,19 @@ package v1alpha1
import "strings"
// Annotations defines the schema for Kubernetes object metadata annotations.
#Annotations: {[string & strings.MaxRunes(253)]: string}
// Labels defines the schema for Kubernetes object metadata labels.
#Labels: {[string & strings.MaxRunes(253)]: string & =~"^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$" & strings.MaxRunes(63)}
#StdLabelName: "app.kubernetes.io/name"
#StdLabelVersion: "app.kubernetes.io/version"
#StdLabelPartOf: "app.kubernetes.io/part-of"
#StdLabelManagedBy: "app.kubernetes.io/managed-by"
#StdLabelComponent: "app.kubernetes.io/component"
#StdLabelInstance: "app.kubernetes.io/instance"
// Metadata defines the schema for Kubernetes object metadata.
#Metadata: {
// Version should be in the strict semver format. Is required when creating resources.
@@ -13,24 +26,90 @@ import "strings"
// Name must be unique within a namespace. Is required when creating resources.
// Name is primarily intended for creation idempotence and configuration definition.
// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names
name!: string & =~"^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$" & strings.MaxRunes(63)
name!: #InstanceName
// Namespace defines the space within which each name must be unique.
// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces
namespace!: string & =~"^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$" & strings.MaxRunes(63)
namespace!: #InstanceNamespace
// Annotations is an unstructured key value map stored with a resource that may be
// set to store and retrieve arbitrary metadata.
// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations
annotations?: {[string & =~"^(([A-Za-z0-9][-A-Za-z0-9_./]*)?[A-Za-z0-9])?$" & strings.MaxRunes(63)]: string}
annotations?: #Annotations
// Map of string keys and values that can be used to organize and categorize (scope and select) objects.
// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels
labels: {[string & =~"^(([A-Za-z0-9][-A-Za-z0-9_./]*)?[A-Za-z0-9])?$" & strings.MaxRunes(63)]: string & =~"^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$" & strings.MaxRunes(63)}
labels: #Labels
// Standard Kubernetes labels: app name and version.
// Standard Kubernetes labels: app name, version and managed-by.
labels: {
"app.kubernetes.io/name": name
"app.kubernetes.io/version": #Version
"\(#StdLabelName)": name
"\(#StdLabelVersion)": #Version
"\(#StdLabelManagedBy)": "timoni"
}
// LabelSelector selects Pods based on the app.kubernetes.io/name label.
#LabelSelector: #Labels & {
"\(#StdLabelName)": name
}
}
// MetaComponent generates the Kubernetes object metadata for a module namespaced component.
// The metadata.name is composed of the instance name and the component name.
// The metadata.labels contain the app.kubernetes.io/component label.
#MetaComponent: {
// Meta is the Kubernetes object's metadata generated by Timoni.
#Meta!: #Metadata
// Component is the name of the component used
// as a suffix for the generate object name.
#Component!: string & strings.MaxRunes(30)
name: #Meta.name + "-" + #Component
namespace: #Meta.namespace
labels: #Meta.labels
labels: "\(#StdLabelComponent)": #Component
annotations?: #Annotations
if #Meta.annotations != _|_ {
annotations: #Meta.annotations
}
// LabelSelector selects Pods based on the app.kubernetes.io/name
// and app.kubernetes.io/component labels.
#LabelSelector: #Labels & {
"\(#StdLabelComponent)": #Component
"\(#StdLabelName)": #Meta.name
}
}
// MetaClusterComponent generates the Kubernetes object metadata for a module non-namespaced component.
// The metadata.name is composed of the instance name and the component name.
// The metadata.namespace is unset.
// The metadata.labels contain the app.kubernetes.io/component label.
#MetaClusterComponent: {
// Meta is the Kubernetes object's metadata generated by Timoni.
#Meta!: #Metadata
// Component is the name of the component used
// as a suffix for the generate object name.
#Component!: string & strings.MaxRunes(30)
name: #Meta.name + "-" + #Component
labels: #Meta.labels
labels: "\(#StdLabelComponent)": #Component
annotations?: #Annotations
if #Meta.annotations != _|_ {
annotations: #Meta.annotations
}
// LabelSelector selects Pods based on the app.kubernetes.io/name
// and app.kubernetes.io/component labels.
#LabelSelector: #Labels & {
"\(#StdLabelComponent)": #Component
"\(#StdLabelName)": #Meta.name
}
}
@@ -0,0 +1,40 @@
// Copyright 2023 Stefan Prodan
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
"strconv"
"strings"
)
// CPUQuantity is a string that is validated as a quantity of CPU, such as 100m or 2000m.
#CPUQuantity: string & =~"^[1-9]\\d*m$"
// MemoryQuantity is a string that is validated as a quantity of memory, such as 128Mi or 2Gi.
#MemoryQuantity: string & =~"^[1-9]\\d*(Mi|Gi)$"
// ResourceRequirement defines the schema for the CPU and Memory resource requirements.
#ResourceRequirement: {
cpu?: #CPUQuantity
memory?: #MemoryQuantity
}
// ResourceRequirements defines the schema for the compute resource requirements of a container.
// More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/.
#ResourceRequirements: {
// Limits describes the maximum amount of compute resources allowed.
limits?: #ResourceRequirement
// Requests describes the minimum amount of compute resources required.
// Requests cannot exceed Limits.
requests?: #ResourceRequirement & {
if limits != _|_ {
if limits.cpu != _|_ {
_lc: strconv.Atoi(strings.Split(limits.cpu, "m")[0])
_rc: strconv.Atoi(strings.Split(requests.cpu, "m")[0])
#cpu: int & >=_rc & _lc
}
}
}
}
@@ -3,19 +3,17 @@
package v1alpha1
import "strings"
// Selector defines the schema for Kubernetes Pod label selector used in Deployments, Services, Jobs, etc.
#Selector: {
// Name must be unique within a namespace. Is required when creating resources.
// Name is primarily intended for creation idempotence and configuration definition.
// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names
#Name!: string & =~"^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$" & strings.MinRunes(1) & strings.MaxRunes(63)
#Name!: #InstanceName
// Map of string keys and values that can be used to organize and categorize (scope and select) objects.
// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels
labels: {[string & =~"^(([A-Za-z0-9][-A-Za-z0-9_./]*)?[A-Za-z0-9])?$" & strings.MaxRunes(63)]: string & =~"^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$" & strings.MaxRunes(63)}
labels: #Labels
// Standard Kubernetes label: app name.
labels: "app.kubernetes.io/name": #Name
labels: "\(#StdLabelName)": #Name
}
@@ -0,0 +1,29 @@
// Copyright 2023 Stefan Prodan
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
"strconv"
"strings"
)
// SemVer validates the input version string and extracts the major and minor version numbers.
// When Minimum is set, the major and minor parts must be greater or equal to the minimum
// or a validation error is returned.
#SemVer: {
// Input version string in strict semver format.
#Version!: string & =~"^\\d+\\.\\d+\\.\\d+(-[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?(\\+[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?$"
// Minimum is the minimum allowed MAJOR.MINOR version.
#Minimum: *"0.0.0" | string & =~"^\\d+\\.\\d+\\.\\d+(-[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?(\\+[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?$"
let minMajor = strconv.Atoi(strings.Split(#Minimum, ".")[0])
let minMinor = strconv.Atoi(strings.Split(#Minimum, ".")[1])
major: int & >=minMajor
major: strconv.Atoi(strings.Split(#Version, ".")[0])
minor: int & >=minMinor
minor: strconv.Atoi(strings.Split(#Version, ".")[1])
}
+51 -26
View File
@@ -7,45 +7,70 @@ import (
// Config defines the schema and defaults for the Instance values.
#Config: {
// UI setting
// Podinfo optional UI setting.
ui: {
color: *"#34577c" | string
message?: string
backend?: string
}
// Runtime version info
// Runtime version info automatically set at apply-time.
moduleVersion!: string
kubeVersion!: string
// Metadata (common to all resources)
// The minimum Kubernetes version to 1.20.
clusterVersion: timoniv1.#SemVer & {#Version: kubeVersion, #Minimum: "1.20.0"}
// Kubernetes metadata common to all resources.
metadata: timoniv1.#Metadata & {#Version: moduleVersion}
// Label selector (common to all resources)
// Label selector common to all resources.
selector: timoniv1.#Selector & {#Name: metadata.name}
// Deployment
// The number of pods replicas.
// By default, the number of replicas is 1.
replicas: *1 | int & >=0
// Pod
podAnnotations?: {[ string]: string}
// The image allows setting the container image repository,
// tag, digest and pull policy.
// The default image repository and tag is set in `values.cue`.
image!: timoniv1.#Image
// The resources allows setting the container resource requirements.
// By default, the container requests 10m CPU and 32Mi memory.
resources: timoniv1.#ResourceRequirements & {
requests: {
cpu: *"10m" | timoniv1.#CPUQuantity
memory: *"32Mi" | timoniv1.#MemoryQuantity
}
}
// The securityContext allows setting the container security context.
securityContext?: corev1.#SecurityContext
// Pod optinal settings.
podAnnotations?: {[string]: string}
podSecurityContext?: corev1.#PodSecurityContext
imagePullSecrets?: [...corev1.LocalObjectReference]
tolerations?: [ ...corev1.#Toleration]
affinity?: corev1.#Affinity
tolerations?: [...corev1.#Toleration]
topologySpreadConstraints?: [...corev1.#TopologySpreadConstraint]
// Container
image: timoniv1.#Image
imagePullPolicy: *"IfNotPresent" | string
resources?: corev1.#ResourceRequirements
securityContext?: corev1.#SecurityContext
// Pod affinity rules, by default, pods are scheduled on Linux nodes.
affinity: *{
nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: [{
matchExpressions: [{
key: corev1.#LabelOSStable
operator: "In"
values: ["linux"]
}]
}]
} | corev1.#Affinity
// Service
service: {
port: *80 | int & >0 & <=65535
annotations?: {[ string]: string}
labels?: {[ string]: string}
port: *80 | int & >0 & <=65535
annotations?: timoniv1.#Annotations
labels?: timoniv1.#Labels
}
// HorizontalPodAutoscaler (optional)
@@ -59,12 +84,12 @@ import (
// Ingress (optional)
ingress: {
enabled: *false | bool
tls: *false | bool
host: *"podinfo.local" | string
annotations?: {[ string]: string}
labels?: {[ string]: string}
className?: string
enabled: *false | bool
tls: *false | bool
host: *"podinfo.local" | string
className?: string
annotations?: timoniv1.#Annotations
labels?: timoniv1.#Labels
}
// ServiceMonitor (optional)
@@ -91,9 +116,9 @@ import (
config: #Config
objects: {
"\(config.metadata.name)-sa": #ServiceAccount & {_config: config}
"\(config.metadata.name)-svc": #Service & {_config: config}
"\(config.metadata.name)-deploy": #Deployment & {_config: config}
"\(config.metadata.name)-sa": #ServiceAccount & {_config: config}
"\(config.metadata.name)-svc": #Service & {_config: config}
"\(config.metadata.name)-deploy": #Deployment & {_config: config}
if config.autoscaling.enabled {
"\(config.metadata.name)-hpa": #HorizontalPodAutoscaler & {_config: config}
+2 -2
View File
@@ -10,7 +10,7 @@ import (
apiVersion: "apps/v1"
kind: "Deployment"
metadata: _config.metadata
spec: appsv1.#DeploymentSpec & {
spec: appsv1.#DeploymentSpec & {
if !_config.autoscaling.enabled {
replicas: _config.replicas
}
@@ -38,7 +38,7 @@ import (
{
name: _config.metadata.name
image: _config.image.reference
imagePullPolicy: _config.imagePullPolicy
imagePullPolicy: _config.image.pullPolicy
ports: [
{
name: "http"
+5 -5
View File
@@ -13,20 +13,20 @@ import (
_config: #Config
apiVersion: "batch/v1"
kind: "Job"
metadata: name: "\(_config.metadata.name)-test"
metadata: namespace: _config.metadata.namespace
metadata: labels: _config.metadata.labels
metadata: timoniv1.#MetaComponent & {
#Meta: _config.metadata
#Component: "test"
}
metadata: annotations: timoniv1.Action.Force
spec: batchv1.#JobSpec & {
template: corev1.#PodTemplateSpec & {
metadata: labels: _config.metadata.labels
let _checksum = uuid.SHA1(uuid.ns.DNS, yaml.Marshal(_config))
metadata: annotations: "timoni.sh/checksum": "\(_checksum)"
spec: {
containers: [{
name: "curl"
image: _config.test.image.reference
imagePullPolicy: _config.imagePullPolicy
imagePullPolicy: _config.test.image.pullPolicy
command: [
"curl",
"-v",
+2 -9
View File
@@ -5,9 +5,6 @@
package main
import (
"strconv"
"strings"
templates "timoni.sh/podinfo/templates"
)
@@ -39,16 +36,12 @@ timoni: {
}
}
// Enforce minimum Kubernetes version.
kubeMinorVersion: int & >=23
kubeMinorVersion: strconv.Atoi(strings.Split(instance.config.kubeVersion, ".")[1])
// Pass Kubernetes resources outputted by the instance
// to Timoni's multi-step apply.
apply: app: [ for obj in instance.objects {obj}]
apply: app: [for obj in instance.objects {obj}]
// Conditionally run tests after an install or upgrade.
if instance.config.test.enabled {
apply: test: [ for obj in instance.tests {obj}]
apply: test: [for obj in instance.tests {obj}]
}
}