Compare commits

..

9 Commits

Author SHA1 Message Date
Jeff McCune
3c977d22fe (#71) Final refactoring of example code to use BuildPlan
Need to test it on all the clusters now.  Will follow up with any
necessary fixes.
2024-03-22 16:58:52 -07:00
Jeff McCune
e34db2b583 (#71) Refactor provisioner to produce a BuildPlan 2024-03-22 16:42:57 -07:00
Jeff McCune
71de57ac88 (#71) Refactor optional vault service to BuildPlan 2024-03-22 15:54:52 -07:00
Jeff McCune
c7cc661018 (#71) Refactor Zitadel components for BuildPlan
❯ holos render --cluster-name k2  ~/workspace/holos-run/holos/docs/examples/platforms/reference/clusters/accounts/iam/zitadel/...
3:04PM INF render.go:43 rendered prod-iam-postgres version=0.60.2 status=ok action=rendered name=prod-iam-postgres
3:04PM INF render.go:43 rendered prod-iam-postgres-certs version=0.60.2 status=ok action=rendered name=prod-iam-postgres-certs
3:04PM INF render.go:43 rendered prod-iam-zitadel version=0.60.2 status=ok action=rendered name=prod-iam-zitadel
2024-03-22 15:04:43 -07:00
Jeff McCune
09f39c02fe (#71) Refactor foundation/cloud/secrets components to BuildPlan 2024-03-22 13:50:34 -07:00
Jeff McCune
23c76a73e0 (#71) Refactor pgo components to BuildPlan 2024-03-22 13:29:38 -07:00
Jeff McCune
1cafe08237 (#71) Refactor prod-metal-ceph to use BuildPlan 2024-03-22 12:44:20 -07:00
Jeff McCune
45b07964ef (#71) Refactor the mesh collection to use BuildPlan
This patch refactors the example reference platform to use the new
BuildPlan API.

```
❯ holos render --cluster-name=k2 /home/jeff/workspace/holos-run/holos/docs/examples/platforms/reference/clusters/foundation/cloud/mesh/...
12:19PM INF render.go:43 rendered prod-mesh-cni version=0.60.2 status=ok action=rendered name=prod-mesh-cni
12:19PM INF render.go:43 rendered prod-mesh-gateway version=0.60.2 status=ok action=rendered name=prod-mesh-gateway
12:19PM INF render.go:43 rendered prod-mesh-httpbin version=0.60.2 status=ok action=rendered name=prod-mesh-httpbin
12:19PM INF render.go:43 rendered prod-mesh-ingress version=0.60.2 status=ok action=rendered name=prod-mesh-ingress
12:19PM INF render.go:43 rendered prod-mesh-istiod version=0.60.2 status=ok action=rendered name=prod-mesh-istiod
12:19PM INF render.go:43 rendered prod-mesh-istio-base version=0.60.2 status=ok action=rendered name=prod-mesh-istio-base
```
2024-03-22 12:44:20 -07:00
Jeff McCune
6cc4a57b62 (#72) BuildPlan DisallowUnknownFields
This patch disallows unknown fields from CUE.  The purpose is to fail
early if there is a typo in a nested field name and to speed up
refactoring the reference platform.

With this patch, refactoring the type definition of the Holos/CUE API is
a faster process:

 1. Change api/vX/*.go
 2. make gencue
 3. Render the reference platform
 4. Fix error with unknown fields
 5. Verify rendered output is the same as before

Closes: #72
2024-03-22 12:44:11 -07:00
47 changed files with 579 additions and 472 deletions

View File

@@ -19,9 +19,9 @@ type BuildPlanSpec struct {
}
type BuildPlanComponents struct {
HelmCharts []HelmChart `json:"helmCharts,omitempty" yaml:"helmCharts,omitempty"`
KubernetesObjects []KubernetesObjects `json:"kubernetesObjects,omitempty" yaml:"kubernetesObjects,omitempty"`
KustomizeBuilds []KustomizeBuild `json:"kustomizeBuilds,omitempty" yaml:"kustomizeBuilds,omitempty"`
HelmChartList []HelmChart `json:"helmChartList,omitempty" yaml:"helmChartList,omitempty"`
KubernetesObjectsList []KubernetesObjects `json:"kubernetesObjectsList,omitempty" yaml:"kubernetesObjectsList,omitempty"`
KustomizeBuildList []KustomizeBuild `json:"kustomizeBuildList,omitempty" yaml:"kustomizeBuildList,omitempty"`
}
func (bp *BuildPlan) Validate() error {

View File

@@ -11,7 +11,7 @@ metadata: name: "jeff"
-- foo/bar/bar.cue --
package holos
spec: components: KubernetesObjects: [
spec: components: KubernetesObjectsList: [
#KubernetesObjects & {
apiObjectMap: foo: bar: "bf2bc7f9-9ba0-4f9e-9bd2-9a205627eb0b"
}
@@ -19,7 +19,7 @@ spec: components: KubernetesObjects: [
-- schema.cue --
package holos
cluster: string @tag(cluster, string)
_cluster: string @tag(cluster, string)
#KubernetesObjects: {
apiVersion: "holos.run/v1alpha1"

View File

@@ -1,6 +1,6 @@
# Want cue errors to show files and lines
! exec holos build .
stderr 'apiObjectMap.foo.bar: cannot convert non-concrete value string:'
stderr 'apiObjectMap.foo.bar: cannot convert incomplete value'
stderr '/component.cue:\d+:\d+$'
-- cue.mod --
@@ -8,9 +8,10 @@ package holos
-- component.cue --
package holos
_cluster: string @tag(cluster, string)
apiVersion: "holos.run/v1alpha1"
kind: "BuildPlan"
cluster: string @tag(cluster, string)
spec: components: KubernetesObjectsList: [{apiObjectMap: foo: bar: _baz}]
baz: string
spec: components: KubernetesObjects: [{apiObjectMap: foo: bar: baz}]
_baz: string

View File

@@ -10,9 +10,9 @@ package holos
apiVersion: "holos.run/v1alpha1"
kind: "BuildPlan"
spec: components: KubernetesObjects: [{apiObjectMap: #APIObjects.apiObjectMap}]
spec: components: KubernetesObjectsList: [{apiObjectMap: #APIObjects.apiObjectMap}]
cluster: string @tag(cluster, string)
_cluster: string @tag(cluster, string)
#SecretStore: {
kind: string

View File

@@ -11,9 +11,9 @@ package holos
apiVersion: "holos.run/v1alpha1"
kind: "BuildPlan"
spec: components: HelmCharts: [{apiObjectMap: #APIObjects.apiObjectMap}]
spec: components: HelmChartList: [{apiObjectMap: #APIObjects.apiObjectMap}]
cluster: string @tag(cluster, string)
_cluster: string @tag(cluster, string)
#SecretStore: {
kind: string

View File

@@ -9,9 +9,9 @@ package holos
apiVersion: "holos.run/v1alpha1"
kind: "BuildPlan"
spec: components: HelmCharts: [_HelmChart]
spec: components: HelmChartList: [_HelmChart]
cluster: string @tag(cluster, string)
_cluster: string @tag(cluster, string)
_HelmChart: {
apiVersion: "holos.run/v1alpha1"

View File

@@ -9,11 +9,11 @@ package holos
-- component.cue --
package holos
cluster: string @tag(cluster, string)
_cluster: string @tag(cluster, string)
apiVersion: "holos.run/v1alpha1"
kind: "BuildPlan"
spec: components: KustomizeBuilds: [{metadata: name: "kstest"}]
spec: components: KustomizeBuildList: [{metadata: name: "kstest"}]
-- kustomization.yaml --
apiVersion: kustomize.config.k8s.io/v1beta1

View File

@@ -0,0 +1,14 @@
# https://github.com/holos-run/holos/issues/72
# Want holos to fail on unknown fields to catch typos and aid refactors
! exec holos build .
stderr 'unknown field \\"TypoKubernetesObjectsList\\"'
-- cue.mod --
package holos
-- component.cue --
package holos
_cluster: string @tag(cluster, string)
apiVersion: "holos.run/v1alpha1"
kind: "BuildPlan"
spec: components: TypoKubernetesObjectsList: []

View File

@@ -1,3 +1 @@
package v1alpha1
#HolosComponent: metadata: name: string

View File

@@ -24,6 +24,8 @@ let DependsOn = {[Name=_]: name: string & Name}
metadata: name: string
#namelen: len(metadata.name) & >=1
let Name = metadata.name
// TODO: ksContent needs to be component scoped, not instance scoped.
ksContent: yaml.Marshal(#Kustomization & {
_dependsOn: DEPENDS_ON
metadata: name: Name
@@ -47,7 +49,7 @@ let DependsOn = {[Name=_]: name: string & Name}
// Holos component types.
#HelmChart: #HolosComponent & h.#HelmChart & {
_values: _
_values: {...}
_kustomizeFiles: #KustomizeFiles
// Render the values to yaml for holos to provide to helm.
@@ -57,6 +59,11 @@ let DependsOn = {[Name=_]: name: string & Name}
resourcesFile: h.#ResourcesFile
// kustomizeFiles represents the files in a kustomize directory tree.
kustomizeFiles: _kustomizeFiles.Files
chart: h.#Chart & {
name: string
release: string | *name
}
}
#KubernetesObjects: #HolosComponent & h.#KubernetesObjects
#KustomizeBuild: #HolosComponent & h.#KustomizeBuild
@@ -93,10 +100,6 @@ let DependsOn = {[Name=_]: name: string & Name}
}
}
// #KustomizeTree represents a kustomize build.
#KustomizeFiles: {
}
// #Kustomize represents the kustomize post processor.
#Kustomize: kc.#Kustomization & {
_patches: {[_]: kc.#Patch}
@@ -109,3 +112,6 @@ let DependsOn = {[Name=_]: name: string & Name}
patches: [for v in _patches {v}]
}
}
// So components don't need to import the package.
#Patch: kc.#Patch

View File

@@ -20,7 +20,17 @@ let SecretNames = {
},
]
#KubernetesObjects & {
spec: components: KubernetesObjectsList: [
#KubernetesObjects & {
metadata: name: "prod-iam-postgres-certs"
_dependsOn: "prod-secrets-stores": _
apiObjectMap: OBJECTS.apiObjectMap
},
]
let OBJECTS = #APIObjects & {
apiObjects: {
for s in SecretNames {
ExternalSecret: "\(s.name)": _

View File

@@ -33,7 +33,17 @@ let RestoreOptions = []
},
]
#KubernetesObjects & {
spec: components: KubernetesObjectsList: [
#KubernetesObjects & {
metadata: name: "prod-iam-postgres"
_dependsOn: "prod-secrets-namespaces": _
_dependsOn: "prod-iam-postgres-certs": _
apiObjectMap: OBJECTS.apiObjectMap
},
]
let OBJECTS = #APIObjects & {
apiObjects: {
ExternalSecret: "\(S3Secret)": _
PostgresCluster: db: #PostgresCluster & HighlyAvailable & {

View File

@@ -1,5 +1,6 @@
package holos
#InstancePrefix: "prod-iam"
#TargetNamespace: #InstancePrefix + "-zitadel"
// _DBName is the database name used across multiple holos components in this project

View File

@@ -4,50 +4,30 @@ import "encoding/yaml"
let Name = "zitadel"
#InputKeys: component: Name
#DependsOn: postgres: _
// Upstream helm chart doesn't specify the namespace field for all resources.
#Kustomization: spec: {
targetNamespace: #TargetNamespace
wait: false
}
spec: components: HelmChartList: [
#HelmChart & {
metadata: name: "\(#InstancePrefix)-zitadel"
if #IsPrimaryCluster == true {
#Kustomization: spec: healthChecks: [
{
apiVersion: "apps/v1"
kind: "Deployment"
name: Name
namespace: #TargetNamespace
},
{
apiVersion: "batch/v1"
kind: "Job"
name: "\(Name)-init"
namespace: #TargetNamespace
},
{
apiVersion: "batch/v1"
kind: "Job"
name: "\(Name)-setup"
namespace: #TargetNamespace
},
]
}
_dependsOn: "prod-secrets-stores": _
_dependsOn: "\(#InstancePrefix)-postgres": _
#HelmChart & {
namespace: #TargetNamespace
enableHooks: true
chart: {
name: Name
version: "7.9.0"
repository: {
name: Name
url: "https://charts.zitadel.com"
namespace: #TargetNamespace
enableHooks: true
chart: {
name: Name
version: "7.9.0"
repository: {
name: Name
url: "https://charts.zitadel.com"
}
}
}
values: #Values
_values: #Values
apiObjectMap: OBJECTS.apiObjectMap
},
]
let OBJECTS = #APIObjects & {
apiObjects: {
ExternalSecret: "zitadel-masterkey": _
VirtualService: "\(Name)": {
@@ -97,8 +77,7 @@ let CAPatch = #Patch & {
patch: yaml.Marshal(DatabaseCACertPatch)
}
// TODO: Replace with #Kustomize & { _patches: foo: {} }
#KustomizePatches: {
#Kustomize: _patches: {
mesh: {
target: {
group: "apps"
@@ -163,3 +142,32 @@ let CAPatch = #Patch & {
}
let DisableFluxPatch = [{op: "replace", path: "/metadata/annotations/kustomize.toolkit.fluxcd.io~1reconcile", value: "disabled"}]
// Upstream helm chart doesn't specify the namespace field for all resources.
#Kustomization: spec: {
targetNamespace: #TargetNamespace
wait: false
}
if #IsPrimaryCluster == true {
#Kustomization: spec: healthChecks: [
{
apiVersion: "apps/v1"
kind: "Deployment"
name: Name
namespace: #TargetNamespace
},
{
apiVersion: "batch/v1"
kind: "Job"
name: "\(Name)-init"
namespace: #TargetNamespace
},
{
apiVersion: "batch/v1"
kind: "Job"
name: "\(Name)-setup"
namespace: #TargetNamespace
},
]
}

View File

@@ -9,7 +9,7 @@ let GitHubConfigSecret = "controller-manager"
// Just sync the external secret, don't configure the scale set
// Work around https://github.com/actions/actions-runner-controller/issues/3351
if #IsPrimaryCluster == false {
spec: components: KubernetesObjects: [
spec: components: KubernetesObjectsList: [
#KubernetesObjects & {
metadata: name: "prod-github-arc-runner"
_dependsOn: "prod-secrets-namespaces": _
@@ -23,7 +23,7 @@ if #IsPrimaryCluster == false {
// Put the scale set on the primary cluster.
if #IsPrimaryCluster == true {
spec: components: HelmCharts: [
spec: components: HelmChartList: [
#HelmChart & {
_dependsOn: "prod-secrets-namespaces": _
metadata: name: "prod-github-arc-runner"

View File

@@ -3,7 +3,7 @@ package holos
#TargetNamespace: #ARCSystemNamespace
#InputKeys: component: "arc-system"
spec: components: HelmCharts: [
spec: components: HelmChartList: [
#HelmChart & {
metadata: name: "prod-github-arc-system"

View File

@@ -2,7 +2,7 @@ package holos
import "list"
spec: components: KubernetesObjects: [
spec: components: KubernetesObjectsList: [
#KubernetesObjects & {
metadata: name: "prod-secrets-namespaces"
apiObjectMap: (#APIObjects & {

View File

@@ -1,17 +1,19 @@
package holos
#InputKeys: component: "istio-base"
#TargetNamespace: "istio-system"
spec: components: HelmChartList: [
#HelmChart & {
_dependsOn: "prod-secrets-namespaces": _
#HelmChart & {
namespace: #TargetNamespace
chart: {
name: "base"
version: "1.20.3"
repository: {
name: "istio"
url: "https://istio-release.storage.googleapis.com/charts"
metadata: name: "prod-mesh-istio-base"
namespace: "istio-system"
chart: {
name: "base"
version: "1.20.3"
repository: {
name: "istio"
url: "https://istio-release.storage.googleapis.com/charts"
}
}
}
values: #IstioValues
}
_values: #IstioValues
},
]

View File

@@ -1,10 +1,13 @@
package holos
#InputKeys: component: "cni"
#TargetNamespace: "kube-system"
spec: components: HelmChartList: [
#HelmChart & {
_dependsOn: "prod-secrets-namespaces": _
_dependsOn: "prod-mesh-istio-base": _
#HelmChart & {
namespace: #TargetNamespace
chart: name: "cni"
values: #IstioValues
}
_values: #IstioValues
metadata: name: "\(#InstancePrefix)-\(chart.name)"
namespace: "kube-system"
chart: name: "cni"
},
]

View File

@@ -4,15 +4,23 @@ import "list"
// The primary istio Gateway, named default
let Name = "gateway"
#InputKeys: component: Name
#TargetNamespace: "istio-ingress"
#DependsOn: _IngressGateway
let LoginCert = #PlatformCerts.login
#KubernetesObjects & {
spec: components: KubernetesObjectsList: [
#KubernetesObjects & {
_dependsOn: "prod-secrets-namespaces": _
_dependsOn: "prod-mesh-istio-base": _
_dependsOn: "prod-mesh-ingress": _
metadata: name: "\(#InstancePrefix)-\(Name)"
apiObjectMap: OBJECTS.apiObjectMap
},
]
let OBJECTS = #APIObjects & {
apiObjects: {
ExternalSecret: login: #ExternalSecret & {
_name: "login"

View File

@@ -1,8 +1,13 @@
package holos
let Name = "httpbin"
let ComponentName = "\(#InstancePrefix)-\(Name)"
let SecretName = #InputKeys.cluster + "-" + Name
let MatchLabels = {app: Name} & #SelectorLabels
let MatchLabels = {
app: Name
"app.kubernetes.io/instance": ComponentName
}
let Metadata = {
name: Name
namespace: #TargetNamespace
@@ -12,11 +17,22 @@ let Metadata = {
#InputKeys: component: Name
#TargetNamespace: "istio-ingress"
#DependsOn: _IngressGateway
let Cert = #PlatformCerts[SecretName]
#KubernetesObjects & {
spec: components: KubernetesObjectsList: [
#KubernetesObjects & {
_dependsOn: "prod-secrets-namespaces": _
_dependsOn: "\(#InstancePrefix)-istio-base": _
_dependsOn: "\(#InstancePrefix)-ingress": _
metadata: name: ComponentName
apiObjectMap: OBJECTS.apiObjectMap
},
]
let OBJECTS = #APIObjects & {
apiObjects: {
ExternalSecret: "\(Cert.spec.secretName)": _
Deployment: httpbin: #Deployment & {
@@ -24,7 +40,6 @@ let Cert = #PlatformCerts[SecretName]
spec: selector: matchLabels: MatchLabels
spec: template: {
metadata: labels: MatchLabels
metadata: labels: #CommonLabels
metadata: labels: #IstioSidecar
spec: securityContext: seccompProfile: type: "RuntimeDefault"
spec: containers: [{
@@ -54,7 +69,7 @@ let Cert = #PlatformCerts[SecretName]
spec: servers: [
{
hosts: [for host in Cert.spec.dnsNames {"\(#TargetNamespace)/\(host)"}]
port: name: "https-\(#InstanceName)"
port: name: "https-\(ComponentName)"
port: number: 443
port: protocol: "HTTPS"
tls: credentialName: Cert.spec.secretName

View File

@@ -2,50 +2,58 @@ package holos
import "encoding/json"
#InputKeys: component: "ingress"
#TargetNamespace: "istio-ingress"
#DependsOn: _IstioD
let ComponentName = "\(#InstancePrefix)-ingress"
#HelmChart & {
chart: name: "gateway"
namespace: #TargetNamespace
values: #GatewayValues & {
// This component expects the load balancer to send the PROXY protocol header.
// Refer to: https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.2/guide/service/annotations/#proxy-protocol-v2
podAnnotations: "proxy.istio.io/config": json.Marshal(_ProxyProtocol)
// TODO This configuration is specific to the OIS Metal NLB, refactor it out to the metal collection.
service: {
type: "NodePort"
annotations: "service.beta.kubernetes.io/aws-load-balancer-proxy-protocol": "*"
externalTrafficPolicy: "Local"
// Add 30000 to the port to get the Nodeport
ports: [
{
name: "status-port"
port: 15021
protocol: "TCP"
targetPort: 15021
nodePort: 30021
},
{
name: "http2"
port: 80
protocol: "TCP"
targetPort: 80
nodePort: 30080
},
{
name: "https"
port: 443
protocol: "TCP"
targetPort: 443
nodePort: 30443
},
]
#TargetNamespace: "istio-ingress"
spec: components: HelmChartList: [
#HelmChart & {
_dependsOn: "prod-secrets-namespaces": _
_dependsOn: "\(#InstancePrefix)-istio-base": _
_dependsOn: "\(#InstancePrefix)-istiod": _
metadata: name: ComponentName
chart: name: "gateway"
namespace: #TargetNamespace
_values: #GatewayValues & {
// This component expects the load balancer to send the PROXY protocol header.
// Refer to: https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.2/guide/service/annotations/#proxy-protocol-v2
podAnnotations: "proxy.istio.io/config": json.Marshal(_ProxyProtocol)
// TODO This configuration is specific to the OIS Metal NLB, refactor it out to the metal collection.
service: {
type: "NodePort"
annotations: "service.beta.kubernetes.io/aws-load-balancer-proxy-protocol": "*"
externalTrafficPolicy: "Local"
// Add 30000 to the port to get the Nodeport
ports: [
{
name: "status-port"
port: 15021
protocol: "TCP"
targetPort: 15021
nodePort: 30021
},
{
name: "http2"
port: 80
protocol: "TCP"
targetPort: 80
nodePort: 30080
},
{
name: "https"
port: 443
protocol: "TCP"
targetPort: 443
nodePort: 30443
},
]
}
}
}
apiObjects: _APIObjects
}
apiObjectMap: OBJECTS.apiObjectMap
},
]
_ProxyProtocol: gatewayTopology: proxyProtocol: {}
@@ -60,36 +68,82 @@ let RedirectMetaName = {
namespace: #TargetNamespace
}
// https-redirect
_APIObjects: {
Gateway: {
"\(RedirectMetaName.name)": #Gateway & {
metadata: RedirectMetaName
spec: selector: GatewayLabels
spec: servers: [{
port: {
number: 80
name: "http2"
protocol: "HTTP2"
}
hosts: ["*"]
// handled by the VirtualService
tls: httpsRedirect: false
}]
let OBJECTS = #APIObjects & {
apiObjects: {
Gateway: {
"\(RedirectMetaName.name)": #Gateway & {
metadata: RedirectMetaName
spec: selector: GatewayLabels
spec: servers: [{
port: {
number: 80
name: "http2"
protocol: "HTTP2"
}
hosts: ["*"]
// handled by the VirtualService
tls: httpsRedirect: false
}]
}
}
}
VirtualService: {
"\(RedirectMetaName.name)": #VirtualService & {
metadata: RedirectMetaName
spec: hosts: ["*"]
spec: gateways: [RedirectMetaName.name]
spec: http: [{
match: [{withoutHeaders: ":path": prefix: "/.well-known/acme-challenge/"}]
redirect: {
scheme: "https"
redirectCode: 302
VirtualService: {
"\(RedirectMetaName.name)": #VirtualService & {
metadata: RedirectMetaName
spec: hosts: ["*"]
spec: gateways: [RedirectMetaName.name]
spec: http: [{
match: [{withoutHeaders: ":path": prefix: "/.well-known/acme-challenge/"}]
redirect: {
scheme: "https"
redirectCode: 302
}
}]
}
}
Deployment: {
loopback: #Deployment & {
_description: LoopbackDescription
metadata: LoopbackMetaName
spec: {
selector: matchLabels: LoopbackLabels
template: {
metadata: {
annotations: "inject.istio.io/templates": "gateway"
annotations: #Description & {
_Description: LoopbackDescription
}
labels: LoopbackLabels & {"sidecar.istio.io/inject": "true"}
}
spec: {
serviceAccountName: "istio-ingressgateway"
// Allow binding to all ports (such as 80 and 443)
securityContext: {
runAsNonRoot: true
seccompProfile: type: "RuntimeDefault"
sysctls: [{name: "net.ipv4.ip_unprivileged_port_start", value: "0"}]
}
containers: [{
name: "istio-proxy"
image: "auto" // Managed by istiod
securityContext: {
allowPrivilegeEscalation: false
capabilities: drop: ["ALL"]
runAsUser: 1337
runAsGroup: 1337
}
}]
}
}
}
}]
}
}
Service: {
loopback: #Service & {
_description: LoopbackDescription
metadata: LoopbackMetaName
spec: selector: LoopbackLabels
spec: ports: [{port: 80, name: "http"}, {port: 443, name: "https"}]
}
}
}
}
@@ -104,52 +158,3 @@ let LoopbackMetaName = {
name: LoopbackName
namespace: #TargetNamespace
}
// istio-ingressgateway-loopback
_APIObjects: {
Deployment: {
loopback: #Deployment & {
_description: LoopbackDescription
metadata: LoopbackMetaName
spec: {
selector: matchLabels: LoopbackLabels
template: {
metadata: {
annotations: "inject.istio.io/templates": "gateway"
annotations: #Description & {
_Description: LoopbackDescription
}
labels: LoopbackLabels & {"sidecar.istio.io/inject": "true"}
}
spec: {
serviceAccountName: "istio-ingressgateway"
// Allow binding to all ports (such as 80 and 443)
securityContext: {
runAsNonRoot: true
seccompProfile: type: "RuntimeDefault"
sysctls: [{name: "net.ipv4.ip_unprivileged_port_start", value: "0"}]
}
containers: [{
name: "istio-proxy"
image: "auto" // Managed by istiod
securityContext: {
allowPrivilegeEscalation: false
capabilities: drop: ["ALL"]
runAsUser: 1337
runAsGroup: 1337
}
}]
}
}
}
}
}
Service: {
loopback: #Service & {
_description: LoopbackDescription
metadata: LoopbackMetaName
spec: selector: LoopbackLabels
spec: ports: [{port: 80, name: "http"}, {port: 443, name: "https"}]
}
}
}

View File

@@ -1,7 +1,5 @@
package holos
#DependsOn: _IstioBase
#HelmChart: {
chart: {
version: "1.20.3"

View File

@@ -5,22 +5,28 @@ import "encoding/yaml"
#InputKeys: component: "istiod"
#TargetNamespace: "istio-system"
#HelmChart & {
namespace: #TargetNamespace
chart: {
name: "istiod"
}
values: #IstioValues & {
pilot: {
// The istio meshconfig ConfigMap is handled in the holos component instead of
// the upstream chart so extension providers can be collected from holos data.
configMap: false
// Set to `type: RuntimeDefault` to use the default profile if available.
seccompProfile: type: "RuntimeDefault"
spec: components: HelmChartList: [
#HelmChart & {
_dependsOn: "prod-secrets-namespaces": _
_dependsOn: "\(#InstancePrefix)-istio-base": _
metadata: name: "prod-mesh-istiod"
chart: name: "istiod"
namespace: #TargetNamespace
_values: #IstioValues & {
pilot: {
// The istio meshconfig ConfigMap is handled in the holos component instead of
// the upstream chart so extension providers can be collected from holos data.
configMap: false
// Set to `type: RuntimeDefault` to use the default profile if available.
seccompProfile: type: "RuntimeDefault"
}
}
}
apiObjects: ConfigMap: istio: #IstioConfigMap
}
apiObjectMap: OBJECTS.apiObjectMap
},
]
let OBJECTS = #APIObjects & {apiObjects: ConfigMap: istio: #IstioConfigMap}
#IstioConfigMap: #ConfigMap & {
metadata: {

View File

@@ -1,14 +1,3 @@
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
_CertManager: CertManager: name: "\(#InstancePrefix)-certmanager"
_Namespaces: Namespaces: name: "\(#StageName)-secrets-namespaces"
_IstioBase: IstioBase: name: "\(#InstancePrefix)-istio-base"
_IstioD: IstioD: name: "\(#InstancePrefix)-istiod"
_IngressGateway: IngressGateway: name: "\(#InstancePrefix)-ingress"
#InstancePrefix: "prod-mesh"

View File

@@ -1,6 +1,10 @@
package holos
#DependsOn: Namespaces: name: "prod-secrets-namespaces"
#DependsOn: CRDS: name: "\(#InstancePrefix)-crds"
#InputKeys: component: "controller"
{} & #KustomizeBuild
spec: components: KustomizeBuildList: [
#KustomizeBuild & {
_dependsOn: "prod-secrets-namespaces": _
_dependsOn: "prod-pgo-crds": _
metadata: name: "prod-pgo-controller"
},
]

View File

@@ -1,6 +1,8 @@
package holos
// Refer to https://github.com/CrunchyData/postgres-operator-examples/tree/main/kustomize/install/crd
#InputKeys: component: "crds"
{} & #KustomizeBuild
spec: components: KustomizeBuildList: [
#KustomizeBuild & {
metadata: name: "prod-pgo-crds"
},
]

View File

@@ -2,8 +2,6 @@ package holos
import "encoding/json"
#DependsOn: _ESO
#InputKeys: {
project: "secrets"
component: "eso-creds-refresher"
@@ -11,8 +9,17 @@ import "encoding/json"
#TargetNamespace: #CredsRefresher.namespace
// output kubernetes api objects for holos
#KubernetesObjects & {
spec: components: KubernetesObjectsList: [
#KubernetesObjects & {
_dependsOn: "prod-secrets-namespaces": _
_dependsOn: "prod-secrets-eso": _
metadata: name: "prod-secrets-eso-creds-refresher"
apiObjectMap: OBJECTS.apiObjectMap
},
]
let OBJECTS = #APIObjects & {
apiObjects: {
for obj in #CredsRefresherService.objects {
let Kind = obj.kind

View File

@@ -3,26 +3,22 @@ package holos
// Manages the External Secrets Operator from the official upstream Helm chart.
#TargetNamespace: "external-secrets"
#InputKeys: component: "eso"
#InputKeys: {
project: "secrets"
service: "eso"
}
#Kustomization: spec: targetNamespace: #TargetNamespace
#DependsOn: Namespaces: name: #InstancePrefix + "-namespaces"
#HelmChart & {
values: installCrds: true
namespace: #TargetNamespace
chart: {
name: "external-secrets"
version: "0.9.12"
repository: {
name: "external-secrets"
url: "https://charts.external-secrets.io"
spec: components: HelmChartList: [
#HelmChart & {
_dependsOn: "prod-secrets-namespaces": _
metadata: name: "prod-secrets-eso"
namespace: #TargetNamespace
chart: {
name: "external-secrets"
version: "0.9.12"
repository: {
name: "external-secrets"
url: "https://charts.external-secrets.io"
}
}
}
}
_values: installCrds: true
},
]

View File

@@ -2,11 +2,3 @@ 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

@@ -2,27 +2,19 @@ package holos
import "list"
#DependsOn: _ESOCreds
#TargetNamespace: "default"
#InputKeys: {
project: "secrets"
component: "stores"
}
spec: components: KubernetesObjectsList: [
#KubernetesObjects & {
_dependsOn: "prod-secrets-namespaces": _
_dependsOn: "prod-secrets-eso-creds-refresher": _
// #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
metadata: name: "prod-secrets-stores"
apiObjectMap: OBJECTS.apiObjectMap
},
]
objects: [
#SecretStore & {
_namespace: _ns.name
},
]
}
#KubernetesObjects & {
let OBJECTS = #APIObjects & {
apiObjects: {
for ns in #PlatformNamespaces {
for obj in (#PlatformNamespaceObjects & {_ns: ns}).objects {
@@ -41,3 +33,14 @@ import "list"
}
}
}
// #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
},
]
}

View File

@@ -4,14 +4,16 @@ package holos
#TargetNamespace: "holos-system"
#InputKeys: {
project: "secrets"
component: "validate"
}
spec: components: KubernetesObjectsList: [
#KubernetesObjects & {
_dependsOn: "prod-secrets-stores": _
#DependsOn: _ESO
metadata: name: "prod-secrets-validate"
apiObjectMap: OBJECTS.apiObjectMap
},
]
#KubernetesObjects & {
let OBJECTS = #APIObjects & {
apiObjects: {
ExternalSecret: validate: #ExternalSecret & {
_name: "validate"

View File

@@ -6,29 +6,30 @@ package holos
#SecretName: "\(#ClusterName)-ceph-csi-rbd"
#InputKeys: {
project: "metal"
service: "ceph"
component: "ceph"
}
#Kustomization: spec: targetNamespace: "ceph-system"
#Kustomization: spec: targetNamespace: #TargetNamespace
#DependsOn: Namespaces: name: "\(#StageName)-secrets-namespaces"
spec: components: HelmChartList: [
#HelmChart & {
_dependsOn: "prod-secrets-namespaces": _
#HelmChart & {
namespace: #TargetNamespace
chart: {
name: "ceph-csi-rbd"
version: "3.10.2"
repository: {
name: "ceph-csi"
url: "https://ceph.github.io/csi-charts"
metadata: name: "prod-metal-ceph"
namespace: #TargetNamespace
chart: {
name: "ceph-csi-rbd"
version: "3.10.2"
repository: {
name: "ceph-csi"
url: "https://ceph.github.io/csi-charts"
}
}
}
_values: #ChartValues
apiObjectMap: OBJECTS.apiObjectMap
},
]
let OBJECTS = #APIObjects & {
apiObjects: {
ExternalSecret: "\(#SecretName)": #ExternalSecret & {
_name: #SecretName
}
ExternalSecret: "\(#SecretName)": _
}
}

View File

@@ -5,46 +5,31 @@ import "encoding/yaml"
import "list"
let Name = "vault"
#InputKeys: component: Name
#InputKeys: project: "core"
#TargetNamespace: "\(#InstancePrefix)-\(Name)"
#TargetNamespace: "prod-core-\(Name)"
let Vault = #OptionalServices[Name]
if Vault.enabled && list.Contains(Vault.clusterNames, #ClusterName) {
#HelmChart & {
namespace: #TargetNamespace
chart: {
name: Name
version: "0.25.0"
repository: {
name: "hashicorp"
url: "https://helm.releases.hashicorp.com"
}
}
values: #Values
#Kustomization: spec: wait: true
apiObjects: {
ExternalSecret: "gcpkms-creds": _
ExternalSecret: "vault-server-cert": _
VirtualService: "\(Name)": {
metadata: name: Name
metadata: namespace: #TargetNamespace
spec: hosts: [for cert in Vault.certs {cert.spec.commonName}]
spec: gateways: ["istio-ingress/\(Name)"]
spec: http: [
{
route: [
{
destination: host: "\(Name)-active"
destination: port: number: 8200
},
]
},
]
if Vault.enabled && list.Contains(Vault.clusterNames, #ClusterName) {
spec: components: HelmChartList: [
#HelmChart & {
metadata: name: "prod-core-\(Name)"
namespace: #TargetNamespace
chart: {
name: Name
version: "0.25.0"
repository: {
name: "hashicorp"
url: "https://helm.releases.hashicorp.com"
}
}
}
}
_values: #Values
apiObjectMap: OBJECTS.apiObjectMap
},
]
#Kustomize: {
patches: [
@@ -59,17 +44,40 @@ if Vault.enabled && list.Contains(Vault.clusterNames, #ClusterName) {
},
]
}
let EnvPatch = [
{
op: "test"
path: "/spec/template/spec/containers/0/env/4/name"
value: "VAULT_ADDR"
},
{
op: "replace"
path: "/spec/template/spec/containers/0/env/4/value"
value: "http://$(VAULT_K8S_POD_NAME):8200"
},
]
}
let EnvPatch = [
{
op: "test"
path: "/spec/template/spec/containers/0/env/4/name"
value: "VAULT_ADDR"
},
{
op: "replace"
path: "/spec/template/spec/containers/0/env/4/value"
value: "http://$(VAULT_K8S_POD_NAME):8200"
},
]
let OBJECTS = #APIObjects & {
apiObjects: {
ExternalSecret: "gcpkms-creds": _
ExternalSecret: "vault-server-cert": _
VirtualService: "\(Name)": {
metadata: name: Name
metadata: namespace: #TargetNamespace
spec: hosts: [for cert in Vault.certs {cert.spec.commonName}]
spec: gateways: ["istio-ingress/\(Name)"]
spec: http: [
{
route: [
{
destination: host: "\(Name)-active"
destination: port: number: 8200
},
]
},
]
}
}
}

View File

@@ -2,9 +2,3 @@ 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

@@ -8,13 +8,27 @@ package holos
// Refer to [Using Cert Manager to Deploy TLS for Postgres on Kubernetes](https://www.crunchydata.com/blog/using-cert-manager-to-deploy-tls-for-postgres-on-kubernetes)
#TargetNamespace: "prod-iam-zitadel"
#InputKeys: component: "postgres-certs"
let SelfSigned = "\(_DBName)-selfsigned"
let RootCA = "\(_DBName)-root-ca"
let DBName = "zitadel"
let SelfSigned = "\(DBName)-selfsigned"
let RootCA = "\(DBName)-root-ca"
let Orgs = ["Database"]
#KubernetesObjects & {
#Kustomization: spec: wait: true
spec: components: KubernetesObjectsList: [
#KubernetesObjects & {
metadata: name: "prod-iam-postgres-certs"
_dependsOn: "prod-secrets-namespaces": _
apiObjectMap: OBJECTS.apiObjectMap
},
]
let OBJECTS = #APIObjects & {
apiObjects: {
// Put everything in the target namespace.
[_]: {
@@ -51,10 +65,10 @@ let Orgs = ["Database"]
subject: organizations: Orgs
}
}
"\(_DBName)-primary-tls": #DatabaseCert & {
"\(DBName)-primary-tls": #DatabaseCert & {
// PGO managed name is "<cluster name>-cluster-cert" e.g. zitadel-cluster-cert
spec: {
commonName: "\(_DBName)-primary"
commonName: "\(DBName)-primary"
dnsNames: [
commonName,
"\(commonName).\(#TargetNamespace)",
@@ -66,16 +80,16 @@ let Orgs = ["Database"]
usages: ["digital signature", "key encipherment"]
}
}
"\(_DBName)-repl-tls": #DatabaseCert & {
"\(DBName)-repl-tls": #DatabaseCert & {
spec: {
commonName: "_crunchyrepl"
dnsNames: [commonName]
usages: ["digital signature", "key encipherment"]
}
}
"\(_DBName)-client-tls": #DatabaseCert & {
"\(DBName)-client-tls": #DatabaseCert & {
spec: {
commonName: "\(_DBName)-client"
commonName: "\(DBName)-client"
dnsNames: [commonName]
usages: ["digital signature", "key encipherment"]
}

View File

@@ -1,6 +1 @@
package holos
#TargetNamespace: #InstancePrefix + "-zitadel"
// _DBName is the database name used across multiple holos components in this project
_DBName: "zitadel"

View File

@@ -1,20 +1,35 @@
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
#Kustomization: spec: wait: true
#KubernetesObjects & {
spec: components: KubernetesObjectsList: [
#KubernetesObjects & {
metadata: name: "\(#InstancePrefix)-certificates"
_dependsOn: "prod-secrets-namespaces": _
_dependsOn: "prod-mesh-letsencrypt": _
apiObjectMap: OBJECTS.apiObjectMap
},
]
let Vault = #OptionalServices.vault
let OBJECTS = #APIObjects & {
apiObjects: {
for k, obj in #PlatformCerts {
"\(obj.kind)": {
"\(obj.metadata.namespace)/\(obj.metadata.name)": obj
}
}
if Vault.enabled {
for k, obj in Vault.certs {
"\(obj.kind)": "\(obj.metadata.name)": obj
}
}
}
}

View File

@@ -1,13 +0,0 @@
package holos
let Vault = #OptionalServices.vault
if Vault.enabled {
#KubernetesObjects & {
apiObjects: {
for k, obj in Vault.certs {
"\(obj.kind)": "\(obj.metadata.name)": obj
}
}
}
}

View File

@@ -4,28 +4,28 @@ package holos
#TargetNamespace: "cert-manager"
#InputKeys: {
component: "certmanager"
service: "cert-manager"
}
spec: components: HelmChartList: [
#HelmChart & {
metadata: name: "\(#InstancePrefix)-certmanager"
#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"
_dependsOn: "prod-secrets-namespaces": _
namespace: #TargetNamespace
_values: #Values & {
installCRDs: true
startupapicheck: enabled: false
// Must not use kube-system on gke autopilot. GKE Warden authz blocks access.
global: leaderElection: 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: {

View File

@@ -1,7 +1,6 @@
package holos
// Lets Encrypt certificate issuers for public tls certs
#InputKeys: component: "letsencrypt"
#TargetNamespace: "cert-manager"
let Name = "letsencrypt"
@@ -9,10 +8,17 @@ let Name = "letsencrypt"
// The cloudflare api token is platform scoped, not cluster scoped.
#SecretName: "cloudflare-api-token-secret"
// Depends on cert manager
#DependsOn: _CertManager
spec: components: KubernetesObjectsList: [
#KubernetesObjects & {
metadata: name: "\(#InstancePrefix)-letsencrypt"
#KubernetesObjects & {
_dependsOn: "prod-secrets-namespaces": _
_dependsOn: "\(#InstancePrefix)-certmanager": _
apiObjectMap: OBJECTS.apiObjectMap
},
]
let OBJECTS = #APIObjects & {
apiObjects: {
ClusterIssuer: {
letsencrypt: #ClusterIssuer & {

View File

@@ -1,13 +1,3 @@
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"
#InstancePrefix: "prod-mesh"

View File

@@ -8,10 +8,15 @@ package holos
// - Namespace
// - ServiceAccount eso-reader, eso-writer
// No flux kustomization
ksObjects: []
spec: components: KubernetesObjectsList: [
#KubernetesObjects & {
metadata: name: "prod-secrets-eso-creds-refresher"
#KubernetesObjects & {
apiObjectMap: OBJECTS.apiObjectMap
},
]
let OBJECTS = #APIObjects & {
apiObjects: {
let role = #CredsRefresherIAM.role
let binding = #CredsRefresherIAM.binding
@@ -35,12 +40,6 @@ ksObjects: []
}
}
#InputKeys: {
cluster: "provisioner"
project: "secrets"
component: "eso-creds-refresher"
}
// #CredsRefresherIAM defines the rbac policy for the job that refreshes credentials used by eso SecretStore resources in clusters other than the provisioner cluster.
#CredsRefresherIAM: {
let _name = #CredsRefresher.name

View File

@@ -2,12 +2,15 @@ package holos
#TargetNamespace: "default"
#InputKeys: {
project: "secrets"
component: "namespaces"
}
spec: components: KubernetesObjectsList: [
#KubernetesObjects & {
metadata: name: "prod-secrets-namespaces"
#KubernetesObjects & {
apiObjectMap: OBJECTS.apiObjectMap
},
]
let OBJECTS = #APIObjects & {
apiObjects: {
// #ManagedNamespaces is the set of all namespaces across all clusters in the platform.
for nsName, ns in #ManagedNamespaces {

View File

@@ -54,7 +54,10 @@ _apiVersion: "holos.run/v1alpha1"
}
// Kubernetes API Objects
#Namespace: corev1.#Namespace
#Namespace: corev1.#Namespace & {
metadata: name: string
metadata: labels: "kubernetes.io/metadata.name": metadata.name
}
#ClusterRole: #ClusterObject & rbacv1.#ClusterRole
#ClusterRoleBinding: #ClusterObject & rbacv1.#ClusterRoleBinding

View File

@@ -4,7 +4,9 @@
package builder
import (
"bytes"
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
@@ -129,13 +131,22 @@ func (b *Builder) Run(ctx context.Context) (results []*v1alpha1.Result, err erro
if err := value.Validate(); err != nil {
return nil, wrapper.Wrap(fmt.Errorf("could not validate: %w", err))
}
log.DebugContext(ctx, "cue: decoding holos build plan")
if err := value.Decode(&buildPlan); err != nil {
return nil, wrapper.Wrap(fmt.Errorf("could not decode: %w", err))
// Hack to catch unknown fields https://github.com/holos-run/holos/issues/72
jsonBytes, err := value.MarshalJSON()
if err != nil {
return nil, wrapper.Wrap(fmt.Errorf("could not marshal cue instance %s: %w", instance.Dir, err))
}
decoder := json.NewDecoder(bytes.NewReader(jsonBytes))
decoder.DisallowUnknownFields()
err = decoder.Decode(&buildPlan)
if err != nil {
return nil, wrapper.Wrap(fmt.Errorf("invalid BuildPlan: %s: %w", instance.Dir, err))
}
if err := buildPlan.Validate(); err != nil {
return nil, wrapper.Wrap(fmt.Errorf("could not render: %w", err))
return nil, wrapper.Wrap(fmt.Errorf("could not validate %s: %w", instance.Dir, err))
}
if buildPlan.Spec.Disabled {
@@ -143,21 +154,22 @@ func (b *Builder) Run(ctx context.Context) (results []*v1alpha1.Result, err erro
continue
}
for _, component := range buildPlan.Spec.Components.KubernetesObjects {
// TODO: concurrent renders
for _, component := range buildPlan.Spec.Components.KubernetesObjectsList {
if result, err := component.Render(ctx, holos.PathComponent(instance.Dir)); err != nil {
return nil, wrapper.Wrap(fmt.Errorf("could not render: %w", err))
} else {
results = append(results, result)
}
}
for _, component := range buildPlan.Spec.Components.HelmCharts {
for _, component := range buildPlan.Spec.Components.HelmChartList {
if result, err := component.Render(ctx, holos.PathComponent(instance.Dir)); err != nil {
return nil, wrapper.Wrap(fmt.Errorf("could not render: %w", err))
} else {
results = append(results, result)
}
}
for _, component := range buildPlan.Spec.Components.KustomizeBuilds {
for _, component := range buildPlan.Spec.Components.KustomizeBuildList {
if result, err := component.Render(ctx, holos.PathComponent(instance.Dir)); err != nil {
return nil, wrapper.Wrap(fmt.Errorf("could not render: %w", err))
} else {

View File

@@ -1 +1 @@
1
3