mirror of
https://github.com/holos-run/holos.git
synced 2026-03-20 09:15:02 +00:00
Multiple holos components rely on kustomize to modify the output of the upstream helm chart, for example patching a Deployment to inject the istio sidecar. The new holos cue based component system did not support running kustomize after helm template. This patch adds the kustomize execution if two fields are defined in the helm chart kind of cue output. The API spec is pretty loose in this patch but I'm proceeding for expedience and to inform the final API with more use cases as more components are migrated to cue.
396 lines
12 KiB
CUE
396 lines
12 KiB
CUE
package holos
|
|
|
|
import (
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
ksv1 "kustomize.toolkit.fluxcd.io/kustomization/v1"
|
|
appsv1 "k8s.io/api/apps/v1"
|
|
corev1 "k8s.io/api/core/v1"
|
|
rbacv1 "k8s.io/api/rbac/v1"
|
|
batchv1 "k8s.io/api/batch/v1"
|
|
es "external-secrets.io/externalsecret/v1beta1"
|
|
ss "external-secrets.io/secretstore/v1beta1"
|
|
is "cert-manager.io/issuer/v1"
|
|
ci "cert-manager.io/clusterissuer/v1"
|
|
crt "cert-manager.io/certificate/v1"
|
|
gw "networking.istio.io/gateway/v1beta1"
|
|
vs "networking.istio.io/virtualservice/v1beta1"
|
|
kc "sigs.k8s.io/kustomize/api/types"
|
|
"encoding/yaml"
|
|
)
|
|
|
|
let ResourcesFile = "resources.yaml"
|
|
|
|
// _apiVersion is the version of this schema. Defines the interface between CUE output and the holos cli.
|
|
_apiVersion: "holos.run/v1alpha1"
|
|
|
|
// #ClusterName is the cluster name for cluster scoped resources.
|
|
#ClusterName: #InputKeys.cluster
|
|
|
|
// #StageName is prod, dev, stage, etc... Usually prod for platform components.
|
|
#StageName: #InputKeys.stage
|
|
|
|
// #CollectionName is the preferred handle to the collection element of the instance name. A collection name mapes to an "application name" as described in the kubernetes recommended labels documentation. Refer to https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/
|
|
#CollectionName: #InputKeys.project
|
|
|
|
// #ComponentName is the name of the holos component.
|
|
#ComponentName: #InputKeys.component
|
|
|
|
// #InstanceName is the name of the holos component instance being managed varying by stage, project, and component names.
|
|
#InstanceName: "\(#StageName)-\(#CollectionName)-\(#ComponentName)"
|
|
|
|
// #InstancePrefix is the stage and project without the component name. Useful for dependency management among multiple components for a project stage.
|
|
#InstancePrefix: "\(#StageName)-\(#CollectionName)"
|
|
|
|
// #TargetNamespace is the target namespace for a holos component.
|
|
#TargetNamespace: string
|
|
|
|
// #SelectorLabels are mixed into selectors.
|
|
#SelectorLabels: {
|
|
"holos.run/stage.name": #StageName
|
|
"holos.run/project.name": #CollectionName
|
|
"holos.run/component.name": #ComponentName
|
|
...
|
|
}
|
|
|
|
// #CommonLabels are mixed into every kubernetes api object.
|
|
#CommonLabels: {
|
|
#SelectorLabels
|
|
"app.kubernetes.io/part-of": #StageName
|
|
"app.kubernetes.io/name": #CollectionName
|
|
"app.kubernetes.io/component": #ComponentName
|
|
"app.kubernetes.io/instance": #InstanceName
|
|
...
|
|
}
|
|
|
|
#ClusterObject: {
|
|
_description: string | *""
|
|
metadata: metav1.#ObjectMeta & {
|
|
labels: #CommonLabels
|
|
annotations: #Description & {
|
|
_Description: _description
|
|
...
|
|
}
|
|
}
|
|
...
|
|
}
|
|
|
|
#Description: {
|
|
_Description: string | *""
|
|
"holos.run/description": _Description
|
|
...
|
|
}
|
|
|
|
#NamespaceObject: #ClusterObject & {
|
|
metadata: namespace: string
|
|
...
|
|
}
|
|
|
|
// Kubernetes API Objects
|
|
#Namespace: corev1.#Namespace & #ClusterObject & {
|
|
metadata: {
|
|
name: string
|
|
labels: "kubernetes.io/metadata.name": name
|
|
}
|
|
}
|
|
#ClusterRole: #ClusterObject & rbacv1.#ClusterRole
|
|
#ClusterRoleBinding: #ClusterObject & rbacv1.#ClusterRoleBinding
|
|
#ClusterIssuer: #ClusterObject & ci.#ClusterIssuer & {...}
|
|
|
|
#Issuer: #NamespaceObject & is.#Issuer
|
|
#Role: #NamespaceObject & rbacv1.#Role
|
|
#RoleBinding: #NamespaceObject & rbacv1.#RoleBinding
|
|
#ConfigMap: #NamespaceObject & corev1.#ConfigMap
|
|
#ServiceAccount: #NamespaceObject & corev1.#ServiceAccount
|
|
#Pod: #NamespaceObject & corev1.#Pod
|
|
#Service: #NamespaceObject & corev1.#Service
|
|
#Job: #NamespaceObject & batchv1.#Job
|
|
#CronJob: #NamespaceObject & batchv1.#CronJob
|
|
#Deployment: #NamespaceObject & appsv1.#Deployment
|
|
#Gateway: #NamespaceObject & gw.#Gateway
|
|
#VirtualService: #NamespaceObject & vs.#VirtualService
|
|
#Certificate: #NamespaceObject & crt.#Certificate
|
|
|
|
// #HTTP01Cert defines a http01 certificate.
|
|
#HTTP01Cert: {
|
|
_name: string
|
|
_secret: string | *_name
|
|
SecretName: _secret
|
|
Host: _name + "." + #ClusterDomain
|
|
object: #Certificate & {
|
|
metadata: {
|
|
name: _secret
|
|
namespace: string | *#TargetNamespace
|
|
}
|
|
spec: {
|
|
commonName: Host
|
|
dnsNames: [Host]
|
|
secretName: _secret
|
|
issuerRef: kind: "ClusterIssuer"
|
|
issuerRef: name: "letsencrypt"
|
|
}
|
|
}
|
|
}
|
|
|
|
// Flux Kustomization CRDs
|
|
#Kustomization: #NamespaceObject & ksv1.#Kustomization & {
|
|
metadata: {
|
|
name: #InstanceName
|
|
namespace: string | *"flux-system"
|
|
}
|
|
spec: ksv1.#KustomizationSpec & {
|
|
interval: string | *"30m0s"
|
|
path: string | *"deploy/clusters/\(#InputKeys.cluster)/components/\(#InstanceName)"
|
|
prune: bool | *true
|
|
retryInterval: string | *"2m0s"
|
|
sourceRef: {
|
|
kind: string | *"GitRepository"
|
|
name: string | *"flux-system"
|
|
}
|
|
suspend?: bool
|
|
targetNamespace?: string
|
|
timeout: string | *"3m0s"
|
|
wait: bool | *true
|
|
dependsOn: [for k, v in #DependsOn {v}]
|
|
}
|
|
}
|
|
|
|
// #DependsOn stores all of the dependencies between components. It's a struct to support merging across levels in the tree.
|
|
#DependsOn: {
|
|
[NAME=_]: {
|
|
name: string
|
|
}
|
|
...
|
|
}
|
|
|
|
// External Secrets CRDs
|
|
#ExternalSecret: #NamespaceObject & es.#ExternalSecret & {
|
|
_name: string
|
|
metadata: {
|
|
name: _name
|
|
namespace: #TargetNamespace
|
|
}
|
|
spec: {
|
|
refreshInterval: string | *"1h"
|
|
secretStoreRef: {
|
|
kind: string | *"SecretStore"
|
|
name: string | *"default"
|
|
}
|
|
target: {
|
|
name: _name
|
|
creationPolicy: string | *"Owner"
|
|
deletionPolicy: string | *"Retain"
|
|
}
|
|
// Copy fields 1:1 from external Secret to target Secret.
|
|
dataFrom: [{extract: key: _name}]
|
|
}
|
|
}
|
|
|
|
#SecretStore: #NamespaceObject & ss.#SecretStore & {
|
|
_namespace: string
|
|
metadata: {
|
|
name: string | *"default"
|
|
namespace: _namespace
|
|
}
|
|
spec: provider: {
|
|
kubernetes: {
|
|
remoteNamespace: _namespace
|
|
auth: token: bearerToken: {
|
|
name: string | *"eso-reader"
|
|
key: string | *"token"
|
|
}
|
|
server: {
|
|
caBundle: #InputKeys.provisionerCABundle
|
|
url: #InputKeys.provisionerURL
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// #InputKeys defines the set of cue tags required to build a cue holos component. The values are used as lookup keys into the #Platform data.
|
|
#InputKeys: {
|
|
// cluster is usually the only key necessary when working with a component on the command line.
|
|
cluster: string @tag(cluster, type=string)
|
|
// stage is usually set by the platform or project.
|
|
stage: *"prod" | string @tag(stage, type=string)
|
|
// service is usually set by the component.
|
|
service: *component | string @tag(service, type=string)
|
|
// component is the name of the component
|
|
component: string @tag(component, type=string)
|
|
|
|
// GCP Project Info used for the Provisioner Cluster
|
|
gcpProjectID: string @tag(gcpProjectID, type=string)
|
|
gcpProjectNumber: int @tag(gcpProjectNumber, type=int)
|
|
|
|
// Same as cluster certificate-authority-data field in ~/.holos/kubeconfig.provisioner
|
|
provisionerCABundle: string @tag(provisionerCABundle, type=string)
|
|
// Same as the cluster server field in ~/.holos/kubeconfig.provisioner
|
|
provisionerURL: string @tag(provisionerURL, type=string)
|
|
}
|
|
|
|
// #Platform defines the primary lookup table for the platform. Lookup keys should be limited to those defined in #KeyTags.
|
|
#Platform: {
|
|
// org holds user defined values scoped organization wide. A platform has one and only one organization.
|
|
org: {
|
|
name: string
|
|
domain: string
|
|
contact: email: string
|
|
cloudflare: email: string
|
|
}
|
|
clusters: [ID=_]: {
|
|
name: string & ID
|
|
region?: string
|
|
}
|
|
stages: [ID=_]: {
|
|
name: string & ID
|
|
environments: [...{name: string}]
|
|
}
|
|
projects: [ID=_]: {
|
|
name: string & ID
|
|
}
|
|
services: [ID=_]: {
|
|
name: string & ID
|
|
}
|
|
}
|
|
|
|
// #APIObjects is the output type for api objects produced by cue. A map is used to aid debugging and clarity.
|
|
#APIObjects: {
|
|
// apiObjects holds each the api objects produced by cue.
|
|
apiObjects: {
|
|
[Kind=_]: {
|
|
[Name=_]: metav1.#TypeMeta & {
|
|
kind: Kind
|
|
}
|
|
}
|
|
}
|
|
|
|
// apiObjectMap holds the marshalled representation of apiObjects
|
|
apiObjectMap: {
|
|
for kind, v in apiObjects {
|
|
"\(kind)": {
|
|
for name, obj in v {
|
|
"\(name)": yaml.Marshal(obj)
|
|
}
|
|
}
|
|
}
|
|
...
|
|
}
|
|
}
|
|
|
|
// #OutputTypeMeta is shared among all output types
|
|
#OutputTypeMeta: {
|
|
// apiVersion is the output api version
|
|
apiVersion: _apiVersion
|
|
// kind is a discriminator of the type of output
|
|
kind: #PlatformSpec.kind | #KubernetesObjects.kind | #HelmChart.kind | #NoOutput.kind
|
|
// name holds a unique name suitable for a filename
|
|
metadata: name: string
|
|
// debug returns arbitrary debug output.
|
|
debug?: _
|
|
}
|
|
|
|
#NoOutput: {
|
|
#OutputTypeMeta
|
|
kind: string | *"Skip"
|
|
metadata: name: string | *"skipped"
|
|
}
|
|
|
|
// #KubernetesObjectOutput is the output schema of a single component.
|
|
#KubernetesObjects: {
|
|
#OutputTypeMeta
|
|
#APIObjects
|
|
kind: "KubernetesObjects"
|
|
metadata: name: #InstanceName
|
|
// ksObjects holds the flux Kustomization objects for gitops
|
|
ksObjects: [...#Kustomization] | *[#Kustomization]
|
|
// ksContent is the yaml representation of kustomization
|
|
ksContent: yaml.Marshal(#Kustomization)
|
|
// platform returns the platform data structure for visibility / troubleshooting.
|
|
platform: #Platform
|
|
}
|
|
|
|
// #Chart defines an upstream helm chart
|
|
#Chart: {
|
|
name: string
|
|
version: string
|
|
repository: {
|
|
name: string
|
|
url: string
|
|
}
|
|
}
|
|
|
|
// #ChartValues represent the values provided to a helm chart. Existing values may be imorted using cue import values.yaml -p holos then wrapping the values.cue content in #Values: {}
|
|
#ChartValues: {...}
|
|
|
|
// #HelmChart is a holos component which produces kubernetes api objects from cue values provided to the helm template command.
|
|
#HelmChart: {
|
|
#OutputTypeMeta
|
|
#APIObjects
|
|
kind: "HelmChart"
|
|
metadata: name: #InstanceName
|
|
// ksObjects holds the flux Kustomization objects for gitops.
|
|
ksObjects: [...#Kustomization] | *[#Kustomization]
|
|
// ksContent is the yaml representation of kustomization.
|
|
ksContent: yaml.MarshalStream(ksObjects)
|
|
// namespace defines the value passed to the helm --namespace flag
|
|
namespace: #TargetNamespace
|
|
// chart defines the upstream helm chart to process.
|
|
chart: #Chart
|
|
// values represents the helm values to provide to the chart.
|
|
values: #ChartValues
|
|
// valuesContent holds the values yaml
|
|
valuesContent: yaml.Marshal(values)
|
|
// platform returns the platform data structure for visibility / troubleshooting.
|
|
platform: #Platform
|
|
// instance returns the key values of the holos component instance.
|
|
instance: #InputKeys
|
|
// resources is the intermediate file name for api objects.
|
|
resourcesFile: ResourcesFile
|
|
// kustomizeFiles represents the files in a kustomize directory tree.
|
|
kustomizeFiles: #KustomizeFiles.Files
|
|
}
|
|
|
|
// #PlatformSpec is the output schema of a platform specification.
|
|
#PlatformSpec: {
|
|
#OutputTypeMeta
|
|
kind: "PlatformSpec"
|
|
}
|
|
|
|
// #SecretName is the name of a Secret, ususally coupling a Deployment to an ExternalSecret
|
|
#SecretName: string
|
|
|
|
// Cluster Domain is the cluster specific domain
|
|
#ClusterDomain: #InputKeys.cluster + "." + #Platform.org.domain
|
|
|
|
// #SidecarInject represents the istio sidecar inject label
|
|
#IstioSidecar: {
|
|
"sidecar.istio.io/inject": "true"
|
|
...
|
|
}
|
|
|
|
// #KustomizeTree represents a kustomize build.
|
|
#KustomizeFiles: {
|
|
Objects: {
|
|
"kustomization.yaml": #Kustomize
|
|
}
|
|
// Files holds the marshaled output holos writes to the filesystem
|
|
Files: {
|
|
for filename, obj in Objects {
|
|
"\(filename)": yaml.Marshal(obj)
|
|
}
|
|
...
|
|
}
|
|
}
|
|
|
|
// kustomization.yaml
|
|
#Kustomize: kc.#Kustomization & {
|
|
apiVersion: "kustomize.config.k8s.io/v1beta1"
|
|
kind: "Kustomization"
|
|
resources: [ResourcesFile]
|
|
...
|
|
}
|
|
|
|
// By default, render kind: Skipped so holos knows to skip over intermediate cue files.
|
|
// This enables the use of holos render ./foo/bar/baz/... when bar contains intermediary constraints which are not complete components.
|
|
// Holos skips over these intermediary cue instances.
|
|
{} & #NoOutput
|