Compare commits

..

23 Commits

Author SHA1 Message Date
Jeff McCune
4c2bc34d58 (#32) SecretStore Component
Separate the SecretStore resources from the namespaces component because
it creates a deadlock.  The secretstore crds don't get applied until the
eso component is managed.

The namespaces component should have nothing but core api objects, no
custom resources.
2024-03-07 16:01:22 -08:00
Jeff McCune
d831070f53 Trim trailing newlines from files when creating secrets
Without this patch, the pattern of echoing data (without -n) or editing
files in a directory to represent the keys of a secret results in a
trailing newline in the kubernetes Secret.

This patch trims off the trailing newline by default, with the option to
preserve it with the --trim-trailing-newlines=false flag.
2024-03-06 11:21:32 -08:00
Jeff McCune
340715f76c (#36) Provide certs to Cockroach DB and Zitadel with ExternalSecrets
This patch switches CockroachDB to use certs provided by ExternalSecrets
instead of managing Certificate resources in-cluster from the upstream
helm chart.

This paves the way for multi-cluster replication by moving certificates
outside of the lifecycle of the workload cluster cockroach db operates
within.

Closes: #36
2024-03-06 10:38:47 -08:00
Jeff McCune
64ffacfc7a (#36) Add Cockroach Issuer for Zitadel to provisioner cluster
Issuing mtls certs for cockroach db moves to the provisioner cluster so
we can more easily support cross cluster replication in the future.
crdb certs will be synced same as public tls certs, using ExternalSecret
resources.
2024-03-06 09:36:20 -08:00
Nate McCurdy
54acea42cb Merge pull request #37 from holos-run/nate/preflight
Add 'holos preflight' command, check for GitHub CLI
2024-03-06 09:32:54 -08:00
Nate McCurdy
5ef8e75194 Fix Actions warning during Lint by updating golangci-lint-action
Warning:
> Node.js 16 actions are deprecated. Please update the following actions to use Node.js 20: golangci/golangci-lint-action@v3. For more information see: https://github.blog/changelog/2023-09-22-github-actions-transitioning-from-node-16-to-node-20/.
2024-03-05 17:42:30 -08:00
Nate McCurdy
cb2b5c0f49 Add the 'preflight' subcommand; check for GitHub access
This adds a new holos subcommand: preflight

Initially, this just checks that the GitHub CLI is installed and
authenticated.

The preflight command will be used to validate that the user has the
neccessary CLI tools, access, and authorization to start using Holos and
setup a Holos cluster.
2024-03-05 17:40:08 -08:00
Jeff McCune
fd5a2fdbc1 (#36) Sync certs as ExternalSecrets from workload clusters
This patch replaces the httpbin and login cert on the workload clusters
with an ExternalSecret to sync the tls cert from the provisioner
cluster.
2024-03-05 17:05:10 -08:00
Jeff McCune
eb3e272612 (#36) Dynamically generate cluster certs from Platform spec
Each cluster should be more or less identical, configure certs from the
dynamic list of platform clusters.
2024-03-05 16:44:35 -08:00
Nate McCurdy
9f2a51bde8 Move the RunCmd function to the util package
More than one Holos package needs to execute commands, so pull out the
runCmd from builder and move it to the util package.

This commits adds the following to the util package:
* util.RunCmd func
* util.runResult struct
2024-03-05 15:12:14 -08:00
Jeff McCune
2b3b5a4887 (#36) Issue login and httpbin certs
This patch uses cert manager in the provisioner cluster to provision tls
certs for https://login.example.com and https://httpbin.k2.example.com

The certs are not yet synced to the clusters.  Next step is to replace
the Certificate resources with ExternalSecret resources, then remove
cert manager from the workload clusters.
2024-03-05 14:27:37 -08:00
Jeff McCune
7426e8f867 (#36) Move cert-manager to the provisioner cluster
This patch moves certificate management to the provisioner cluster to
centralize all secrets into the highly secured cluster.  This change
also simplifies the architecture in a number of ways:

1. Certificate lives are now completely independent of cluster
   lifecycle.
2. Remove the need for bi-directional sync to save cert secrets.
3. Workload clusters no longer need access to DNS.
2024-03-05 12:51:58 -08:00
Jeff McCune
cf0c455aa2 (#34) Add test for print secret data 2024-03-05 11:14:37 -08:00
Jeff McCune
752a3f912d (#34) Remove debug info logs 2024-03-05 11:05:51 -08:00
Jeff McCune
7d5852d675 (#34) Print secret data as json
Closes: #34
2024-03-05 11:03:47 -08:00
Jeff McCune
66b4ca0e6c (#31) Fix helm missing in actions workflow
Causing test failures that should pass.
2024-03-05 10:11:43 -08:00
Jeff McCune
b3f682453d (#31) Inject istio sidecar into Deployment zitadel using Kustomize
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.
2024-03-05 09:56:39 -08:00
Jeff McCune
0c3181ae05 (#31) Add VirtualService for Zitadel
Also import the Kustomize types using:

    cue get go sigs.k8s.io/kustomize/api/types/...
2024-03-04 17:18:46 -08:00
Jeff McCune
18cbff0c13 (#31) Add tls cert for zitadel to connect to cockroach db
Cockroach DB uses tls certs for client authentication.  Issue one for
Zitadel.

With this patch Zitadel starts up bit is not yet exposted with a
VirtualService.

Refer to https://zitadel.com/docs/self-hosting/manage/configure
2024-03-04 14:46:49 -08:00
Jeff McCune
b4fca0929c (#31) ExternalSecret for zitadel-masterkey 2024-03-04 14:31:27 -08:00
Jeff McCune
911d65bdc6 (#31) Setup login.ois.run with basic istio default Gateway
The istio default Gateway is the basis for what will become a dynamic
set of server entries specified from cue project data integrated with
extauthz.

For now we simply need to get the identity provider up and running as
the first step toward identity and access management.
2024-03-04 13:59:17 -08:00
Jeff McCune
2a5eccf0c1 (#33) Helm stderr logging
Log error messages from helm when building and rendering holos
components.

Closes: #33
2024-03-04 13:16:51 -08:00
Jeff McCune
9db4873205 (#31) Add Cockroach DB for Zitadel
Following https://github.com/zitadel/zitadel-charts/blob/main/examples/4-cockroach-secure/README.md
2024-03-04 10:31:39 -08:00
96 changed files with 3321 additions and 250 deletions

View File

@@ -1,6 +1,7 @@
---
# https://github.com/golangci/golangci-lint-action?tab=readme-ov-file#how-to-use
name: Lint
on:
"on":
push:
branches:
- main
@@ -22,6 +23,6 @@ jobs:
go-version: stable
cache: false
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
uses: golangci/golangci-lint-action@v4
with:
version: latest

View File

@@ -23,5 +23,10 @@ jobs:
with:
go-version: stable
- name: Set up Helm
uses: azure/setup-helm@v4.1.0
with:
version: 'latest'
- name: Test
run: ./scripts/test

View File

@@ -0,0 +1,280 @@
# Want helm errors to show up
! exec holos build .
stderr 'Error: execution error at \(zitadel/templates/secret_zitadel-masterkey.yaml:2:4\): Either set .Values.zitadel.masterkey xor .Values.zitadel.masterkeySecretName'
-- cue.mod --
package holos
-- zitadel.cue --
package holos
cluster: string @tag(cluster, string)
apiVersion: "holos.run/v1alpha1"
kind: "HelmChart"
metadata: name: "zitadel"
namespace: "zitadel"
chart: {
name: "zitadel"
version: "7.9.0"
repository: {
name: "zitadel"
url: "https://charts.zitadel.com"
}
}
-- vendor/zitadel/templates/secret_zitadel-masterkey.yaml --
{{- if (or (and .Values.zitadel.masterkey .Values.zitadel.masterkeySecretName) (and (not .Values.zitadel.masterkey) (not .Values.zitadel.masterkeySecretName)) ) }}
{{- fail "Either set .Values.zitadel.masterkey xor .Values.zitadel.masterkeySecretName" }}
{{- end }}
{{- if .Values.zitadel.masterkey -}}
apiVersion: v1
kind: Secret
type: Opaque
metadata:
name: zitadel-masterkey
{{- with .Values.zitadel.masterkeyAnnotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
labels:
{{- include "zitadel.labels" . | nindent 4 }}
stringData:
masterkey: {{ .Values.zitadel.masterkey }}
{{- end -}}
-- vendor/zitadel/Chart.yaml --
apiVersion: v2
appVersion: v2.46.0
description: A Helm chart for ZITADEL
icon: https://zitadel.com/zitadel-logo-dark.svg
kubeVersion: '>= 1.21.0-0'
maintainers:
- email: support@zitadel.com
name: zitadel
url: https://zitadel.com
name: zitadel
type: application
version: 7.9.0
-- vendor/zitadel/values.yaml --
# Default values for zitadel.
zitadel:
# The ZITADEL config under configmapConfig is written to a Kubernetes ConfigMap
# See all defaults here:
# https://github.com/zitadel/zitadel/blob/main/cmd/defaults.yaml
configmapConfig:
ExternalSecure: true
Machine:
Identification:
Hostname:
Enabled: true
Webhook:
Enabled: false
# The ZITADEL config under secretConfig is written to a Kubernetes Secret
# See all defaults here:
# https://github.com/zitadel/zitadel/blob/main/cmd/defaults.yaml
secretConfig:
# Annotations set on secretConfig secret
secretConfigAnnotations:
helm.sh/hook: pre-install,pre-upgrade
helm.sh/hook-delete-policy: before-hook-creation
helm.sh/hook-weight: "0"
# Reference the name of a secret that contains ZITADEL configuration.
configSecretName:
# The key under which the ZITADEL configuration is located in the secret.
configSecretKey: config-yaml
# ZITADEL uses the masterkey for symmetric encryption.
# You can generate it for example with tr -dc A-Za-z0-9 </dev/urandom | head -c 32
masterkey: ""
# Reference the name of the secret that contains the masterkey. The key should be named "masterkey".
# Note: Either zitadel.masterkey or zitadel.masterkeySecretName must be set
masterkeySecretName: ""
# Annotations set on masterkey secret
masterkeyAnnotations:
helm.sh/hook: pre-install,pre-upgrade
helm.sh/hook-delete-policy: before-hook-creation
helm.sh/hook-weight: "0"
# The CA Certificate needed for establishing secure database connections
dbSslCaCrt: ""
# The Secret containing the CA certificate at key ca.crt needed for establishing secure database connections
dbSslCaCrtSecret: ""
# The db admins secret containing the client certificate and key at tls.crt and tls.key needed for establishing secure database connections
dbSslAdminCrtSecret: ""
# The db users secret containing the client certificate and key at tls.crt and tls.key needed for establishing secure database connections
dbSslUserCrtSecret: ""
# Generate a self-signed certificate using an init container
# This will also mount the generated files to /etc/tls/ so that you can reference them in the pod.
# E.G. KeyPath: /etc/tls/tls.key CertPath: /etc/tls/tls.crt
# By default, the SAN DNS names include, localhost, the POD IP address and the POD name. You may include one more by using additionalDnsName like "my.zitadel.fqdn".
selfSignedCert:
enabled: false
additionalDnsName:
replicaCount: 3
image:
repository: ghcr.io/zitadel/zitadel
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: ""
chownImage:
repository: alpine
pullPolicy: IfNotPresent
tag: "3.19"
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
# Annotations to add to the deployment
annotations: {}
# Annotations to add to the configMap
configMap:
annotations:
helm.sh/hook: pre-install,pre-upgrade
helm.sh/hook-delete-policy: before-hook-creation
helm.sh/hook-weight: "0"
serviceAccount:
# Specifies whether a service account should be created
create: true
# Annotations to add to the service account
annotations:
helm.sh/hook: pre-install,pre-upgrade
helm.sh/hook-delete-policy: before-hook-creation
helm.sh/hook-weight: "0"
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
podAnnotations: {}
podAdditionalLabels: {}
podSecurityContext:
runAsNonRoot: true
runAsUser: 1000
securityContext: {}
# Additional environment variables
env:
[]
# - name: ZITADEL_DATABASE_POSTGRES_HOST
# valueFrom:
# secretKeyRef:
# name: postgres-pguser-postgres
# key: host
service:
type: ClusterIP
# If service type is "ClusterIP", this can optionally be set to a fixed IP address.
clusterIP: ""
port: 8080
protocol: http2
annotations: {}
scheme: HTTP
ingress:
enabled: false
className: ""
annotations: {}
hosts:
- host: localhost
paths:
- path: /
pathType: Prefix
tls: []
resources: {}
nodeSelector: {}
tolerations: []
affinity: {}
topologySpreadConstraints: []
initJob:
# Once ZITADEL is installed, the initJob can be disabled.
enabled: true
annotations:
helm.sh/hook: pre-install,pre-upgrade
helm.sh/hook-delete-policy: before-hook-creation
helm.sh/hook-weight: "1"
resources: {}
backoffLimit: 5
activeDeadlineSeconds: 300
extraContainers: []
podAnnotations: {}
# Available init commands :
# "": initialize ZITADEL instance (without skip anything)
# database: initialize only the database
# grant: set ALL grant to user
# user: initialize only the database user
# zitadel: initialize ZITADEL internals (skip "create user" and "create database")
command: ""
setupJob:
annotations:
helm.sh/hook: pre-install,pre-upgrade
helm.sh/hook-delete-policy: before-hook-creation
helm.sh/hook-weight: "2"
resources: {}
activeDeadlineSeconds: 300
extraContainers: []
podAnnotations: {}
additionalArgs:
- "--init-projections=true"
machinekeyWriter:
image:
repository: bitnami/kubectl
tag: ""
resources: {}
readinessProbe:
enabled: true
initialDelaySeconds: 0
periodSeconds: 5
failureThreshold: 3
livenessProbe:
enabled: true
initialDelaySeconds: 0
periodSeconds: 5
failureThreshold: 3
startupProbe:
enabled: true
periodSeconds: 1
failureThreshold: 30
metrics:
enabled: false
serviceMonitor:
# If true, the chart creates a ServiceMonitor that is compatible with Prometheus Operator
# https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/api.md#monitoring.coreos.com/v1.ServiceMonitor.
# The Prometheus community Helm chart installs this operator
# https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack#kube-prometheus-stack
enabled: false
honorLabels: false
honorTimestamps: true
pdb:
enabled: false
# these values are used for the PDB and are mutally exclusive
minAvailable: 1
# maxUnavailable: 1
annotations: {}

View File

@@ -0,0 +1,7 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go sigs.k8s.io/kustomize/api/types
package types
_#_BuiltinPluginLoadingOptions_name: "BploUndefinedBploUseStaticallyLinkedBploLoadFromFileSys"

View File

@@ -0,0 +1,10 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go sigs.k8s.io/kustomize/api/types
package types
// ConfigMapArgs contains the metadata of how to generate a configmap.
#ConfigMapArgs: {
#GeneratorArgs
}

View File

@@ -0,0 +1,10 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go sigs.k8s.io/kustomize/api/types
// Package types holds the definition of the kustomization struct and
// supporting structs. It's the k8s API conformant object that describes
// a set of generation and transformation operations to create and/or
// modify k8s resources.
// A kustomization file is a serialization of this struct.
package types

View File

@@ -0,0 +1,29 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go sigs.k8s.io/kustomize/api/types
package types
// FieldSpec completely specifies a kustomizable field in a k8s API object.
// It helps define the operands of transformations.
//
// For example, a directive to add a common label to objects
// will need to know that a 'Deployment' object (in API group
// 'apps', any version) can have labels at field path
// 'spec/template/metadata/labels', and further that it is OK
// (or not OK) to add that field path to the object if the
// field path doesn't exist already.
//
// This would look like
// {
// group: apps
// kind: Deployment
// path: spec/template/metadata/labels
// create: true
// }
#FieldSpec: {
path?: string @go(Path)
create?: bool @go(CreateIfNotPresent)
}
#FsSlice: [...#FieldSpec]

View File

@@ -0,0 +1,33 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go sigs.k8s.io/kustomize/api/types
package types
// GenerationBehavior specifies generation behavior of configmaps, secrets and maybe other resources.
#GenerationBehavior: int // #enumGenerationBehavior
#enumGenerationBehavior:
#BehaviorUnspecified |
#BehaviorCreate |
#BehaviorReplace |
#BehaviorMerge
#values_GenerationBehavior: {
BehaviorUnspecified: #BehaviorUnspecified
BehaviorCreate: #BehaviorCreate
BehaviorReplace: #BehaviorReplace
BehaviorMerge: #BehaviorMerge
}
// BehaviorUnspecified is an Unspecified behavior; typically treated as a Create.
#BehaviorUnspecified: #GenerationBehavior & 0
// BehaviorCreate makes a new resource.
#BehaviorCreate: #GenerationBehavior & 1
// BehaviorReplace replaces a resource.
#BehaviorReplace: #GenerationBehavior & 2
// BehaviorMerge attempts to merge a new resource with an existing resource.
#BehaviorMerge: #GenerationBehavior & 3

View File

@@ -0,0 +1,27 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go sigs.k8s.io/kustomize/api/types
package types
// GeneratorArgs contains arguments common to ConfigMap and Secret generators.
#GeneratorArgs: {
// Namespace for the resource, optional
namespace?: string @go(Namespace)
// Name - actually the partial name - of the generated resource.
// The full name ends up being something like
// NamePrefix + this.Name + hash(content of generated resource).
name?: string @go(Name)
// Behavior of generated resource, must be one of:
// 'create': create a new one
// 'replace': replace the existing one
// 'merge': merge with the existing one
behavior?: string @go(Behavior)
#KvPairSources
// Local overrides to global generatorOptions field.
options?: null | #GeneratorOptions @go(Options,*GeneratorOptions)
}

View File

@@ -0,0 +1,22 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go sigs.k8s.io/kustomize/api/types
package types
// GeneratorOptions modify behavior of all ConfigMap and Secret generators.
#GeneratorOptions: {
// Labels to add to all generated resources.
labels?: {[string]: string} @go(Labels,map[string]string)
// Annotations to add to all generated resources.
annotations?: {[string]: string} @go(Annotations,map[string]string)
// DisableNameSuffixHash if true disables the default behavior of adding a
// suffix to the names of generated resources that is a hash of the
// resource contents.
disableNameSuffixHash?: bool @go(DisableNameSuffixHash)
// Immutable if true add to all generated resources.
immutable?: bool @go(Immutable)
}

View File

@@ -0,0 +1,116 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go sigs.k8s.io/kustomize/api/types
package types
#HelmDefaultHome: "charts"
#HelmGlobals: {
// ChartHome is a file path, relative to the kustomization root,
// to a directory containing a subdirectory for each chart to be
// included in the kustomization.
// The default value of this field is "charts".
// So, for example, kustomize looks for the minecraft chart
// at {kustomizationRoot}/{ChartHome}/minecraft.
// If the chart is there at build time, kustomize will use it as found,
// and not check version numbers or dates.
// If the chart is not there, kustomize will attempt to pull it
// using the version number specified in the kustomization file,
// and put it there. To suppress the pull attempt, simply assure
// that the chart is already there.
chartHome?: string @go(ChartHome)
// ConfigHome defines a value that kustomize should pass to helm via
// the HELM_CONFIG_HOME environment variable. kustomize doesn't attempt
// to read or write this directory.
// If omitted, {tmpDir}/helm is used, where {tmpDir} is some temporary
// directory created by kustomize for the benefit of helm.
// Likewise, kustomize sets
// HELM_CACHE_HOME={ConfigHome}/.cache
// HELM_DATA_HOME={ConfigHome}/.data
// for the helm subprocess.
configHome?: string @go(ConfigHome)
}
#HelmChart: {
// Name is the name of the chart, e.g. 'minecraft'.
name?: string @go(Name)
// Version is the version of the chart, e.g. '3.1.3'
version?: string @go(Version)
// Repo is a URL locating the chart on the internet.
// This is the argument to helm's `--repo` flag, e.g.
// `https://itzg.github.io/minecraft-server-charts`.
repo?: string @go(Repo)
// ReleaseName replaces RELEASE-NAME in chart template output,
// making a particular inflation of a chart unique with respect to
// other inflations of the same chart in a cluster. It's the first
// argument to the helm `install` and `template` commands, i.e.
// helm install {RELEASE-NAME} {chartName}
// helm template {RELEASE-NAME} {chartName}
// If omitted, the flag --generate-name is passed to 'helm template'.
releaseName?: string @go(ReleaseName)
// Namespace set the target namespace for a release. It is .Release.Namespace
// in the helm template
namespace?: string @go(Namespace)
// AdditionalValuesFiles are local file paths to values files to be used in
// addition to either the default values file or the values specified in ValuesFile.
additionalValuesFiles?: [...string] @go(AdditionalValuesFiles,[]string)
// ValuesFile is a local file path to a values file to use _instead of_
// the default values that accompanied the chart.
// The default values are in '{ChartHome}/{Name}/values.yaml'.
valuesFile?: string @go(ValuesFile)
// ValuesInline holds value mappings specified directly,
// rather than in a separate file.
valuesInline?: {...} @go(ValuesInline,map[string]interface{})
// ValuesMerge specifies how to treat ValuesInline with respect to Values.
// Legal values: 'merge', 'override', 'replace'.
// Defaults to 'override'.
valuesMerge?: string @go(ValuesMerge)
// IncludeCRDs specifies if Helm should also generate CustomResourceDefinitions.
// Defaults to 'false'.
includeCRDs?: bool @go(IncludeCRDs)
// SkipHooks sets the --no-hooks flag when calling helm template. This prevents
// helm from erroneously rendering test templates.
skipHooks?: bool @go(SkipHooks)
// ApiVersions is the kubernetes apiversions used for Capabilities.APIVersions
apiVersions?: [...string] @go(ApiVersions,[]string)
// KubeVersion is the kubernetes version used by Helm for Capabilities.KubeVersion"
kubeVersion?: string @go(KubeVersion)
// NameTemplate is for specifying the name template used to name the release.
nameTemplate?: string @go(NameTemplate)
// SkipTests skips tests from templated output.
skipTests?: bool @go(SkipTests)
}
// HelmChartArgs contains arguments to helm.
// Deprecated. Use HelmGlobals and HelmChart instead.
#HelmChartArgs: {
chartName?: string @go(ChartName)
chartVersion?: string @go(ChartVersion)
chartRepoUrl?: string @go(ChartRepoURL)
chartHome?: string @go(ChartHome)
chartRepoName?: string @go(ChartRepoName)
helmBin?: string @go(HelmBin)
helmHome?: string @go(HelmHome)
values?: string @go(Values)
valuesLocal?: {...} @go(ValuesLocal,map[string]interface{})
valuesMerge?: string @go(ValuesMerge)
releaseName?: string @go(ReleaseName)
releaseNamespace?: string @go(ReleaseNamespace)
extraArgs?: [...string] @go(ExtraArgs,[]string)
}

View File

@@ -0,0 +1,40 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go sigs.k8s.io/kustomize/api/types
package types
#Cloud: string // #enumCloud
#enumCloud:
#GKE
#GKE: #Cloud & "gke"
// IAMPolicyGeneratorArgs contains arguments to generate a GKE service account resource.
#IAMPolicyGeneratorArgs: {
// which cloud provider to generate for (e.g. "gke")
cloud: #Cloud @go(Cloud)
// information about the kubernetes cluster for this object
kubernetesService: #KubernetesService @go(KubernetesService)
// information about the service account and project
serviceAccount: #ServiceAccount @go(ServiceAccount)
}
#KubernetesService: {
// the name used for the Kubernetes service account
name: string @go(Name)
// the name of the Kubernetes namespace for this object
namespace?: string @go(Namespace)
}
#ServiceAccount: {
// the name of the new cloud provider service account
name: string @go(Name)
// The ID of the project
projectId: string @go(ProjectId)
}

View File

@@ -0,0 +1,26 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go sigs.k8s.io/kustomize/api/types
package types
// Image contains an image name, a new name, a new tag or digest,
// which will replace the original name and tag.
#Image: {
// Name is a tag-less image name.
name?: string @go(Name)
// NewName is the value used to replace the original name.
newName?: string @go(NewName)
// TagSuffix is the value used to suffix the original tag
// If Digest and NewTag is present an error is thrown
tagSuffix?: string @go(TagSuffix)
// NewTag is the value used to replace the original tag.
newTag?: string @go(NewTag)
// Digest is the value used to replace the original image tag.
// If digest is present NewTag value is ignored.
digest?: string @go(Digest)
}

View File

@@ -0,0 +1,163 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go sigs.k8s.io/kustomize/api/types
package types
#KustomizationVersion: "kustomize.config.k8s.io/v1beta1"
#KustomizationKind: "Kustomization"
#ComponentVersion: "kustomize.config.k8s.io/v1alpha1"
#ComponentKind: "Component"
#MetadataNamespacePath: "metadata/namespace"
#MetadataNamespaceApiVersion: "v1"
#MetadataNamePath: "metadata/name"
#OriginAnnotations: "originAnnotations"
#TransformerAnnotations: "transformerAnnotations"
#ManagedByLabelOption: "managedByLabel"
// Kustomization holds the information needed to generate customized k8s api resources.
#Kustomization: {
#TypeMeta
// MetaData is a pointer to avoid marshalling empty struct
metadata?: null | #ObjectMeta @go(MetaData,*ObjectMeta)
// OpenAPI contains information about what kubernetes schema to use.
openapi?: {[string]: string} @go(OpenAPI,map[string]string)
// NamePrefix will prefix the names of all resources mentioned in the kustomization
// file including generated configmaps and secrets.
namePrefix?: string @go(NamePrefix)
// NameSuffix will suffix the names of all resources mentioned in the kustomization
// file including generated configmaps and secrets.
nameSuffix?: string @go(NameSuffix)
// Namespace to add to all objects.
namespace?: string @go(Namespace)
// CommonLabels to add to all objects and selectors.
commonLabels?: {[string]: string} @go(CommonLabels,map[string]string)
// Labels to add to all objects but not selectors.
labels?: [...#Label] @go(Labels,[]Label)
// CommonAnnotations to add to all objects.
commonAnnotations?: {[string]: string} @go(CommonAnnotations,map[string]string)
// Deprecated: Use the Patches field instead, which provides a superset of the functionality of PatchesStrategicMerge.
// PatchesStrategicMerge specifies the relative path to a file
// containing a strategic merge patch. Format documented at
// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-api-machinery/strategic-merge-patch.md
// URLs and globs are not supported.
patchesStrategicMerge?: [...#PatchStrategicMerge] @go(PatchesStrategicMerge,[]PatchStrategicMerge)
// Deprecated: Use the Patches field instead, which provides a superset of the functionality of JSONPatches.
// JSONPatches is a list of JSONPatch for applying JSON patch.
// Format documented at https://tools.ietf.org/html/rfc6902
// and http://jsonpatch.com
patchesJson6902?: [...#Patch] @go(PatchesJson6902,[]Patch)
// Patches is a list of patches, where each one can be either a
// Strategic Merge Patch or a JSON patch.
// Each patch can be applied to multiple target objects.
patches?: [...#Patch] @go(Patches,[]Patch)
// Images is a list of (image name, new name, new tag or digest)
// for changing image names, tags or digests. This can also be achieved with a
// patch, but this operator is simpler to specify.
images?: [...#Image] @go(Images,[]Image)
// Deprecated: Use the Images field instead.
imageTags?: [...#Image] @go(ImageTags,[]Image)
// Replacements is a list of replacements, which will copy nodes from a
// specified source to N specified targets.
replacements?: [...#ReplacementField] @go(Replacements,[]ReplacementField)
// Replicas is a list of {resourcename, count} that allows for simpler replica
// specification. This can also be done with a patch.
replicas?: [...#Replica] @go(Replicas,[]Replica)
// Deprecated: Vars will be removed in future release. Migrate to Replacements instead.
// Vars allow things modified by kustomize to be injected into a
// kubernetes object specification. A var is a name (e.g. FOO) associated
// with a field in a specific resource instance. The field must
// contain a value of type string/bool/int/float, and defaults to the name field
// of the instance. Any appearance of "$(FOO)" in the object
// spec will be replaced at kustomize build time, after the final
// value of the specified field has been determined.
vars?: [...#Var] @go(Vars,[]Var)
// SortOptions change the order that kustomize outputs resources.
sortOptions?: null | #SortOptions @go(SortOptions,*SortOptions)
// Resources specifies relative paths to files holding YAML representations
// of kubernetes API objects, or specifications of other kustomizations
// via relative paths, absolute paths, or URLs.
resources?: [...string] @go(Resources,[]string)
// Components specifies relative paths to specifications of other Components
// via relative paths, absolute paths, or URLs.
components?: [...string] @go(Components,[]string)
// Crds specifies relative paths to Custom Resource Definition files.
// This allows custom resources to be recognized as operands, making
// it possible to add them to the Resources list.
// CRDs themselves are not modified.
crds?: [...string] @go(Crds,[]string)
// Deprecated: Anything that would have been specified here should be specified in the Resources field instead.
bases?: [...string] @go(Bases,[]string)
// ConfigMapGenerator is a list of configmaps to generate from
// local data (one configMap per list item).
// The resulting resource is a normal operand, subject to
// name prefixing, patching, etc. By default, the name of
// the map will have a suffix hash generated from its contents.
configMapGenerator?: [...#ConfigMapArgs] @go(ConfigMapGenerator,[]ConfigMapArgs)
// SecretGenerator is a list of secrets to generate from
// local data (one secret per list item).
// The resulting resource is a normal operand, subject to
// name prefixing, patching, etc. By default, the name of
// the map will have a suffix hash generated from its contents.
secretGenerator?: [...#SecretArgs] @go(SecretGenerator,[]SecretArgs)
// HelmGlobals contains helm configuration that isn't chart specific.
helmGlobals?: null | #HelmGlobals @go(HelmGlobals,*HelmGlobals)
// HelmCharts is a list of helm chart configuration instances.
helmCharts?: [...#HelmChart] @go(HelmCharts,[]HelmChart)
// HelmChartInflationGenerator is a list of helm chart configurations.
// Deprecated. Auto-converted to HelmGlobals and HelmCharts.
helmChartInflationGenerator?: [...#HelmChartArgs] @go(HelmChartInflationGenerator,[]HelmChartArgs)
// GeneratorOptions modify behavior of all ConfigMap and Secret generators.
generatorOptions?: null | #GeneratorOptions @go(GeneratorOptions,*GeneratorOptions)
// Configurations is a list of transformer configuration files
configurations?: [...string] @go(Configurations,[]string)
// Generators is a list of files containing custom generators
generators?: [...string] @go(Generators,[]string)
// Transformers is a list of files containing transformers
transformers?: [...string] @go(Transformers,[]string)
// Validators is a list of files containing validators
validators?: [...string] @go(Validators,[]string)
// BuildMetadata is a list of strings used to toggle different build options
buildMetadata?: [...string] @go(BuildMetadata,[]string)
}
_#deprecatedWarningToRunEditFix: "Run 'kustomize edit fix' to update your Kustomization automatically."
_#deprecatedWarningToRunEditFixExperimential: "[EXPERIMENTAL] Run 'kustomize edit fix' to update your Kustomization automatically."
_#deprecatedBaseWarningMessage: "# Warning: 'bases' is deprecated. Please use 'resources' instead. Run 'kustomize edit fix' to update your Kustomization automatically."
_#deprecatedImageTagsWarningMessage: "# Warning: 'imageTags' is deprecated. Please use 'images' instead. Run 'kustomize edit fix' to update your Kustomization automatically."
_#deprecatedPatchesJson6902Message: "# Warning: 'patchesJson6902' is deprecated. Please use 'patches' instead. Run 'kustomize edit fix' to update your Kustomization automatically."
_#deprecatedPatchesStrategicMergeMessage: "# Warning: 'patchesStrategicMerge' is deprecated. Please use 'patches' instead. Run 'kustomize edit fix' to update your Kustomization automatically."
_#deprecatedVarsMessage: "# Warning: 'vars' is deprecated. Please use 'replacements' instead. [EXPERIMENTAL] Run 'kustomize edit fix' to update your Kustomization automatically."
_#deprecatedCommonLabelsWarningMessage: "# Warning: 'commonLabels' is deprecated. Please use 'labels' instead. Run 'kustomize edit fix' to update your Kustomization automatically."

View File

@@ -0,0 +1,37 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go sigs.k8s.io/kustomize/api/types
package types
// KvPairSources defines places to obtain key value pairs.
#KvPairSources: {
// LiteralSources is a list of literal
// pair sources. Each literal source should
// be a key and literal value, e.g. `key=value`
literals?: [...string] @go(LiteralSources,[]string)
// FileSources is a list of file "sources" to
// use in creating a list of key, value pairs.
// A source takes the form: [{key}=]{path}
// If the "key=" part is missing, the key is the
// path's basename. If they "key=" part is present,
// it becomes the key (replacing the basename).
// In either case, the value is the file contents.
// Specifying a directory will iterate each named
// file in the directory whose basename is a
// valid configmap key.
files?: [...string] @go(FileSources,[]string)
// EnvSources is a list of file paths.
// The contents of each file should be one
// key=value pair per line, e.g. a Docker
// or npm ".env" file or a ".ini" file
// (wikipedia.org/wiki/INI_file)
envs?: [...string] @go(EnvSources,[]string)
// Older, singular form of EnvSources.
// On edits (e.g. `kustomize fix`) this is merged into the plural form
// for consistency with LiteralSources and FileSources.
env?: string @go(EnvSource)
}

View File

@@ -0,0 +1,23 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go sigs.k8s.io/kustomize/api/types
package types
#Label: {
// Pairs contains the key-value pairs for labels to add
pairs?: {[string]: string} @go(Pairs,map[string]string)
// IncludeSelectors inidicates should transformer include the
// fieldSpecs for selectors. Custom fieldSpecs specified by
// FieldSpecs will be merged with builtin fieldSpecs if this
// is true.
includeSelectors?: bool @go(IncludeSelectors)
// IncludeTemplates inidicates should transformer include the
// spec/template/metadata fieldSpec. Custom fieldSpecs specified by
// FieldSpecs will be merged with spec/template/metadata fieldSpec if this
// is true. If IncludeSelectors is true, IncludeTemplates is not needed.
includeTemplates?: bool @go(IncludeTemplates)
fields?: [...#FieldSpec] @go(FieldSpecs,[]FieldSpec)
}

View File

@@ -0,0 +1,34 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go sigs.k8s.io/kustomize/api/types
package types
// Restrictions on what things can be referred to
// in a kustomization file.
//
//go:generate stringer -type=LoadRestrictions
#LoadRestrictions: int // #enumLoadRestrictions
#enumLoadRestrictions:
#LoadRestrictionsUnknown |
#LoadRestrictionsRootOnly |
#LoadRestrictionsNone
#values_LoadRestrictions: {
LoadRestrictionsUnknown: #LoadRestrictionsUnknown
LoadRestrictionsRootOnly: #LoadRestrictionsRootOnly
LoadRestrictionsNone: #LoadRestrictionsNone
}
#LoadRestrictionsUnknown: #LoadRestrictions & 0
// Files referenced by a kustomization file must be in
// or under the directory holding the kustomization
// file itself.
#LoadRestrictionsRootOnly: #LoadRestrictions & 1
// The kustomization file may specify absolute or
// relative paths to patch or resources files outside
// its own tree.
#LoadRestrictionsNone: #LoadRestrictions & 2

View File

@@ -0,0 +1,7 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go sigs.k8s.io/kustomize/api/types
package types
_#_LoadRestrictions_name: "LoadRestrictionsUnknownLoadRestrictionsRootOnlyLoadRestrictionsNone"

View File

@@ -0,0 +1,14 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go sigs.k8s.io/kustomize/api/types
package types
// ObjectMeta partially copies apimachinery/pkg/apis/meta/v1.ObjectMeta
// No need for a direct dependence; the fields are stable.
#ObjectMeta: {
name?: string @go(Name)
namespace?: string @go(Namespace)
labels?: {[string]: string} @go(Labels,map[string]string)
annotations?: {[string]: string} @go(Annotations,map[string]string)
}

View File

@@ -0,0 +1,11 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go sigs.k8s.io/kustomize/api/types
package types
// Pair is a key value pair.
#Pair: {
Key: string
Value: string
}

View File

@@ -0,0 +1,23 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go sigs.k8s.io/kustomize/api/types
package types
// Patch represent either a Strategic Merge Patch or a JSON patch
// and its targets.
// The content of the patch can either be from a file
// or from an inline string.
#Patch: {
// Path is a relative file path to the patch file.
path?: string @go(Path)
// Patch is the content of a patch.
patch?: string @go(Patch)
// Target points to the resources that the patch is applied to
target?: #Target | #Selector @go(Target,*Selector)
// Options is a list of options for the patch
options?: {[string]: bool} @go(Options,map[string]bool)
}

View File

@@ -0,0 +1,10 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go sigs.k8s.io/kustomize/api/types
package types
// PatchStrategicMerge represents a relative path to a
// stategic merge patch with the format
// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-api-machinery/strategic-merge-patch.md
#PatchStrategicMerge: string

View File

@@ -0,0 +1,27 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go sigs.k8s.io/kustomize/api/types
package types
#HelmConfig: {
Enabled: bool
Command: string
ApiVersions: [...string] @go(,[]string)
KubeVersion: string
}
// PluginConfig holds plugin configuration.
#PluginConfig: {
// PluginRestrictions distinguishes plugin restrictions.
PluginRestrictions: #PluginRestrictions
// BpLoadingOptions distinguishes builtin plugin behaviors.
BpLoadingOptions: #BuiltinPluginLoadingOptions
// FnpLoadingOptions sets the way function-based plugin behaviors.
FnpLoadingOptions: #FnPluginLoadingOptions
// HelmConfig contains metadata needed for allowing and running helm.
HelmConfig: #HelmConfig
}

View File

@@ -0,0 +1,87 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go sigs.k8s.io/kustomize/api/types
package types
// Some plugin classes
// - builtin: plugins defined in the kustomize repo.
// May be freely used and re-configured.
// - local: plugins that aren't builtin but are
// locally defined (presumably by the user), meaning
// the kustomization refers to them via a relative
// file path, not a URL.
// - remote: require a build-time download to obtain.
// Unadvised, unless one controls the
// serving site.
//
//go:generate stringer -type=PluginRestrictions
#PluginRestrictions: int // #enumPluginRestrictions
#enumPluginRestrictions:
#PluginRestrictionsUnknown |
#PluginRestrictionsBuiltinsOnly |
#PluginRestrictionsNone
#values_PluginRestrictions: {
PluginRestrictionsUnknown: #PluginRestrictionsUnknown
PluginRestrictionsBuiltinsOnly: #PluginRestrictionsBuiltinsOnly
PluginRestrictionsNone: #PluginRestrictionsNone
}
#PluginRestrictionsUnknown: #PluginRestrictions & 0
// Non-builtin plugins completely disabled.
#PluginRestrictionsBuiltinsOnly: #PluginRestrictions & 1
// No restrictions, do whatever you want.
#PluginRestrictionsNone: #PluginRestrictions & 2
// BuiltinPluginLoadingOptions distinguish ways in which builtin plugins are used.
//go:generate stringer -type=BuiltinPluginLoadingOptions
#BuiltinPluginLoadingOptions: int // #enumBuiltinPluginLoadingOptions
#enumBuiltinPluginLoadingOptions:
#BploUndefined |
#BploUseStaticallyLinked |
#BploLoadFromFileSys
#values_BuiltinPluginLoadingOptions: {
BploUndefined: #BploUndefined
BploUseStaticallyLinked: #BploUseStaticallyLinked
BploLoadFromFileSys: #BploLoadFromFileSys
}
#BploUndefined: #BuiltinPluginLoadingOptions & 0
// Desired in production use for performance.
#BploUseStaticallyLinked: #BuiltinPluginLoadingOptions & 1
// Desired in testing and development cycles where it's undesirable
// to generate static code.
#BploLoadFromFileSys: #BuiltinPluginLoadingOptions & 2
// FnPluginLoadingOptions set way functions-based plugins are restricted
#FnPluginLoadingOptions: {
// Allow to run executables
EnableExec: bool
// Allow to run starlark
EnableStar: bool
// Allow container access to network
Network: bool
NetworkName: string
// list of mounts
Mounts: [...string] @go(,[]string)
// list of env variables to pass to fn
Env: [...string] @go(,[]string)
// Run as uid and gid of the command executor
AsCurrentUser: bool
// Run in this working directory
WorkingDir: string
}

View File

@@ -0,0 +1,7 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go sigs.k8s.io/kustomize/api/types
package types
_#_PluginRestrictions_name: "PluginRestrictionsUnknownPluginRestrictionsBuiltinsOnlyPluginRestrictionsNone"

View File

@@ -0,0 +1,57 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go sigs.k8s.io/kustomize/api/types
package types
#DefaultReplacementFieldPath: "metadata.name"
// Replacement defines how to perform a substitution
// where it is from and where it is to.
#Replacement: {
// The source of the value.
source?: null | #SourceSelector @go(Source,*SourceSelector)
// The N fields to write the value to.
targets?: [...null | #TargetSelector] @go(Targets,[]*TargetSelector)
}
// SourceSelector is the source of the replacement transformer.
#SourceSelector: {
// Structured field path expected in the allowed object.
fieldPath?: string @go(FieldPath)
// Used to refine the interpretation of the field.
options?: null | #FieldOptions @go(Options,*FieldOptions)
}
// TargetSelector specifies fields in one or more objects.
#TargetSelector: {
// Include objects that match this.
select?: null | #Selector @go(Select,*Selector)
// From the allowed set, remove objects that match this.
reject?: [...null | #Selector] @go(Reject,[]*Selector)
// Structured field paths expected in each allowed object.
fieldPaths?: [...string] @go(FieldPaths,[]string)
// Used to refine the interpretation of the field.
options?: null | #FieldOptions @go(Options,*FieldOptions)
}
// FieldOptions refine the interpretation of FieldPaths.
#FieldOptions: {
// Used to split/join the field.
delimiter?: string @go(Delimiter)
// Which position in the split to consider.
index?: int @go(Index)
// TODO (#3492): Implement use of this option
// None, Base64, URL, Hex, etc
encoding?: string @go(Encoding)
// If field missing, add it.
create?: bool @go(Create)
}

View File

@@ -0,0 +1,10 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go sigs.k8s.io/kustomize/api/types
package types
#ReplacementField: {
#Replacement
path?: string @go(Path)
}

View File

@@ -0,0 +1,17 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go sigs.k8s.io/kustomize/api/types
package types
// Replica specifies a modification to a replica config.
// The number of replicas of a resource whose name matches will be set to count.
// This struct is used by the ReplicaCountTransform, and is meant to supplement
// the existing patch functionality with a simpler syntax for replica configuration.
#Replica: {
// The name of the resource to change the replica count
name?: string @go(Name)
// The number of replicas required.
count: int64 @go(Count)
}

View File

@@ -0,0 +1,19 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go sigs.k8s.io/kustomize/api/types
package types
// SecretArgs contains the metadata of how to generate a secret.
#SecretArgs: {
#GeneratorArgs
// Type of the secret.
//
// This is the same field as the secret type field in v1/Secret:
// It can be "Opaque" (default), or "kubernetes.io/tls".
//
// If type is "kubernetes.io/tls", then "literals" or "files" must have exactly two
// keys: "tls.key" and "tls.crt"
type?: string @go(Type)
}

View File

@@ -0,0 +1,20 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go sigs.k8s.io/kustomize/api/types
package types
// Selector specifies a set of resources.
// Any resource that matches intersection of all conditions
// is included in this set.
#Selector: {
// AnnotationSelector is a string that follows the label selection expression
// https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api
// It matches with the resource annotations.
annotationSelector?: string @go(AnnotationSelector)
// LabelSelector is a string that follows the label selection expression
// https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api
// It matches with the resource labels.
labelSelector?: string @go(LabelSelector)
}

View File

@@ -0,0 +1,36 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go sigs.k8s.io/kustomize/api/types
package types
// SortOptions defines the order that kustomize outputs resources.
#SortOptions: {
// Order selects the ordering strategy.
order?: #SortOrder @go(Order)
// LegacySortOptions tweaks the sorting for the "legacy" sort ordering
// strategy.
legacySortOptions?: null | #LegacySortOptions @go(LegacySortOptions,*LegacySortOptions)
}
// SortOrder defines different ordering strategies.
#SortOrder: string // #enumSortOrder
#enumSortOrder:
#LegacySortOrder |
#FIFOSortOrder
#LegacySortOrder: #SortOrder & "legacy"
#FIFOSortOrder: #SortOrder & "fifo"
// LegacySortOptions define various options for tweaking the "legacy" ordering
// strategy.
#LegacySortOptions: {
// OrderFirst selects the resource kinds to order first.
orderFirst: [...string] @go(OrderFirst,[]string)
// OrderLast selects the resource kinds to order last.
orderLast: [...string] @go(OrderLast,[]string)
}

View File

@@ -0,0 +1,12 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go sigs.k8s.io/kustomize/api/types
package types
// TypeMeta partially copies apimachinery/pkg/apis/meta/v1.TypeMeta
// No need for a direct dependence; the fields are stable.
#TypeMeta: {
kind?: string @go(Kind)
apiVersion?: string @go(APIVersion)
}

View File

@@ -0,0 +1,45 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go sigs.k8s.io/kustomize/api/types
package types
// Var represents a variable whose value will be sourced
// from a field in a Kubernetes object.
#Var: {
// Value of identifier name e.g. FOO used in container args, annotations
// Appears in pod template as $(FOO)
name: string @go(Name)
// ObjRef must refer to a Kubernetes resource under the
// purview of this kustomization. ObjRef should use the
// raw name of the object (the name specified in its YAML,
// before addition of a namePrefix and a nameSuffix).
objref: #Target @go(ObjRef)
// FieldRef refers to the field of the object referred to by
// ObjRef whose value will be extracted for use in
// replacing $(FOO).
// If unspecified, this defaults to fieldPath: $defaultFieldPath
fieldref?: #FieldSelector @go(FieldRef)
}
// Target refers to a kubernetes object by Group, Version, Kind and Name
// gvk.Gvk contains Group, Version and Kind
// APIVersion is added to keep the backward compatibility of using ObjectReference
// for Var.ObjRef
#Target: {
apiVersion?: string @go(APIVersion)
name: string @go(Name)
namespace?: string @go(Namespace)
}
// FieldSelector contains the fieldPath to an object field.
// This struct is added to keep the backward compatibility of using ObjectFieldSelector
// for Var.FieldRef
#FieldSelector: {
fieldPath?: string @go(FieldPath)
}
// byName is a sort interface which sorts Vars by name alphabetically
_#byName: [...#Var]

View File

@@ -1,6 +1,6 @@
package v1
#Deployment: {
apiVersion: "apps/v1"
kind: "Deployment"
apiVersion: "apps/v1"
kind: "Deployment"
}

View File

@@ -1,11 +1,11 @@
package v1
#CronJob: {
apiVersion: "batch/v1"
kind: "CronJob"
apiVersion: "batch/v1"
kind: "CronJob"
}
#Job: {
apiVersion: "batch/v1"
kind: "Job"
apiVersion: "batch/v1"
kind: "Job"
}

View File

@@ -1,26 +1,26 @@
package v1
#Namespace: {
apiVersion: "v1"
kind: "Namespace"
apiVersion: "v1"
kind: "Namespace"
}
#ConfigMap: {
apiVersion: "v1"
kind: "ConfigMap"
apiVersion: "v1"
kind: "ConfigMap"
}
#ServiceAccount: {
apiVersion: "v1"
kind: "ServiceAccount"
apiVersion: "v1"
kind: "ServiceAccount"
}
#Pod: {
apiVersion: "v1"
kind: "Pod"
apiVersion: "v1"
kind: "Pod"
}
#Service: {
apiVersion: "v1"
kind: "Service"
apiVersion: "v1"
kind: "Service"
}

View File

@@ -0,0 +1,15 @@
package types
#Patch: {
// Path is a relative file path to the patch file.
path?: string @go(Path)
// Patch is the content of a patch.
patch?: string @go(Patch)
// Target points to the resources that the patch is applied to
target?: #Target | #Selector @go(Target,*Selector)
// Options is a list of options for the patch
options?: {[string]: bool} @go(Options,map[string]bool)
}

View File

@@ -0,0 +1,7 @@
package types
#Target: {
group?: string @go(Group)
version?: string @go(Version)
kind?: string @go(Kind)
}

View File

@@ -0,0 +1,61 @@
package holos
#PlatformCerts: {
// Globally scoped platform services are defined here.
login: #PlatformCert & {
_name: "login"
_wildcard: true
_description: "Cert for Zitadel oidc identity provider for iam services"
}
// Cluster scoped services are defined here.
for cluster in #Platform.clusters {
"\(cluster.name)-httpbin": #ClusterCert & {
_name: "httpbin"
_cluster: cluster.name
_description: "Test endpoint to verify the service mesh ingress gateway"
}
}
}
// #PlatformCert provisions a cert in the provisioner cluster.
// Workload clusters use ExternalSecret resources to fetch the Secret tls key and cert from the provisioner cluster.
#PlatformCert: #Certificate & {
_name: string
_wildcard: true | *false
metadata: name: string | *_name
metadata: namespace: string | *"istio-ingress"
spec: {
commonName: string | *"\(_name).\(#Platform.org.domain)"
if _wildcard {
dnsNames: [commonName, "*.\(commonName)"]
}
if !_wildcard {
dnsNames: [commonName]
}
secretName: metadata.name
issuerRef: kind: "ClusterIssuer"
issuerRef: name: string | *"letsencrypt"
}
}
// #ClusterCert provisions a cluster specific certificate.
#ClusterCert: #Certificate & {
_name: string
_cluster: string
_wildcard: true | *false
metadata: name: string | *"\(_cluster)-\(_name)"
metadata: namespace: string | *"istio-ingress"
spec: {
commonName: string | *"\(_name).\(_cluster).\(#Platform.org.domain)"
if _wildcard {
dnsNames: [commonName, "*.\(commonName)"]
}
if !_wildcard {
dnsNames: [commonName]
}
secretName: metadata.name
issuerRef: kind: "ClusterIssuer"
issuerRef: name: string | *"letsencrypt"
}
}

View File

@@ -0,0 +1,10 @@
package holos
// Components under this directory are part of this collection
#InputKeys: project: "iam"
// Shared dependencies for all components in this collection.
#DependsOn: _Namespaces
// Common Dependencies
_Namespaces: Namespaces: name: "\(#StageName)-secrets-namespaces"

View File

@@ -0,0 +1,17 @@
# IAM
The IAM service provides identity and access management for a holos managed platform. Zitadel is the identity provider which integrates tightly with:
1. AuthorizationPolicy at the level of the service mesh.
2. Application level oidc login (ArgoCD, Grafana, etc...)
3. Cloud provider IAM via oidc.
## Preflight
The zitadel master key needs to have a data key named `masterkey` with a Secret name of `zitadel-masterkey`.
```bash
holos create secret zitadel-masterkey --namespace prod-iam-zitadel --append-hash=false --data-stdin <<EOF
{"masterkey":"$(tr -dc A-Za-z0-9 </dev/urandom | head -c 32)"}
EOF
```

View File

@@ -0,0 +1,20 @@
package holos
#InputKeys: component: "crdb"
#HelmChart & {
namespace: #TargetNamespace
chart: {
name: "cockroachdb"
version: "11.2.3"
repository: {
name: "cockroachdb"
url: "https://charts.cockroachdb.com/"
}
}
values: #Values
apiObjects: {
ExternalSecret: node: #ExternalSecret & {_name: "cockroachdb-node"}
ExternalSecret: root: #ExternalSecret & {_name: "cockroachdb-root"}
}
}

View File

@@ -0,0 +1,606 @@
package holos
#Values: {
// Generated file, DO NOT EDIT. Source: build/templates/values.yaml
// Overrides the chart name against the label "app.kubernetes.io/name: " placed on every resource this chart creates.
nameOverride: ""
// Override the resource names created by this chart which originally is generated using release and chart name.
fullnameOverride: string | *""
image: {
repository: string | *"cockroachdb/cockroach"
tag: "v23.1.13"
pullPolicy: "IfNotPresent"
credentials: {}
}
// registry: docker.io
// username: john_doe
// password: changeme
// Additional labels to apply to all Kubernetes resources created by this chart.
labels: {}
// app.kubernetes.io/part-of: my-app
// Cluster's default DNS domain.
// You should overwrite it if you're using a different one,
// otherwise CockroachDB nodes discovery won't work.
clusterDomain: "cluster.local"
conf: {
// An ordered list of CockroachDB node attributes.
// Attributes are arbitrary strings specifying machine capabilities.
// Machine capabilities might include specialized hardware or number of cores
// (e.g. "gpu", "x16c").
attrs: []
// - x16c
// - gpu
// Total size in bytes for caches, shared evenly if there are multiple
// storage devices. Size suffixes are supported (e.g. `1GB` and `1GiB`).
// A percentage of physical memory can also be specified (e.g. `.25`).
cache: "25%"
// Sets a name to verify the identity of a cluster.
// The value must match between all nodes specified via `conf.join`.
// This can be used as an additional verification when either the node or
// cluster, or both, have not yet been initialized and do not yet know their
// cluster ID.
// To introduce a cluster name into an already-initialized cluster, pair this
// option with `conf.disable-cluster-name-verification: yes`.
"cluster-name": ""
// Tell the server to ignore `conf.cluster-name` mismatches.
// This is meant for use when opting an existing cluster into starting to use
// cluster name verification, or when changing the cluster name.
// The cluster should be restarted once with `conf.cluster-name` and
// `conf.disable-cluster-name-verification: yes` combined, and once all nodes
// have been updated to know the new cluster name, the cluster can be restarted
// again with `conf.disable-cluster-name-verification: no`.
// This option has no effect if `conf.cluster-name` is not specified.
"disable-cluster-name-verification": false
// The addresses for connecting a CockroachDB nodes to an existing cluster.
// If you are deploying a second CockroachDB instance that should join a first
// one, use the below list to join to the existing instance.
// Each item in the array should be a FQDN (and port if needed) resolvable by
// new Pods.
join: []
// New logging configuration.
log: {
enabled: false
// https://www.cockroachlabs.com/docs/v21.1/configure-logs
config: {}
}
// file-defaults:
// dir: /custom/dir/path/
// fluent-defaults:
// format: json-fluent
// sinks:
// stderr:
// channels: [DEV]
// Logs at or above this threshold to STDERR. Ignored when "log" is enabled
logtostderr: "INFO"
// Maximum storage capacity available to store temporary disk-based data for
// SQL queries that exceed the memory budget (e.g. join, sorts, etc are
// sometimes able to spill intermediate results to disk).
// Accepts numbers interpreted as bytes, size suffixes (e.g. `32GB` and
// `32GiB`) or a percentage of disk size (e.g. `10%`).
// The location of the temporary files is within the first store dir.
// If expressed as a percentage, `max-disk-temp-storage` is interpreted
// relative to the size of the storage device on which the first store is
// placed. The temp space usage is never counted towards any store usage
// (although it does share the device with the first store) so, when
// configuring this, make sure that the size of this temp storage plus the size
// of the first store don't exceed the capacity of the storage device.
// If the first store is an in-memory one (i.e. `type=mem`), then this
// temporary "disk" data is also kept in-memory.
// A percentage value is interpreted as a percentage of the available internal
// memory.
// max-disk-temp-storage: 0GB
// Maximum allowed clock offset for the cluster. If observed clock offsets
// exceed this limit, servers will crash to minimize the likelihood of
// reading inconsistent data. Increasing this value will increase the time
// to recovery of failures as well as the frequency of uncertainty-based
// read restarts.
// Note, that this value must be the same on all nodes in the cluster.
// In order to change it, all nodes in the cluster must be stopped
// simultaneously and restarted with the new value.
// max-offset: 500ms
// Maximum memory capacity available to store temporary data for SQL clients,
// including prepared queries and intermediate data rows during query
// execution. Accepts numbers interpreted as bytes, size suffixes
// (e.g. `1GB` and `1GiB`) or a percentage of physical memory (e.g. `.25`).
"max-sql-memory": "25%"
// An ordered, comma-separated list of key-value pairs that describe the
// topography of the machine. Topography might include country, datacenter
// or rack designations. Data is automatically replicated to maximize
// diversities of each tier. The order of tiers is used to determine
// the priority of the diversity, so the more inclusive localities like
// country should come before less inclusive localities like datacenter.
// The tiers and order must be the same on all nodes. Including more tiers
// is better than including fewer. For example:
// locality: country=us,region=us-west,datacenter=us-west-1b,rack=12
// locality: country=ca,region=ca-east,datacenter=ca-east-2,rack=4
// locality: planet=earth,province=manitoba,colo=secondary,power=3
locality: ""
// Run CockroachDB instances in standalone mode with replication disabled
// (replication factor = 1).
// Enabling this option makes the following values to be ignored:
// - `conf.cluster-name`
// - `conf.disable-cluster-name-verification`
// - `conf.join`
//
// WARNING: Enabling this option makes each deployed Pod as a STANDALONE
// CockroachDB instance, so the StatefulSet does NOT FORM A CLUSTER.
// Don't use this option for production deployments unless you clearly
// understand what you're doing.
// Usually, this option is intended to be used in conjunction with
// `statefulset.replicas: 1` for temporary one-time deployments (like
// running E2E tests, for example).
"single-node": false
// If non-empty, create a SQL audit log in the specified directory.
"sql-audit-dir": ""
// CockroachDB's port to listen to inter-communications and client connections.
port: 26257
// CockroachDB's port to listen to HTTP requests.
"http-port": 8080
// CockroachDB's data mount path.
path: "cockroach-data"
// CockroachDB's storage configuration https://www.cockroachlabs.com/docs/v21.1/cockroach-start.html#storage
// Uses --store flag
store: {
enabled: false
// Should be empty or 'mem'
type: null
// Required for type=mem. If type and size is empty - storage.persistentVolume.size is used
size: null
// Arbitrary strings, separated by colons, specifying disk type or capability
attrs: null
}
}
statefulset: {
replicas: 3
updateStrategy: type: "RollingUpdate"
podManagementPolicy: "Parallel"
budget: maxUnavailable: 1
// List of additional command-line arguments you want to pass to the
// `cockroach start` command.
args: []
// - --disable-cluster-name-verification
// List of extra environment variables to pass into container
env: []
// - name: COCKROACH_ENGINE_MAX_SYNC_DURATION
// value: "24h"
// List of Secrets names in the same Namespace as the CockroachDB cluster,
// which shall be mounted into `/etc/cockroach/secrets/` for every cluster
// member.
secretMounts: []
// Additional labels to apply to this StatefulSet and all its Pods.
labels: {
"app.kubernetes.io/component": "cockroachdb"
}
// Additional annotations to apply to the Pods of this StatefulSet.
annotations: {}
// Affinity rules for scheduling Pods of this StatefulSet on Nodes.
// https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#node-affinity
nodeAffinity: {}
// Inter-Pod Affinity rules for scheduling Pods of this StatefulSet.
// https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#inter-pod-affinity-and-anti-affinity
podAffinity: {}
// Anti-affinity rules for scheduling Pods of this StatefulSet.
// https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#inter-pod-affinity-and-anti-affinity
// You may either toggle options below for default anti-affinity rules,
// or specify the whole set of anti-affinity rules instead of them.
podAntiAffinity: {
// The topologyKey to be used.
// Can be used to spread across different nodes, AZs, regions etc.
topologyKey: "kubernetes.io/hostname"
// Type of anti-affinity rules: either `soft`, `hard` or empty value (which
// disables anti-affinity rules).
type: "soft"
// Weight for `soft` anti-affinity rules.
// Does not apply for other anti-affinity types.
weight: 100
}
// Node selection constraints for scheduling Pods of this StatefulSet.
// https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector
nodeSelector: {}
// PriorityClassName given to Pods of this StatefulSet
// https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#priorityclass
priorityClassName: ""
// Taints to be tolerated by Pods of this StatefulSet.
// https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/
tolerations: []
// https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/
topologySpreadConstraints: {
maxSkew: 1
topologyKey: "topology.kubernetes.io/zone"
whenUnsatisfiable: "ScheduleAnyway"
}
// Uncomment the following resources definitions or pass them from
// command line to control the CPU and memory resources allocated
// by Pods of this StatefulSet.
resources: {}
// limits:
// cpu: 100m
// memory: 512Mi
// requests:
// cpu: 100m
// memory: 512Mi
// Custom Liveness probe
// https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-a-liveness-http-request
customLivenessProbe: {}
// httpGet:
// path: /health
// port: http
// scheme: HTTPS
// initialDelaySeconds: 30
// periodSeconds: 5
// Custom Rediness probe
// https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-readiness-probes
customReadinessProbe: {}
// httpGet:
// path: /health
// port: http
// scheme: HTTPS
// initialDelaySeconds: 30
// periodSeconds: 5
securityContext: {
enabled: true
}
serviceAccount: {
// Specifies whether this ServiceAccount should be created.
create: true
// The name of this ServiceAccount to use.
// If not set and `create` is `true`, then service account is auto-generated.
// If not set and `create` is `false`, then it uses default service account.
name: ""
// Additional serviceAccount annotations (e.g. for attaching AWS IAM roles to pods)
annotations: {}
}
}
service: {
ports: {
// You can set a different external and internal gRPC ports and their name.
grpc: {
external: {
port: 26257
name: "grpc"
}
// If the port number is different than `external.port`, then it will be
// named as `internal.name` in Service.
internal: {
port: 26257
// If using Istio set it to `cockroach`.
name: "grpc-internal"
}
}
http: {
port: 8080
name: "http"
}
}
// This Service is meant to be used by clients of the database.
// It exposes a ClusterIP that will automatically load balance connections
// to the different database Pods.
public: {
type: "ClusterIP"
// Additional labels to apply to this Service.
labels: {
"app.kubernetes.io/component": "cockroachdb"
}
// Additional annotations to apply to this Service.
annotations: {}
}
// This service only exists to create DNS entries for each pod in
// the StatefulSet such that they can resolve each other's IP addresses.
// It does not create a load-balanced ClusterIP and should not be used directly
// by clients in most circumstances.
discovery: {
// Additional labels to apply to this Service.
labels: {
"app.kubernetes.io/component": "cockroachdb"
}
// Additional annotations to apply to this Service.
annotations: {}
}
}
// CockroachDB's ingress for web ui.
ingress: {
enabled: false
labels: {}
annotations: {}
// kubernetes.io/ingress.class: nginx
// cert-manager.io/cluster-issuer: letsencrypt
paths: ["/"]
hosts: []
// - cockroachlabs.com
tls: []
}
// - hosts: [cockroachlabs.com]
// secretName: cockroachlabs-tls
prometheus: {
enabled: true
}
securityContext: enabled: true
// CockroachDB's Prometheus operator ServiceMonitor support
serviceMonitor: {
enabled: false
labels: {}
annotations: {}
interval: "10s"
// scrapeTimeout: 10s
// Limits the ServiceMonitor to the current namespace if set to `true`.
namespaced: false
// tlsConfig: TLS configuration to use when scraping the endpoint.
// Of type: https://github.com/coreos/prometheus-operator/blob/main/Documentation/api.md#tlsconfig
tlsConfig: {}
}
// CockroachDB's data persistence.
// If neither `persistentVolume` nor `hostPath` is used, then data will be
// persisted in ad-hoc `emptyDir`.
storage: {
// Absolute path on host to store CockroachDB's data.
// If not specified, then `emptyDir` will be used instead.
// If specified, but `persistentVolume.enabled` is `true`, then has no effect.
hostPath: ""
// If `enabled` is `true` then a PersistentVolumeClaim will be created and
// used to store CockroachDB's data, otherwise `hostPath` is used.
persistentVolume: {
enabled: true
size: string | *"100Gi"
// If defined, then `storageClassName: <storageClass>`.
// If set to "-", then `storageClassName: ""`, which disables dynamic
// provisioning.
// If undefined or empty (default), then no `storageClassName` spec is set,
// so the default provisioner will be chosen (gp2 on AWS, standard on
// GKE, AWS & OpenStack).
storageClass: ""
// Additional labels to apply to the created PersistentVolumeClaims.
labels: {}
// Additional annotations to apply to the created PersistentVolumeClaims.
annotations: {}
}
}
// Kubernetes Job which initializes multi-node CockroachDB cluster.
// It's not created if `statefulset.replicas` is `1`.
init: {
// Additional labels to apply to this Job and its Pod.
labels: {
"app.kubernetes.io/component": "init"
}
// Additional annotations to apply to this Job.
jobAnnotations: {}
// Additional annotations to apply to the Pod of this Job.
annotations: {}
// Affinity rules for scheduling the Pod of this Job.
// https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#node-affinity
affinity: {}
// Node selection constraints for scheduling the Pod of this Job.
// https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector
nodeSelector: {}
// Taints to be tolerated by the Pod of this Job.
// https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/
tolerations: []
// The init Pod runs at cluster creation to initialize CockroachDB. It finishes
// quickly and doesn't continue to consume resources in the Kubernetes
// cluster. Normally, you should leave this section commented out, but if your
// Kubernetes cluster uses Resource Quotas and requires all pods to specify
// resource requests or limits, you can set those here.
resources: {}
// requests:
// cpu: "10m"
// memory: "128Mi"
// limits:
// cpu: "10m"
// memory: "128Mi"
securityContext: {
enabled: true
}
provisioning: {
enabled: false
// https://www.cockroachlabs.com/docs/stable/cluster-settings.html
clusterSettings: null
// cluster.organization: "'FooCorp - Local Testing'"
// enterprise.license: "'xxxxx'"
users: []
// - name:
// password:
// # https://www.cockroachlabs.com/docs/stable/create-user.html#parameters
// options: [LOGIN]
databases: []
}
}
// - name:
// # https://www.cockroachlabs.com/docs/stable/create-database.html#parameters
// options: [encoding='utf-8']
// owners: []
// # https://www.cockroachlabs.com/docs/stable/grant.html#parameters
// owners_with_grant_option: []
// # Backup schedules are not idemponent for now and will fail on next run
// # https://github.com/cockroachdb/cockroach/issues/57892
// backup:
// into: s3://
// # Enterprise-only option (revision_history)
// # https://www.cockroachlabs.com/docs/stable/create-schedule-for-backup.html#backup-options
// options: [revision_history]
// recurring: '@always'
// # Enterprise-only feature. Remove this value to use `FULL BACKUP ALWAYS`
// fullBackup: '@daily'
// schedule:
// # https://www.cockroachlabs.com/docs/stable/create-schedule-for-backup.html#schedule-options
// options: [first_run = 'now']
// Whether to run securely using TLS certificates.
tls: {
enabled: true
copyCerts: image: "busybox"
certs: {
// Bring your own certs scenario. If provided, tls.init section will be ignored.
provided: true | *false
// Secret name for the client root cert.
clientRootSecret: "cockroachdb-root"
// Secret name for node cert.
nodeSecret: "cockroachdb-node"
// Secret name for CA cert
caSecret: "cockroach-ca"
// Enable if the secret is a dedicated TLS.
// TLS secrets are created by cert-mananger, for example.
tlsSecret: true | *false
// Enable if the you want cockroach db to create its own certificates
selfSigner: {
// If set, the cockroach db will generate its own certificates
enabled: false | *true
// Run selfSigner as non-root
securityContext: {
enabled: true
}
// If set, the user should provide the CA certificate to sign other certificates.
caProvided: false
// It holds the name of the secret with caCerts. If caProvided is set, this can not be empty.
caSecret: ""
// Minimum Certificate duration for all the certificates, all certs duration will be validated against this.
minimumCertDuration: "624h"
// Duration of CA certificates in hour
caCertDuration: "43800h"
// Expiry window of CA certificates means a window before actual expiry in which CA certs should be rotated.
caCertExpiryWindow: "648h"
// Duration of Client certificates in hour
clientCertDuration: "672h"
// Expiry window of client certificates means a window before actual expiry in which client certs should be rotated.
clientCertExpiryWindow: "48h"
// Duration of node certificates in hour
nodeCertDuration: "8760h"
// Expiry window of node certificates means a window before actual expiry in which node certs should be rotated.
nodeCertExpiryWindow: "168h"
// If set, the cockroachdb cert selfSigner will rotate the certificates before expiry.
rotateCerts: true
// Wait time for each cockroachdb replica to become ready once it comes in running state. Only considered when rotateCerts is set to true
readinessWait: "30s"
// Wait time for each cockroachdb replica to get to running state. Only considered when rotateCerts is set to true
podUpdateTimeout: "2m"
// ServiceAccount annotations for selfSigner jobs (e.g. for attaching AWS IAM roles to pods)
svcAccountAnnotations: {}
}
// Use cert-manager to issue certificates for mTLS.
certManager: true | *false
// Specify an Issuer or a ClusterIssuer to use, when issuing
// node and client certificates. The values correspond to the
// issuerRef specified in the certificate.
certManagerIssuer: {
group: "cert-manager.io"
kind: "Issuer"
name: string | *"cockroachdb"
// Make it false when you are providing your own CA issuer
isSelfSignedIssuer: true
// Duration of Client certificates in hours
clientCertDuration: "672h"
// Expiry window of client certificates means a window before actual expiry in which client certs should be rotated.
clientCertExpiryWindow: "48h"
// Duration of node certificates in hours
nodeCertDuration: "8760h"
// Expiry window of node certificates means a window before actual expiry in which node certs should be rotated.
nodeCertExpiryWindow: "168h"
}
}
selfSigner: {
// Additional annotations to apply to the Pod of this Job.
annotations: {}
// Affinity rules for scheduling the Pod of this Job.
// https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#node-affinity
affinity: {}
// Node selection constraints for scheduling the Pod of this Job.
// https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector
nodeSelector: {}
// Taints to be tolerated by the Pod of this Job.
// https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/
tolerations: []
// Image Placeholder for the selfSigner utility. This will be changed once the CI workflows for the image is in place.
image: {
repository: "cockroachlabs-helm-charts/cockroach-self-signer-cert"
tag: "1.5"
pullPolicy: "IfNotPresent"
credentials: {}
registry: "gcr.io"
}
}
}
// username: john_doe
// password: changeme
networkPolicy: {
enabled: false
ingress: {
// List of sources which should be able to access the CockroachDB Pods via
// gRPC port. Items in this list are combined using a logical OR operation.
// Rules for allowing inter-communication are applied automatically.
// If empty, then connections from any Pod is allowed.
grpc: []
// - podSelector:
// matchLabels:
// app.kubernetes.io/name: my-app-django
// app.kubernetes.io/instance: my-app
// List of sources which should be able to access the CockroachDB Pods via
// HTTP port. Items in this list are combined using a logical OR operation.
// If empty, then connections from any Pod is allowed.
http: []
}
}
// - namespaceSelector:
// matchLabels:
// project: my-project
// To put the admin interface behind Identity Aware Proxy (IAP) on Google Cloud Platform
// make sure to set ingress.paths: ['/*']
iap: {
enabled: false
}
}

View File

@@ -0,0 +1,23 @@
package holos
#Values: {
image: repository: "quay.io/holos/cockroachdb/cockroach"
fullnameOverride: #ComponentName
tls: {
enabled: true
certs: {
// https://github.com/cockroachdb/helm-charts/blob/3dcf96726ebcfe3784afb526ddcf4095a1684aea/README.md?plain=1#L204-L215
selfSigner: enabled: false
certManager: false
provided: true
tlsSecret: true
}
}
storage: persistentVolume: {
enabled: true
size: "1Gi"
}
}

View File

@@ -0,0 +1,10 @@
package holos
#TargetNamespace: #InstancePrefix + "-zitadel"
#DB: {
Host: "crdb-public"
}
// The canonical login domain for the entire platform. Zitadel will be active on a singlec cluster at a time, but always accessible from this hostname.
#ExternalDomain: "login.\(#Platform.org.domain)"

View File

@@ -0,0 +1,251 @@
package holos
#Values: {
// Default values for zitadel.
zitadel: {
// The ZITADEL config under configmapConfig is written to a Kubernetes ConfigMap
// See all defaults here:
// https://github.com/zitadel/zitadel/blob/main/cmd/defaults.yaml
configmapConfig: {
ExternalSecure: true
Machine: Identification: {
Hostname: Enabled: true
Webhook: Enabled: false
}
}
// The ZITADEL config under secretConfig is written to a Kubernetes Secret
// See all defaults here:
// https://github.com/zitadel/zitadel/blob/main/cmd/defaults.yaml
secretConfig: null
// Annotations set on secretConfig secret
secretConfigAnnotations: {
"helm.sh/hook": "pre-install,pre-upgrade"
"helm.sh/hook-delete-policy": "before-hook-creation"
"helm.sh/hook-weight": "0"
}
// Reference the name of a secret that contains ZITADEL configuration.
configSecretName: null
// The key under which the ZITADEL configuration is located in the secret.
configSecretKey: "config-yaml"
// ZITADEL uses the masterkey for symmetric encryption.
// You can generate it for example with tr -dc A-Za-z0-9 </dev/urandom | head -c 32
masterkey: ""
// Reference the name of the secret that contains the masterkey. The key should be named "masterkey".
// Note: Either zitadel.masterkey or zitadel.masterkeySecretName must be set
masterkeySecretName: string | *""
// Annotations set on masterkey secret
masterkeyAnnotations: {
"helm.sh/hook": "pre-install,pre-upgrade"
"helm.sh/hook-delete-policy": "before-hook-creation"
"helm.sh/hook-weight": "0"
}
// The CA Certificate needed for establishing secure database connections
dbSslCaCrt: ""
// The Secret containing the CA certificate at key ca.crt needed for establishing secure database connections
dbSslCaCrtSecret: string | *""
// The db admins secret containing the client certificate and key at tls.crt and tls.key needed for establishing secure database connections
dbSslAdminCrtSecret: string | *""
// The db users secret containing the client certificate and key at tls.crt and tls.key needed for establishing secure database connections
dbSslUserCrtSecret: string | *""
// Generate a self-signed certificate using an init container
// This will also mount the generated files to /etc/tls/ so that you can reference them in the pod.
// E.G. KeyPath: /etc/tls/tls.key CertPath: /etc/tls/tls.crt
// By default, the SAN DNS names include, localhost, the POD IP address and the POD name. You may include one more by using additionalDnsName like "my.zitadel.fqdn".
selfSignedCert: {
enabled: false
additionalDnsName: null
}
}
replicaCount: 3
image: {
repository: "ghcr.io/zitadel/zitadel"
pullPolicy: "IfNotPresent"
// Overrides the image tag whose default is the chart appVersion.
tag: ""
}
chownImage: {
repository: "alpine"
pullPolicy: "IfNotPresent"
tag: "3.19"
}
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
// Annotations to add to the deployment
annotations: {}
// Annotations to add to the configMap
configMap: {
annotations: {
"helm.sh/hook": "pre-install,pre-upgrade"
"helm.sh/hook-delete-policy": "before-hook-creation"
"helm.sh/hook-weight": "0"
}
}
serviceAccount: {
// Specifies whether a service account should be created
create: true
// Annotations to add to the service account
annotations: {
"helm.sh/hook": "pre-install,pre-upgrade"
"helm.sh/hook-delete-policy": "before-hook-creation"
"helm.sh/hook-weight": "0"
}
// The name of the service account to use.
// If not set and create is true, a name is generated using the fullname template
name: ""
}
podAnnotations: {}
podAdditionalLabels: {}
podSecurityContext: {
runAsNonRoot: true
runAsUser: 1000
}
securityContext: {}
// Additional environment variables
env: []
// - name: ZITADEL_DATABASE_POSTGRES_HOST
// valueFrom:
// secretKeyRef:
// name: postgres-pguser-postgres
// key: host
service: {
type: "ClusterIP"
// If service type is "ClusterIP", this can optionally be set to a fixed IP address.
clusterIP: ""
port: 8080
protocol: "http2"
annotations: {}
scheme: "HTTP"
}
ingress: {
enabled: false
className: ""
annotations: {}
hosts: [{
host: "localhost"
paths: [{
path: "/"
pathType: "Prefix"
}]
}]
tls: []
}
resources: {}
nodeSelector: {}
tolerations: []
affinity: {}
topologySpreadConstraints: []
initJob: {
// Once ZITADEL is installed, the initJob can be disabled.
enabled: true
annotations: {
"helm.sh/hook": "pre-install,pre-upgrade"
"helm.sh/hook-delete-policy": "before-hook-creation"
"helm.sh/hook-weight": "1"
}
resources: {}
backoffLimit: 5
activeDeadlineSeconds: 300
extraContainers: []
podAnnotations: {}
// Available init commands :
// "": initialize ZITADEL instance (without skip anything)
// database: initialize only the database
// grant: set ALL grant to user
// user: initialize only the database user
// zitadel: initialize ZITADEL internals (skip "create user" and "create database")
command: ""
}
setupJob: {
annotations: {
"helm.sh/hook": "pre-install,pre-upgrade"
"helm.sh/hook-delete-policy": "before-hook-creation"
"helm.sh/hook-weight": "2"
}
resources: {}
activeDeadlineSeconds: 300
extraContainers: []
podAnnotations: {}
additionalArgs: ["--init-projections=true"]
machinekeyWriter: {
image: {
repository: "bitnami/kubectl"
tag: ""
}
resources: {}
}
}
readinessProbe: {
enabled: true
initialDelaySeconds: 0
periodSeconds: 5
failureThreshold: 3
}
livenessProbe: {
enabled: true
initialDelaySeconds: 0
periodSeconds: 5
failureThreshold: 3
}
startupProbe: {
enabled: true
periodSeconds: 1
failureThreshold: 30
}
metrics: {
enabled: false
serviceMonitor: {
// If true, the chart creates a ServiceMonitor that is compatible with Prometheus Operator
// https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/api.md#monitoring.coreos.com/v1.ServiceMonitor.
// The Prometheus community Helm chart installs this operator
// https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack#kube-prometheus-stack
enabled: false
honorLabels: false
honorTimestamps: true
}
}
pdb: {
enabled: false
// these values are used for the PDB and are mutally exclusive
minAvailable: 1
// maxUnavailable: 1
annotations: {}
}
}

View File

@@ -0,0 +1,34 @@
package holos
#Values: {
// https://raw.githubusercontent.com/zitadel/zitadel-charts/main/examples/4-cockroach-secure/zitadel-values.yaml
zitadel: {
masterkeySecretName: "zitadel-masterkey"
// https://github.com/zitadel/zitadel-charts/blob/zitadel-7.4.0/charts/zitadel/templates/configmap.yaml#L13
configmapConfig: {
// NOTE: You can change the ExternalDomain, ExternalPort and ExternalSecure
// configuration options at any time. However, for ZITADEL to be able to
// pick up the changes, you need to rerun ZITADELs setup phase. Do so with
// kubectl delete job zitadel-setup, then re-apply the new config.
//
// https://zitadel.com/docs/self-hosting/manage/custom-domain
ExternalDomain: #ExternalDomain
ExternalPort: 443
ExternalSecure: true
TLS: Enabled: false
Database: Cockroach: {
Host: #DB.Host
User: SSL: Mode: "verify-full"
Admin: SSL: Mode: "verify-full"
}
}
// Managed by crdb component
dbSslCaCrtSecret: "cockroach-ca"
dbSslAdminCrtSecret: "cockroachdb-root"
// Managed by this component
dbSslUserCrtSecret: "cockroachdb-zitadel"
}
}

View File

@@ -0,0 +1,55 @@
package holos
import "encoding/yaml"
let Name = "zitadel"
#InputKeys: component: Name
// Upstream helm chart doesn't specify the namespace field for all resources.
#Kustomization: spec: targetNamespace: #TargetNamespace
#HelmChart & {
namespace: #TargetNamespace
chart: {
name: Name
version: "7.9.0"
repository: {
name: Name
url: "https://charts.zitadel.com"
}
}
values: #Values
apiObjects: {
ExternalSecret: masterkey: #ExternalSecret & {
_name: "zitadel-masterkey"
}
ExternalSecret: zitadel: #ExternalSecret & {
_name: "cockroachdb-zitadel"
}
VirtualService: zitadel: #VirtualService & {
metadata: name: Name
metadata: namespace: #TargetNamespace
spec: hosts: ["login.\(#Platform.org.domain)"]
spec: gateways: ["istio-ingress/default"]
spec: http: [{route: [{destination: host: Name}]}]
}
}
}
// TODO: Generalize this common pattern of injecting the istio sidecar into a Deployment
let Patch = [{op: "add", path: "/spec/template/metadata/labels/sidecar.istio.io~1inject", value: "true"}]
#Kustomize: {
patches: [
{
target: {
group: "apps"
version: "v1"
kind: "Deployment"
name: Name
}
patch: yaml.Marshal(Patch)
},
]
}

View File

@@ -0,0 +1,10 @@
package holos
// Components under this directory are part of this collection
#InputKeys: project: "iam"
// Shared dependencies for all components in this collection.
#DependsOn: _Namespaces
// Common Dependencies
_Namespaces: Namespaces: name: "\(#StageName)-secrets-namespaces"

View File

@@ -0,0 +1,108 @@
package holos
// Manage an Issuer for cockroachdb for zitadel.
// For the iam login service, zitadel connects to cockroach db using tls certs for authz.
// Upstream: "The recommended approach is to use cert-manager for certificate management. For details, refer to Deploy cert-manager for mTLS."
// Refer to https://www.cockroachlabs.com/docs/stable/secure-cockroachdb-kubernetes#deploy-cert-manager-for-mtls
#InputKeys: component: "crdb"
#KubernetesObjects & {
apiObjects: {
Issuer: {
// https://github.com/cockroachdb/helm-charts/blob/3dcf96726ebcfe3784afb526ddcf4095a1684aea/README.md?plain=1#L196-L201
crdb: #Issuer & {
_description: "Issues the self signed root ca cert for cockroach db"
metadata: name: #ComponentName
metadata: namespace: #TargetNamespace
spec: selfSigned: {}
}
"crdb-ca-issuer": #Issuer & {
_description: "Issues mtls certs for cockroach db"
metadata: name: "crdb-ca-issuer"
metadata: namespace: #TargetNamespace
spec: ca: secretName: "cockroach-ca"
}
}
Certificate: {
"crdb-ca-cert": #Certificate & {
_description: "Root CA cert for cockroach db"
metadata: name: "crdb-ca-cert"
metadata: namespace: #TargetNamespace
spec: {
commonName: "root"
isCA: true
issuerRef: group: "cert-manager.io"
issuerRef: kind: "Issuer"
issuerRef: name: "crdb"
privateKey: algorithm: "ECDSA"
privateKey: size: 256
secretName: "cockroach-ca"
subject: organizations: ["Cockroach"]
}
}
"crdb-node": #Certificate & {
metadata: name: "crdb-node"
metadata: namespace: #TargetNamespace
spec: {
commonName: "node"
dnsNames: [
"localhost",
"127.0.0.1",
"crdb-public",
"crdb-public.\(#TargetNamespace)",
"crdb-public.\(#TargetNamespace).svc.cluster.local",
"*.crdb",
"*.crdb.\(#TargetNamespace)",
"*.crdb.\(#TargetNamespace).svc.cluster.local",
]
duration: "876h"
issuerRef: group: "cert-manager.io"
issuerRef: kind: "Issuer"
issuerRef: name: "crdb-ca-issuer"
privateKey: algorithm: "RSA"
privateKey: size: 2048
renewBefore: "168h"
secretName: "cockroachdb-node"
subject: organizations: ["Cockroach"]
usages: ["digital signature", "key encipherment", "server auth", "client auth"]
}
}
"crdb-root-client": #Certificate & {
metadata: name: "crdb-root-client"
metadata: namespace: #TargetNamespace
spec: {
commonName: "root"
duration: "672h"
issuerRef: group: "cert-manager.io"
issuerRef: kind: "Issuer"
issuerRef: name: "crdb-ca-issuer"
privateKey: algorithm: "RSA"
privateKey: size: 2048
renewBefore: "48h"
secretName: "cockroachdb-root"
subject: organizations: ["Cockroach"]
usages: ["digital signature", "key encipherment", "client auth"]
}
}
}
Certificate: zitadel: #Certificate & {
metadata: name: "crdb-zitadel-client"
metadata: namespace: #TargetNamespace
spec: {
commonName: "zitadel"
issuerRef: {
group: "cert-manager.io"
kind: "Issuer"
name: "crdb-ca-issuer"
}
privateKey: algorithm: "RSA"
privateKey: size: 2048
renewBefore: "48h0m0s"
secretName: "cockroachdb-zitadel"
subject: organizations: ["Cockroach"]
usages: ["digital signature", "key encipherment", "client auth"]
}
}
}
}

View File

@@ -0,0 +1,7 @@
package holos
#TargetNamespace: #InstancePrefix + "-zitadel"
#DB: {
Host: "crdb-public"
}

View File

@@ -0,0 +1,20 @@
package holos
// Provision all platform certificates.
#InputKeys: component: "certificates"
// Certificates usually go into the istio-system namespace, but they may go anywhere.
#TargetNamespace: "default"
// Depends on issuers
#DependsOn: _LetsEncrypt
#KubernetesObjects & {
apiObjects: {
for k, obj in #PlatformCerts {
"\(obj.kind)": {
"\(obj.metadata.namespace)/\(obj.metadata.name)": obj
}
}
}
}

View File

@@ -0,0 +1,43 @@
package holos
// https://cert-manager.io/docs/
#TargetNamespace: "cert-manager"
#InputKeys: {
component: "certmanager"
service: "cert-manager"
}
#HelmChart & {
values: #Values & {
installCRDs: true
startupapicheck: enabled: false
// Must not use kube-system on gke autopilot. GKE Warden authz blocks access.
global: leaderElection: namespace: #TargetNamespace
}
namespace: #TargetNamespace
chart: {
name: "cert-manager"
version: "1.14.3"
repository: {
name: "jetstack"
url: "https://charts.jetstack.io"
}
}
}
// https://cloud.google.com/kubernetes-engine/docs/concepts/autopilot-resource-requests#min-max-requests
#PodResources: {
requests: {
cpu: string | *"250m"
memory: string | *"512Mi"
"ephemeral-storage": string | *"100Mi"
}
}
// https://cloud.google.com/kubernetes-engine/docs/how-to/autopilot-spot-pods
#NodeSelector: {
"kubernetes.io/os": "linux"
"cloud.google.com/gke-spot": "true"
}

View File

@@ -1,6 +1,6 @@
package holos
#UpstreamValues: {
#Values: {
// +docs:section=Global
// Default values for cert-manager.
@@ -51,7 +51,7 @@ package holos
leaderElection: {
// Override the namespace used for the leader election lease
namespace: "kube-system"
namespace: string | *"kube-system"
}
}
@@ -246,7 +246,7 @@ package holos
// memory: 32Mi
//
// ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
resources: {}
resources: #PodResources
// Pod Security Context
// ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/
@@ -310,9 +310,7 @@ package holos
// This default ensures that Pods are only scheduled to Linux nodes.
// It prevents Pods being scheduled to Windows nodes in a mixed OS cluster.
// +docs:property
nodeSelector: {
"kubernetes.io/os": "linux"
}
nodeSelector: #NodeSelector
// +docs:ignore
ingressShim: {}
@@ -408,7 +406,7 @@ package holos
enabled: true
servicemonitor: {
// Create a ServiceMonitor to add cert-manager to Prometheus
enabled: false
enabled: true | *false
// Specifies the `prometheus` label on the created ServiceMonitor, this is
// used when different Prometheus instances have label selectors matching
@@ -652,7 +650,7 @@ package holos
// memory: 32Mi
//
// ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
resources: {}
resources: #PodResources
// Liveness probe values
// ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes
@@ -685,9 +683,7 @@ package holos
// This default ensures that Pods are only scheduled to Linux nodes.
// It prevents Pods being scheduled to Windows nodes in a mixed OS cluster.
// +docs:property
nodeSelector: {
"kubernetes.io/os": "linux"
}
nodeSelector: #NodeSelector
// A Kubernetes Affinity, if required; see https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#affinity-v1-core
//
@@ -959,7 +955,7 @@ package holos
// memory: 32Mi
//
// ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
resources: {}
resources: #PodResources
// The nodeSelector on Pods tells Kubernetes to schedule Pods on the nodes with
// matching labels.
@@ -968,9 +964,7 @@ package holos
// This default ensures that Pods are only scheduled to Linux nodes.
// It prevents Pods being scheduled to Windows nodes in a mixed OS cluster.
// +docs:property
nodeSelector: {
"kubernetes.io/os": "linux"
}
nodeSelector: #NodeSelector
// A Kubernetes Affinity, if required; see https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#affinity-v1-core
//
@@ -1098,7 +1092,7 @@ package holos
startupapicheck: {
// Enables the startup api check
enabled: true
enabled: *true | false
// Pod Security Context to be set on the startupapicheck component Pod
// ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/
@@ -1151,7 +1145,7 @@ package holos
// memory: 32Mi
//
// ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
resources: {}
resources: #PodResources
// The nodeSelector on Pods tells Kubernetes to schedule Pods on the nodes with
// matching labels.
@@ -1160,9 +1154,7 @@ package holos
// This default ensures that Pods are only scheduled to Linux nodes.
// It prevents Pods being scheduled to Windows nodes in a mixed OS cluster.
// +docs:property
nodeSelector: {
"kubernetes.io/os": "linux"
}
nodeSelector: #NodeSelector
// A Kubernetes Affinity, if required; see https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#affinity-v1-core
//

View File

@@ -0,0 +1,78 @@
package holos
// Lets Encrypt certificate issuers for public tls certs
#InputKeys: component: "letsencrypt"
#TargetNamespace: "cert-manager"
let Name = "letsencrypt"
// The cloudflare api token is platform scoped, not cluster scoped.
#SecretName: "cloudflare-api-token-secret"
// Depends on cert manager
#DependsOn: _CertManager
#KubernetesObjects & {
apiObjects: {
ClusterIssuer: {
letsencrypt: #ClusterIssuer & {
metadata: name: Name
spec: {
acme: {
email: #Platform.org.contact.email
server: "https://acme-v02.api.letsencrypt.org/directory"
privateKeySecretRef: name: Name
solvers: [{
dns01: cloudflare: {
email: #Platform.org.cloudflare.email
apiTokenSecretRef: name: #SecretName
apiTokenSecretRef: key: "api_token"
}}]
}
}
}
letsencryptStaging: #ClusterIssuer & {
metadata: name: Name + "-staging"
spec: {
acme: {
email: #Platform.org.contact.email
server: "https://acme-staging-v02.api.letsencrypt.org/directory"
privateKeySecretRef: name: Name + "-staging"
solvers: [{
dns01: cloudflare: {
email: #Platform.org.cloudflare.email
apiTokenSecretRef: name: #SecretName
apiTokenSecretRef: key: "api_token"
}}]
}
}
}
}
}
}
// _HTTPSolvers are disabled in the provisioner cluster, dns is the method supported by holos.
_HTTPSolvers: {
letsencryptHTTP: #ClusterIssuer & {
metadata: name: Name + "-http"
spec: {
acme: {
email: #Platform.org.contact.email
server: "https://acme-v02.api.letsencrypt.org/directory"
privateKeySecretRef: name: Name
solvers: [{http01: ingress: class: "istio"}]
}
}
}
letsencryptHTTPStaging: #ClusterIssuer & {
metadata: name: Name + "-http-staging"
spec: {
acme: {
email: #Platform.org.contact.email
server: "https://acme-staging-v02.api.letsencrypt.org/directory"
privateKeySecretRef: name: Name + "-staging"
solvers: [{http01: ingress: class: "istio"}]
}
}
}
}

View File

@@ -0,0 +1,13 @@
package holos
// Components under this directory are part of this collection
#InputKeys: project: "mesh"
// Shared dependencies for all components in this collection.
#DependsOn: _Namespaces
// Common Dependencies
_Namespaces: Namespaces: name: "\(#StageName)-secrets-namespaces"
_CertManager: CertManager: name: "\(#InstancePrefix)-certmanager"
_LetsEncrypt: LetsEncrypt: name: "\(#InstancePrefix)-letsencrypt"
_Certificates: Certificates: name: "\(#InstancePrefix)-certificates"

View File

@@ -1,61 +0,0 @@
package holos
// Lets Encrypt certificate issuers for public tls certs
#InputKeys: component: "certissuers"
#TargetNamespace: "cert-manager"
let Name = "letsencrypt"
// The cloudflare api token is platform scoped, not cluster scoped.
#SecretName: "cloudflare-api-token-secret"
// Depends on cert manager
#DependsOn: _CertManager
#KubernetesObjects & {
apiObjects: {
ClusterIssuer: {
letsencrypt: #ClusterIssuer & {
metadata: name: Name
spec: {
acme: {
email: #Platform.org.contact.email
server: "https://acme-v02.api.letsencrypt.org/directory"
privateKeySecretRef: name: Name + "-istio"
solvers: [{http01: ingress: class: "istio"}]
}
}
}
letsencryptStaging: #ClusterIssuer & {
metadata: name: Name + "-staging"
spec: {
acme: {
email: #Platform.org.contact.email
server: "https://acme-staging-v02.api.letsencrypt.org/directory"
privateKeySecretRef: name: Name + "-staging-istio"
solvers: [{http01: ingress: class: "istio"}]
}
}
}
letsencryptDns: #ClusterIssuer & {
metadata: name: Name + "-dns"
spec: {
acme: {
email: #Platform.org.contact.email
server: "https://acme-v02.api.letsencrypt.org/directory"
privateKeySecretRef: name: Name + "-istio"
solvers: [{
dns01: cloudflare: {
email: #Platform.org.cloudflare.email
apiTokenSecretRef: name: #SecretName
apiTokenSecretRef: key: "api_token"
}}]
}
}
}
}
ExternalSecret: "\(#SecretName)": #ExternalSecret & {
_name: #SecretName
}
}
}

View File

@@ -1,25 +0,0 @@
package holos
// https://cert-manager.io/docs/
#TargetNamespace: "cert-manager"
#InputKeys: {
component: "certmanager"
service: "cert-manager"
}
#HelmChart & {
values: #UpstreamValues & {
installCRDs: true
}
namespace: #TargetNamespace
chart: {
name: "cert-manager"
version: "1.14.3"
repository: {
name: "jetstack"
url: "https://charts.jetstack.io"
}
}
}

View File

@@ -0,0 +1,35 @@
package holos
// The primary istio Gateway, named default
let Name = "gateway"
#InputKeys: component: Name
#TargetNamespace: "istio-ingress"
#DependsOn: _IngressGateway
let LoginCert = #PlatformCerts.login
#KubernetesObjects & {
apiObjects: {
ExternalSecret: login: #ExternalSecret & {
_name: "login"
}
Gateway: default: #Gateway & {
metadata: name: "default"
metadata: namespace: #TargetNamespace
spec: selector: istio: "ingressgateway"
spec: servers: [
{
hosts: [for dnsName in LoginCert.spec.dnsNames {"prod-iam-zitadel/\(dnsName)"}]
port: name: "https-prod-iam-login"
port: number: 443
port: protocol: "HTTPS"
tls: credentialName: LoginCert.spec.secretName
tls: mode: "SIMPLE"
},
]
}
}
}

View File

@@ -14,14 +14,13 @@ let Metadata = {
#TargetNamespace: "istio-ingress"
#DependsOn: _IngressGateway
let Cert = #HTTP01Cert & {
_name: Name
_secret: SecretName
}
let Cert = #PlatformCerts[SecretName]
#KubernetesObjects & {
apiObjects: {
Certificate: httpbin: Cert.object
ExternalSecret: httpbin: #ExternalSecret & {
_name: Cert.spec.secretName
}
Deployment: httpbin: #Deployment & {
metadata: Metadata
spec: selector: matchLabels: MatchLabels
@@ -56,18 +55,18 @@ let Cert = #HTTP01Cert & {
spec: selector: istio: "ingressgateway"
spec: servers: [
{
hosts: ["\(#TargetNamespace)/\(Cert.Host)"]
hosts: [for host in Cert.spec.dnsNames {"\(#TargetNamespace)/\(host)"}]
port: name: "https-\(#InstanceName)"
port: number: 443
port: protocol: "HTTPS"
tls: credentialName: Cert.SecretName
tls: credentialName: Cert.spec.secretName
tls: mode: "SIMPLE"
},
]
}
VirtualService: httpbin: #VirtualService & {
metadata: Metadata
spec: hosts: [Cert.Host]
spec: hosts: [for host in Cert.spec.dnsNames {host}]
spec: gateways: ["\(#TargetNamespace)/\(Name)"]
spec: http: [{route: [{destination: host: Name}]}]
}

View File

@@ -2,6 +2,15 @@ package holos
import "encoding/json"
#DependsOn: _ESO
#InputKeys: {
project: "secrets"
component: "eso-creds-refresher"
}
#TargetNamespace: #CredsRefresher.namespace
// output kubernetes api objects for holos
#KubernetesObjects & {
apiObjects: {
@@ -13,15 +22,6 @@ import "encoding/json"
}
}
#InputKeys: {
project: "secrets"
component: "eso-creds-refresher"
}
#TargetNamespace: #CredsRefresher.namespace
#DependsOn: Namespaces: name: #InstancePrefix + "-namespaces"
let NAME = #CredsRefresher.name
let AUD = "//iam.googleapis.com/projects/\(#InputKeys.gcpProjectNumber)/locations/global/workloadIdentityPools/holos/providers/k8s-\(#InputKeys.cluster)"
let MOUNT = "/var/run/service-account"

View File

@@ -0,0 +1,12 @@
package holos
// Components under this directory are part of this collection
#InputKeys: project: "secrets"
// Shared dependencies for all components in this collection.
#DependsOn: _Namespaces
// Common Dependencies
_Namespaces: Namespaces: name: "\(#StageName)-secrets-namespaces"
_ESO: ESO: name: "\(#InstancePrefix)-eso"
_ESOCreds: ESOCreds: name: "\(#InstancePrefix)-eso-creds-refresher"

View File

@@ -0,0 +1,34 @@
package holos
#DependsOn: _ESOCreds
#TargetNamespace: "default"
#InputKeys: {
project: "secrets"
component: "stores"
}
// #PlatformNamespaceObjects defines the api objects necessary for eso SecretStores in external clusters to access secrets in a given namespace in the provisioner cluster.
#PlatformNamespaceObjects: {
_ns: #PlatformNamespace
objects: [
#SecretStore & {
_namespace: _ns.name
},
]
}
#KubernetesObjects & {
apiObjects: {
for ns in #PlatformNamespaces {
for obj in (#PlatformNamespaceObjects & {_ns: ns}).objects {
let Kind = obj.kind
let NS = ns.name
let Name = obj.metadata.name
"\(Kind)": "\(NS)/\(Name)": obj
}
}
}
}

View File

@@ -9,7 +9,7 @@ package holos
component: "validate"
}
#DependsOn: Namespaces: name: #InstancePrefix + "-eso"
#DependsOn: _ESO
#KubernetesObjects & {
apiObjects: {

View File

@@ -21,4 +21,5 @@ let Privileged = {
{name: "istio-ingress"} & Restricted,
{name: "cert-manager"},
{name: "argocd"},
{name: "prod-iam-zitadel"},
]

View File

@@ -9,13 +9,17 @@ import (
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"
@@ -91,6 +95,8 @@ _apiVersion: "holos.run/v1alpha1"
#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
@@ -257,7 +263,7 @@ _apiVersion: "holos.run/v1alpha1"
}
}
// apiObjectsContent holds the marshalled representation of apiObjects
// apiObjectMap holds the marshalled representation of apiObjects
apiObjectMap: {
for kind, v in apiObjects {
"\(kind)": {
@@ -337,6 +343,10 @@ _apiVersion: "holos.run/v1alpha1"
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.
@@ -357,6 +367,28 @@ _apiVersion: "holos.run/v1alpha1"
...
}
// #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.

View File

@@ -133,9 +133,12 @@ This section configured:
1. Provisioner Cluster to provide secrets to workload clusters.
2. IAM service account `eso-creds-refresher` to identify the credential refresher job.
3. Workload identity pool to authenticate the `eso-creds-refresher` Kubernetes service account in an external cluster.
4. IAM policy to allow `eso-creds-refresher` to authenticate to the Provisioner Cluster.
5. RoleBinding to allow `eso-creds-refresher` to create kubernetes service account tokens representing the credentials for use by SecretStore resources in workload clusters.
3. Workload identity pool to authenticate the `system:serviceaccount:holos-system:eso-creds-refresher` Kubernetes service account in all clusters that share the workload identity pool.
4. IAM policy to allow the `eso-creds-refresher` IAM service account to authenticate to the Provisioner Cluster.
5. RoleBinding to allow the `eso-creds-refresher` IAM service account to create kubernetes service account tokens representing the credentials for use by SecretStore resources in workload clusters.
> [!NOTE]
> Any cluster in the workload identity pool can impersonate the eso-creds-refresher IAM service account.
## Cluster Setup
@@ -150,6 +153,12 @@ HOLOS_CLUSTER_NAME=west1
ISSUER_URL="https://example.com/clusters/${HOLOS_CLUSTER_NAME}"
```
Alternatively:
```shell
ISSUER_URL="$(kubectl get --raw='/.well-known/openid-configuration' | jq -r .issuer)"
```
```shell
gcloud iam workload-identity-pools providers create-oidc \
k8s-$HOLOS_CLUSTER_NAME \

10
go.mod
View File

@@ -5,10 +5,14 @@ go 1.21.5
require (
cuelang.org/go v0.7.0
github.com/mattn/go-isatty v0.0.20
github.com/rogpeppe/go-internal v1.12.0
github.com/spf13/cobra v1.7.0
golang.org/x/tools v0.18.0
k8s.io/api v0.29.2
k8s.io/apimachinery v0.29.2
k8s.io/client-go v0.29.2
k8s.io/kubectl v0.29.2
sigs.k8s.io/yaml v1.4.0
)
require (
@@ -27,7 +31,7 @@ require (
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/imdario/mergo v0.3.6 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
@@ -41,7 +45,6 @@ require (
github.com/opencontainers/image-spec v1.1.0-rc4 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/protocolbuffers/txtpbfmt v0.0.0-20230328191034-3462fbc510c0 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/oauth2 v0.10.0 // indirect
@@ -54,12 +57,9 @@ require (
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.29.2 // indirect
k8s.io/klog/v2 v2.110.1 // indirect
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
k8s.io/kubectl v0.29.2 // indirect
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)

11
go.sum
View File

@@ -46,8 +46,8 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJY
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
@@ -96,8 +96,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/protocolbuffers/txtpbfmt v0.0.0-20230328191034-3462fbc510c0 h1:sadMIsgmHpEOGbUs6VtHBXRR1OHevnj7hLx9ZcdNGW4=
github.com/protocolbuffers/txtpbfmt v0.0.0-20230328191034-3462fbc510c0/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c=
github.com/rogpeppe/go-internal v1.11.1-0.20231026093722-fa6a31e0812c h1:fPpdjePK1atuOg28PXfNSqgwf9I/qD1Hlo39JFwKBXk=
github.com/rogpeppe/go-internal v1.11.1-0.20231026093722-fa6a31e0812c/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -177,6 +175,7 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.29.2 h1:hBC7B9+MU+ptchxEqTNW2DkUosJpp1P+Wn6YncZ474A=
@@ -197,5 +196,5 @@ sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMm
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=

View File

@@ -23,7 +23,7 @@ func makeBuildRunFunc(cfg *holos.Config) command.RunFunc {
if result.Skip {
continue
}
outs = append(outs, result.FinalOutput())
outs = append(outs, result.AccumulatedOutput())
}
out := strings.Join(outs, "---\n")
if _, err := fmt.Fprintln(cmd.OutOrStdout(), out); err != nil {

129
pkg/cli/preflight/gh.go Normal file
View File

@@ -0,0 +1,129 @@
package preflight
import (
"context"
"fmt"
"strings"
"github.com/holos-run/holos/pkg/logger"
"github.com/holos-run/holos/pkg/util"
"github.com/holos-run/holos/pkg/wrapper"
)
type ghAuthStatusResponse string
// RunGhChecks runs all the preflight checks related to GitHub.
func RunGhChecks(ctx context.Context, cfg *config) error {
if err := cliIsInstalled(ctx); err != nil {
return err
}
if err := cliIsAuthed(ctx, cfg); err != nil {
return err
}
return nil
}
// cliIsInstalled checks if the GitHub CLI is installed.
func cliIsInstalled(ctx context.Context) error {
log := logger.FromContext(ctx)
version, err := getGhVersion(ctx)
if err != nil {
log.WarnContext(ctx, "GitHub CLI (gh) not installed or not in PATH.")
return guideToInstallGh(ctx)
}
log.InfoContext(ctx, "GitHub CLI found", "gh_version", version)
return nil
}
// cliIsAuthed checks if 'gh' is authenticated. If not, 'gh auth login' is run then cliIsAuthed is called again.
func cliIsAuthed(ctx context.Context, cfg *config) error {
log := logger.FromContext(ctx)
status, err := ghAuthStatus(ctx, cfg)
if err != nil || !ghIsAuthenticated(status, cfg) {
log.WarnContext(ctx, "GitHub CLI not authenticated to "+*cfg.githubInstance)
if err := authenticateGh(ctx, cfg); err != nil {
return wrapper.Wrap(fmt.Errorf("failed to authenticate the GitHub CLI to %v: %w", cfg.githubInstance, err))
}
// Re-run this check now that gh should be authenticated.
err := cliIsAuthed(ctx, cfg)
return err
}
log.InfoContext(ctx, "GitHub CLI is authenticated to "+*cfg.githubInstance)
if !ghTokenAllowsRepoCreation(status) {
return wrapper.Wrap(fmt.Errorf("GitHub token does not have the necessary scopes to create a repository"))
}
log.InfoContext(ctx, "GitHub token is able to create a repository")
return nil
}
// ghAuthStatus runs 'gh auth status' and returns the result.
func ghAuthStatus(ctx context.Context, cfg *config) (ghAuthStatusResponse, error) {
log := logger.FromContext(ctx)
out, err := util.RunCmd(ctx, "gh", "auth", "status", "--hostname="+*cfg.githubInstance)
var status ghAuthStatusResponse
if err != nil {
status = ghAuthStatusResponse(out.Stderr.String())
} else {
status = ghAuthStatusResponse(out.Stdout.String())
}
log.DebugContext(ctx, "gh auth status", "gh_auth_status", status)
return status, err
}
// getGhVersion retrieves the version of 'gh'.
func getGhVersion(ctx context.Context) (string, error) {
out, err := util.RunCmd(ctx, "gh", "--version")
if err != nil {
return "", err
}
return strings.Split(out.Stdout.String(), "\n")[0], nil
}
// guideToInstallGh guides the user towards installing the GitHub CLI.
func guideToInstallGh(ctx context.Context) error {
log := logger.FromContext(ctx)
log.WarnContext(ctx, "The GitHub CLI is required to set up Holos. To install it, follow the instructions at: https://github.com/cli/cli#installation")
return wrapper.Wrap(fmt.Errorf("GitHub CLI is not installed"))
}
// authenticateGh runs 'gh auth login' to authenticate the GitHub CLI.
func authenticateGh(ctx context.Context, cfg *config) error {
log := logger.FromContext(ctx)
log.InfoContext(ctx, "Authenticating GitHub CLI with 'gh auth login --hostname="+*cfg.githubInstance+"'. Please follow the prompts.")
err := util.RunInteractiveCmd(ctx, "gh", "auth", "login", "--hostname="+*cfg.githubInstance)
if err != nil {
log.ErrorContext(ctx, "Failed to authenticate GitHub CLI")
return wrapper.Wrap(fmt.Errorf("failed to authenticate GitHub CLI: %w", err))
}
log.InfoContext(ctx, "GitHub CLI has been authenticated.")
return nil
}
// ghIsAuthenticated checks if the GitHub CLI is authenticated and logged in to githubInstance.
func ghIsAuthenticated(status ghAuthStatusResponse, cfg *config) bool {
return strings.Contains(string(status), "Logged in to "+*cfg.githubInstance)
}
// ghTokenAllowsRepoCreation validates that the GitHub CLI is authenticated
// with a token that allows repository creation. This is a naive implementation
// that just checks the output of 'gh auth status' for the presence of the
// 'repo' scope. Note that the 'repo' scope is sufficient to create a secret in
// a repository, so this check also covers that.
// Example token scope line: "- Token scopes: 'admin:public_key', 'gist', 'read:org', 'repo'"
func ghTokenAllowsRepoCreation(status ghAuthStatusResponse) bool {
return strings.Contains(string(status), "'repo'")
}

View File

@@ -0,0 +1,36 @@
package preflight
import "testing"
func TestGhTokenAllowsRepoCreation(t *testing.T) {
testCases := []struct {
name string
status ghAuthStatusResponse
expected bool
}{
{
name: "token has necessary scopes",
status: "- Token: gho_************************************\n- Token scopes: 'gist', 'read:org', 'repo'",
expected: true,
},
{
name: "token has necessary scopes",
status: " - Token scopes: 'gist', 'read:org', 'repo'",
expected: true,
},
{
name: "token does not have necessary scopes",
status: "- Token: gho_************************************\n- Token scopes: 'gist', 'read:org'",
expected: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := ghTokenAllowsRepoCreation(tc.status)
if result != tc.expected {
t.Errorf("expected %v, got %v", tc.expected, result)
}
})
}
}

View File

@@ -0,0 +1,55 @@
package preflight
import (
"flag"
"github.com/spf13/cobra"
"github.com/holos-run/holos/pkg/cli/command"
"github.com/holos-run/holos/pkg/holos"
"github.com/holos-run/holos/pkg/logger"
)
// Config holds configuration parameters for preflight checks.
type config struct {
githubInstance *string
}
// Build the shared configuration and flagset for the preflight command.
func newConfig() (*config, *flag.FlagSet) {
cfg := &config{}
flagSet := flag.NewFlagSet("", flag.ContinueOnError)
cfg.githubInstance = flagSet.String("github-instance", "github.com", "Address of the GitHub instance you want to use")
return cfg, flagSet
}
// New returns the preflight command for the root command.
func New(hc *holos.Config) *cobra.Command {
cfg, flagSet := newConfig()
cmd := command.New("preflight")
cmd.Short = "Run preflight checks to ensure you're ready to use Holos"
cmd.Flags().AddGoFlagSet(flagSet)
cmd.RunE = makePreflightRunFunc(hc, cfg)
return cmd
}
// makePreflightRunFunc returns the internal implementation of the preflight cli command.
func makePreflightRunFunc(_ *holos.Config, cfg *config) command.RunFunc {
return func(cmd *cobra.Command, _ []string) error {
ctx := cmd.Context()
log := logger.FromContext(ctx)
log.Info("Starting preflight checks")
// GitHub checks
if err := RunGhChecks(ctx, cfg); err != nil {
return err
}
// Other checks can be added here
log.Info("Preflight checks complete. Ready to use Holos 🚀")
return nil
}
}

View File

@@ -32,7 +32,7 @@ func makeRenderRunFunc(cfg *holos.Config) command.RunFunc {
}
// API Objects
path := result.Filename(cfg.WriteTo(), cfg.ClusterName())
if err := result.Save(ctx, path, result.FinalOutput()); err != nil {
if err := result.Save(ctx, path, result.AccumulatedOutput()); err != nil {
return wrapper.Wrap(err)
}
// Kustomization

View File

@@ -1,17 +1,20 @@
package cli
import (
"log/slog"
"github.com/spf13/cobra"
"github.com/holos-run/holos/pkg/cli/build"
"github.com/holos-run/holos/pkg/cli/create"
"github.com/holos-run/holos/pkg/cli/get"
"github.com/holos-run/holos/pkg/cli/kv"
"github.com/holos-run/holos/pkg/cli/preflight"
"github.com/holos-run/holos/pkg/cli/render"
"github.com/holos-run/holos/pkg/cli/txtar"
"github.com/holos-run/holos/pkg/holos"
"github.com/holos-run/holos/pkg/logger"
"github.com/holos-run/holos/pkg/version"
"github.com/spf13/cobra"
"log/slog"
)
// New returns a new root *cobra.Command for command line execution.
@@ -51,6 +54,7 @@ func New(cfg *holos.Config) *cobra.Command {
rootCmd.AddCommand(render.New(cfg))
rootCmd.AddCommand(get.New(cfg))
rootCmd.AddCommand(create.New(cfg))
rootCmd.AddCommand(preflight.New(cfg))
// Maybe not needed?
rootCmd.AddCommand(txtar.New(cfg))

View File

@@ -1,6 +1,7 @@
package secret
import (
"bytes"
"fmt"
"github.com/holos-run/holos/pkg/cli/command"
"github.com/holos-run/holos/pkg/holos"
@@ -29,6 +30,7 @@ func NewCreateCmd(hc *holos.Config) *cobra.Command {
cfg.dryRun = flagSet.Bool("dry-run", false, "dry run")
cfg.appendHash = flagSet.Bool("append-hash", true, "append hash to kubernetes secret name")
cfg.dataStdin = flagSet.Bool("data-stdin", false, "read data field as json from stdin if")
cfg.trimTrailingNewlines = flagSet.Bool("trim-trailing-newlines", true, "trim trailing newlines if true")
cmd.Flags().SortFlags = false
cmd.Flags().AddGoFlagSet(flagSet)
@@ -80,7 +82,7 @@ func makeCreateRunFunc(hc *holos.Config, cfg *config) command.RunFunc {
}
for _, file := range cfg.files {
if err := filepath.WalkDir(file, makeWalkFunc(secret.Data, file)); err != nil {
if err := filepath.WalkDir(file, makeWalkFunc(secret.Data, file, *cfg.trimTrailingNewlines)); err != nil {
return wrapper.Wrap(err)
}
}
@@ -125,7 +127,7 @@ func makeCreateRunFunc(hc *holos.Config, cfg *config) command.RunFunc {
}
}
func makeWalkFunc(data secretData, root string) fs.WalkDirFunc {
func makeWalkFunc(data secretData, root string, trimNewlines bool) fs.WalkDirFunc {
return func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
@@ -143,6 +145,9 @@ func makeWalkFunc(data secretData, root string) fs.WalkDirFunc {
if data[key], err = os.ReadFile(path); err != nil {
return wrapper.Wrap(err)
}
if trimNewlines {
data[key] = bytes.TrimRight(data[key], "\r\n")
}
}
return nil

View File

@@ -2,10 +2,12 @@ package secret
import (
"context"
"encoding/json"
"fmt"
"github.com/holos-run/holos/pkg/cli/command"
"github.com/holos-run/holos/pkg/holos"
"github.com/holos-run/holos/pkg/logger"
"github.com/holos-run/holos/pkg/util"
"github.com/holos-run/holos/pkg/wrapper"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -90,17 +92,24 @@ func makeGetRunFunc(hc *holos.Config, cfg *config) command.RunFunc {
printFile := *cfg.printFile
if len(toExtract) == 0 {
if printFile == "" {
printFile = secretName
}
}
if printFile != "" {
if data, found := secret.Data[printFile]; found {
hc.Write(data)
} else {
err := fmt.Errorf("cannot print: want %s have %v: did you mean --extract-all or --%s=name", printFile, keys, printFlagName)
return wrapper.Wrap(err)
if printFile == "" { // print all data keys
data := make(map[string]string)
for k, v := range secret.Data {
data[k] = string(v)
}
b, err := json.MarshalIndent(data, "", " ")
if err != nil {
return wrapper.Wrap(err)
}
b = util.EnsureNewline(b)
hc.Write(b)
} else { // print named data keys keys
if data, found := secret.Data[printFile]; found {
hc.Write(data)
} else {
err := fmt.Errorf("cannot print: want %s have %v: did you mean --extract-all or --%s=name", printFile, keys, printFlagName)
return wrapper.Wrap(err)
}
}
}

View File

@@ -12,15 +12,16 @@ const ClusterLabel = "holos.run/cluster.name"
type secretData map[string][]byte
type config struct {
files holos.StringSlice
printFile *string
extract *bool
dryRun *bool
appendHash *bool
dataStdin *bool
cluster *string
namespace *string
extractTo *string
files holos.StringSlice
printFile *string
extract *bool
dryRun *bool
appendHash *bool
dataStdin *bool
trimTrailingNewlines *bool
cluster *string
namespace *string
extractTo *string
}
func newConfig() (*config, *flag.FlagSet) {

View File

@@ -36,6 +36,30 @@ var secret = v1.Secret{
Type: "Opaque",
}
var loginSecret = v1.Secret{
TypeMeta: metav1.TypeMeta{
Kind: "Secret",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "zitadel-admin-d7fgbgbfbt",
Namespace: "secrets",
Labels: map[string]string{
"holos.run/owner.name": "jeff",
"holos.run/secret.name": "zitadel-admin",
},
CreationTimestamp: metav1.Time{
Time: time.Date(2021, time.January, 1, 0, 0, 0, 0, time.UTC),
},
},
Data: map[string][]byte{
"url": []byte("https://login.example.com"),
"username": []byte("zitadel-admin@zitadel.login.example.com"),
"password": []byte("Password1!"),
},
Type: "Opaque",
}
// cmdHolos executes the holos root command with a kubernetes.Interface that
// persists for the duration of the testscript. holos is NOT executed in a
// subprocess, the current working directory is not and should not be changed.
@@ -72,7 +96,7 @@ func TestSecrets(t *testing.T) {
testscript.Run(t, testscript.Params{
Dir: "testdata",
Setup: func(env *testscript.Env) error {
env.Values[clientsetKey] = fake.NewSimpleClientset(&secret)
env.Values[clientsetKey] = fake.NewSimpleClientset(&secret, &loginSecret)
return nil
},
Cmds: map[string]func(ts *testscript.TestScript, neg bool, args []string){

View File

@@ -1,5 +1,5 @@
# Create the secret
holos create secret directory --from-file=$WORK/fixture --dry-run
holos create secret directory --trim-trailing-newlines=false --from-file=$WORK/fixture --dry-run
# Want no warnings.
! stderr 'WRN'

View File

@@ -1,5 +1,5 @@
# Create the secret
holos create secret directory --from-file=$WORK/want
holos create secret directory --trim-trailing-newlines=false --from-file=$WORK/want
stderr 'created: directory-..........'
stderr 'secret=directory-..........'
stderr 'name=directory'

View File

@@ -0,0 +1,17 @@
# Create a secret from files with trailing newlines
holos create secret smtp --from-file=$WORK/smtp
# Get the secret back expecting no trailing newlines
mkdir have
holos get secret smtp
stdout '"username": "holos.run@gmail.com"'
stdout '"password": "secret"'
-- smtp/username --
holos.run@gmail.com
-- smtp/password --
secret
-- smtp/host --
smtp.gmail.com
-- smtp/port --
587

View File

@@ -1,5 +1,5 @@
# Create the secret
holos create secret directory --from-file=$WORK/want
holos create secret directory --trim-trailing-newlines=false --from-file=$WORK/want
# Get the secret back
mkdir have

View File

@@ -0,0 +1,7 @@
# Print the data key by default
holos get secret zitadel-admin
stdout '^{$'
stdout '^ "url": "https://login.example.com"'
stdout '^ "username": "zitadel-admin@zitadel.login.example.com"'
stdout '^ "password": "Password1!"'
stdout '^}$'

View File

@@ -4,22 +4,22 @@
package builder
import (
"bytes"
"context"
"cuelang.org/go/cue/build"
"fmt"
"log/slog"
"os"
"path/filepath"
"slices"
"strings"
"cuelang.org/go/cue/build"
"cuelang.org/go/cue/cuecontext"
"cuelang.org/go/cue/load"
"github.com/holos-run/holos"
"github.com/holos-run/holos/pkg/logger"
"github.com/holos-run/holos/pkg/util"
"github.com/holos-run/holos/pkg/wrapper"
"log/slog"
"os"
"os/exec"
"path/filepath"
"slices"
"cuelang.org/go/cue/cuecontext"
"cuelang.org/go/cue/load"
)
const (
@@ -81,13 +81,21 @@ type Metadata struct {
// holos cli. A map is used to improve the clarity of error messages from cue.
type apiObjectMap map[string]map[string]string
// fileContentMap is a map of file names to file contents.
type fileContentMap map[string]string
// Result is the build result for display or writing.
type Result struct {
Metadata Metadata `json:"metadata,omitempty"`
KsContent string `json:"ksContent,omitempty"`
APIObjectMap apiObjectMap `json:"apiObjectMap,omitempty"`
finalOutput string
Skip bool
Metadata Metadata `json:"metadata,omitempty"`
KsContent string `json:"ksContent,omitempty"`
// APIObjectMap holds the marshalled representation of api objects.
APIObjectMap apiObjectMap `json:"apiObjectMap,omitempty"`
accumulatedOutput string
Skip bool
// KustomizeFiles holds the files for a kustomize kustomization directory.
KustomizeFiles fileContentMap `json:"kustomizeFiles"`
// ResourcesFile is the file name used for api objects in kustomization.yaml
ResourcesFile string `json:"resourcesFile,omitempty"`
}
type Repository struct {
@@ -103,14 +111,15 @@ type Chart struct {
// A HelmChart represents a helm command to provide chart values in order to render kubernetes api objects.
type HelmChart struct {
APIVersion string `json:"apiVersion"`
Kind string `json:"kind"`
Metadata Metadata `json:"metadata"`
KsContent string `json:"ksContent"`
Namespace string `json:"namespace"`
Chart Chart `json:"chart"`
ValuesContent string `json:"valuesContent"`
APIObjectMap apiObjectMap `json:"APIObjectMap"`
APIVersion string `json:"apiVersion"`
Kind string `json:"kind"`
Metadata Metadata `json:"metadata"`
KsContent string `json:"ksContent"`
Namespace string `json:"namespace"`
Chart Chart `json:"chart"`
ValuesContent string `json:"valuesContent"`
// APIObjectMap holds the marshalled representation of api objects.
APIObjectMap apiObjectMap `json:"APIObjectMap"`
}
// Name returns the metadata name of the result. Equivalent to the
@@ -127,14 +136,14 @@ func (r *Result) KustomizationFilename(writeTo string, cluster string) string {
return filepath.Join(writeTo, "clusters", cluster, "holos", "components", r.Name()+"-kustomization.gen.yaml")
}
// FinalOutput returns the final rendered output.
func (r *Result) FinalOutput() string {
return r.finalOutput
// AccumulatedOutput returns the accumulated rendered output.
func (r *Result) AccumulatedOutput() string {
return r.accumulatedOutput
}
// addAPIObjects adds the overlay api objects to finalOutput.
// addAPIObjects adds the overlay api objects to accumulatedOutput.
func (r *Result) addOverlayObjects(log *slog.Logger) {
b := []byte(r.FinalOutput())
b := []byte(r.AccumulatedOutput())
kinds := make([]string, 0, len(r.APIObjectMap))
// Sort the keys
for kind := range r.APIObjectMap {
@@ -154,13 +163,64 @@ func (r *Result) addOverlayObjects(log *slog.Logger) {
for _, name := range names {
yamlString := v[name]
log.Debug(fmt.Sprintf("%s/%s", kind, name), "kind", kind, "name", name)
util.EnsureNewline(b)
b = util.EnsureNewline(b)
header := fmt.Sprintf("---\n# Source: CUE apiObjects.%s.%s\n", kind, name)
b = append(b, []byte(header+yamlString)...)
util.EnsureNewline(b)
b = util.EnsureNewline(b)
}
}
r.finalOutput = string(b)
r.accumulatedOutput = string(b)
}
// kustomize replaces the final output with the output of kustomize build if the
func (r *Result) kustomize(ctx context.Context) error {
log := logger.FromContext(ctx)
if r.ResourcesFile == "" {
log.DebugContext(ctx, "skipping kustomize: no resourcesFile")
return nil
}
if len(r.KustomizeFiles) < 1 {
log.DebugContext(ctx, "skipping kustomize: no kustomizeFiles")
return nil
}
tempDir, err := os.MkdirTemp("", "holos.kustomize")
if err != nil {
return wrapper.Wrap(err)
}
defer remove(ctx, tempDir)
// Write the main api object resources file for kustomize.
target := filepath.Join(tempDir, r.ResourcesFile)
b := []byte(r.AccumulatedOutput())
b = util.EnsureNewline(b)
if err := os.WriteFile(target, b, 0644); err != nil {
return wrapper.Wrap(fmt.Errorf("could not write resources: %w", err))
}
log.DebugContext(ctx, "wrote: "+target, "op", "write", "path", target, "bytes", len(b))
// Write the kustomization tree, kustomization.yaml must be in this map for kustomize to work.
for file, content := range r.KustomizeFiles {
target := filepath.Join(tempDir, file)
if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
return wrapper.Wrap(err)
}
b := []byte(content)
b = util.EnsureNewline(b)
if err := os.WriteFile(target, b, 0644); err != nil {
return wrapper.Wrap(fmt.Errorf("could not write: %w", err))
}
log.DebugContext(ctx, "wrote: "+target, "op", "write", "path", target, "bytes", len(b))
}
// Run kustomize.
kOut, err := util.RunCmd(ctx, "kubectl", "kustomize", tempDir)
if err != nil {
log.ErrorContext(ctx, kOut.Stderr.String())
return wrapper.Wrap(err)
}
// Replace the accumulated output
r.accumulatedOutput = kOut.Stdout.String()
return nil
}
// Save writes the content to the filesystem for git ops.
@@ -277,6 +337,9 @@ func (b *Builder) Run(ctx context.Context) (results []*Result, err error) {
return nil, err
}
result.addOverlayObjects(log)
if err := result.kustomize(ctx); err != nil {
return nil, wrapper.Wrap(fmt.Errorf("could not kustomize: %w", err))
}
default:
return nil, wrapper.Wrap(fmt.Errorf("build kind not implemented: %v", kind))
}
@@ -315,25 +378,6 @@ func (b *Builder) findCueMod() (dir holos.PathCueMod, err error) {
return dir, nil
}
type runResult struct {
stdout *bytes.Buffer
stderr *bytes.Buffer
}
func runCmd(ctx context.Context, name string, args ...string) (result runResult, err error) {
result = runResult{
stdout: new(bytes.Buffer),
stderr: new(bytes.Buffer),
}
cmd := exec.CommandContext(ctx, name, args...)
cmd.Stdout = result.stdout
cmd.Stderr = result.stderr
log := logger.FromContext(ctx)
log.DebugContext(ctx, "run: "+name, "name", name, "args", args)
err = cmd.Run()
return
}
// runHelm provides the values produced by CUE to helm template and returns
// the rendered kubernetes api objects in the result.
func runHelm(ctx context.Context, hc *HelmChart, r *Result, path holos.PathComponent) error {
@@ -347,15 +391,15 @@ func runHelm(ctx context.Context, hc *HelmChart, r *Result, path holos.PathCompo
if isNotExist(cachedChartPath) {
// Add repositories
repo := hc.Chart.Repository
out, err := runCmd(ctx, "helm", "repo", "add", repo.Name, repo.URL)
out, err := util.RunCmd(ctx, "helm", "repo", "add", repo.Name, repo.URL)
if err != nil {
log.ErrorContext(ctx, "could not run helm", "stderr", out.stderr.String(), "stdout", out.stdout.String())
log.ErrorContext(ctx, "could not run helm", "stderr", out.Stderr.String(), "stdout", out.Stdout.String())
return wrapper.Wrap(fmt.Errorf("could not run helm repo add: %w", err))
}
// Update repository
out, err = runCmd(ctx, "helm", "repo", "update", repo.Name)
out, err = util.RunCmd(ctx, "helm", "repo", "update", repo.Name)
if err != nil {
log.ErrorContext(ctx, "could not run helm", "stderr", out.stderr.String(), "stdout", out.stdout.String())
log.ErrorContext(ctx, "could not run helm", "stderr", out.Stderr.String(), "stdout", out.Stdout.String())
return wrapper.Wrap(fmt.Errorf("could not run helm repo update: %w", err))
}
// Cache the chart
@@ -379,12 +423,19 @@ func runHelm(ctx context.Context, hc *HelmChart, r *Result, path holos.PathCompo
// Run charts
chart := hc.Chart
helmOut, err := runCmd(ctx, "helm", "template", "--values", valuesPath, "--namespace", hc.Namespace, "--kubeconfig", "/dev/null", "--version", chart.Version, chart.Name, cachedChartPath)
helmOut, err := util.RunCmd(ctx, "helm", "template", "--values", valuesPath, "--namespace", hc.Namespace, "--kubeconfig", "/dev/null", "--version", chart.Version, chart.Name, cachedChartPath)
if err != nil {
stderr := helmOut.Stderr.String()
lines := strings.Split(stderr, "\n")
for _, line := range lines {
if strings.HasPrefix(line, "Error:") {
err = fmt.Errorf("%s: %w", line, err)
}
}
return wrapper.Wrap(fmt.Errorf("could not run helm template: %w", err))
}
r.finalOutput = helmOut.stdout.String()
r.accumulatedOutput = helmOut.Stdout.String()
return nil
}
@@ -415,11 +466,11 @@ func cacheChart(ctx context.Context, path holos.PathComponent, chartDir string,
defer remove(ctx, cacheTemp)
chartName := fmt.Sprintf("%s/%s", chart.Repository.Name, chart.Name)
helmOut, err := runCmd(ctx, "helm", "pull", "--destination", cacheTemp, "--untar=true", "--version", chart.Version, chartName)
helmOut, err := util.RunCmd(ctx, "helm", "pull", "--destination", cacheTemp, "--untar=true", "--version", chart.Version, chartName)
if err != nil {
return wrapper.Wrap(fmt.Errorf("could not run helm pull: %w", err))
}
log.Debug("helm pull", "stdout", helmOut.stdout, "stderr", helmOut.stderr)
log.Debug("helm pull", "stdout", helmOut.Stdout, "stderr", helmOut.Stderr)
cachePath := filepath.Join(string(path), chartDir)
if err := os.Rename(cacheTemp, cachePath); err != nil {

56
pkg/util/run.go Normal file
View File

@@ -0,0 +1,56 @@
package util
import (
"bytes"
"context"
"os"
"os/exec"
"github.com/holos-run/holos/pkg/logger"
)
// runResult holds the stdout and stderr of a command.
type RunResult struct {
Stdout *bytes.Buffer
Stderr *bytes.Buffer
}
// RunCmd runs a command within a context, captures its output, provides debug
// logging, and returns the result.
// Example:
//
// result, err := RunCmd(ctx, "echo", "hello")
// if err != nil {
// return wrapper.Wrap(err)
// }
// fmt.Println(result.Stdout.String())
//
// Output:
//
// "hello\n"
func RunCmd(ctx context.Context, name string, args ...string) (result RunResult, err error) {
result = RunResult{
Stdout: new(bytes.Buffer),
Stderr: new(bytes.Buffer),
}
cmd := exec.CommandContext(ctx, name, args...)
cmd.Stdout = result.Stdout
cmd.Stderr = result.Stderr
log := logger.FromContext(ctx)
log.DebugContext(ctx, "running: "+name, "name", name, "args", args)
err = cmd.Run()
return result, err
}
// RunInteractiveCmd runs a command within a context but allows the command to
// accept stdin interactively from the user. The caller is expected to handle
// errors.
func RunInteractiveCmd(ctx context.Context, name string, args ...string) error {
cmd := exec.CommandContext(ctx, name, args...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
log := logger.FromContext(ctx)
log.DebugContext(ctx, "running: "+name, "name", name, "args", args)
return cmd.Run()
}

View File

@@ -1 +1 @@
49
53

View File

@@ -1 +1 @@
7
4