Compare commits

...

38 Commits

Author SHA1 Message Date
Jeff McCune
d81e25c4e4 (#66) Project Certificates
Provisioner cluster:

This patch creates a Certificate resource in the provisioner for each
host associated with the project.  By default, one host is created for
each stage with the short hostname set to the project name.

A namespace is also created for each project for eso creds refresher to
manage service accounts for SecretStore resources in the workload
clusters.

Workload cluster:

For each env, plus one system namespace per stage:

 - Namespace per env
 - SecretStore per env
 - ExternalSecret per host in the env

Common names for the holos project, prod stage:

- holos.k1.ois.run
- holos.k2.ois.run
- holos.ois.run

Common names for the holos project, dev stage:

- holos.dev.k1.ois.run
- holos.dev.k2.ois.run
- holos.dev.ois.run
- holos.gary.k1.ois.run
- holos.gary.k2.ois.run
- holos.gary.ois.run
- holos.jeff.k1.ois.run
- holos.jeff.k2.ois.run
- holos.jeff.ois.run
- holos.nate.k1.ois.run
- holos.nate.k2.ois.run
- holos.nate.ois.run

Usage:

    holos render --cluster-name=provisioner \
      ~/workspace/holos-run/holos/docs/examples/platforms/reference/clusters/provisioner/projects/...
    holos render --cluster-name=k1 \
      ~/workspace/holos-run/holos/docs/examples/platforms/reference/clusters/workload/projects/...
    holos render --cluster-name=k2 \
      ~/workspace/holos-run/holos/docs/examples/platforms/reference/clusters/workload/projects/...
2024-03-27 20:54:51 -07:00
Jeff McCune
c4612ff5d2 (#64) Manage one system namespace per project
This patch introduces a new BuildPlan spec.components.resources
collection, which is a map version of
spec.components.kubernetesObjectsList.  The map version is much easier
to work with and produce in CUE than the list version.

The list version should be deprecated and removed prior to public
release.

The projects holos instance renders multiple holos components, each
containing kubernetes api objects defined directly in CUE.

<project>-system is intended for the ext auth proxy providers for all
stages.

<project>-namespaces is intended to create a namespace for each
environment in the project.

The intent is to expand the platform level definition of a project to
include the per-stage auth proxy and per-env role bindings.  Secret
Store and ESO creds refresher resources will also be defined by the
platform level definition of a project.
2024-03-26 12:23:01 -07:00
Jeff McCune
d70acbb47e ignore .vscode 2024-03-22 21:22:06 -07:00
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
Jeff McCune
31280acbae (#71) Add HelmChart BuildPlan support
This patch refactors the #HelmChart definition to a BuildPlan.HelmCharts,
which executes a collection of HelmCharts.  The same behavior is
preserved, helm template executes then a kustomize post processor
executes.

```
❯ holos render --cluster-name=k2 ~/workspace/holos-run/holos/docs/examples/platforms/reference/clusters/foundation/cloud/github/arc/... --log-level=debug
9:53PM DBG config.go:150 finalized config from flags version=0.60.1 state=finalized
9:53PM DBG builder.go:108 cue: building instances version=0.60.1
9:53PM DBG builder.go:95 cue: equivalent command: cue export --out yaml -t cluster=k2 ./platforms/reference/clusters/foundation/cloud/github/arc/... version=0.60.1
9:53PM DBG builder.go:100 cue: tags [cluster=k2] version=0.60.1
9:53PM DBG builder.go:122 cue: building instance version=0.60.1 dir=/home/jeff/workspace/holos-run/holos/docs/examples/platforms/reference/clusters/foundation/cloud/github/arc
9:53PM DBG builder.go:127 cue: validating instance version=0.60.1 dir=/home/jeff/workspace/holos-run/holos/docs/examples/platforms/reference/clusters/foundation/cloud/github/arc
9:53PM DBG builder.go:131 cue: decoding holos build plan version=0.60.1 dir=/home/jeff/workspace/holos-run/holos/docs/examples/platforms/reference/clusters/foundation/cloud/github/arc
9:53PM DBG builder.go:122 cue: building instance version=0.60.1 dir=/home/jeff/workspace/holos-run/holos/docs/examples/platforms/reference/clusters/foundation/cloud/github/arc/runner
9:53PM DBG builder.go:127 cue: validating instance version=0.60.1 dir=/home/jeff/workspace/holos-run/holos/docs/examples/platforms/reference/clusters/foundation/cloud/github/arc/runner
9:53PM DBG builder.go:131 cue: decoding holos build plan version=0.60.1 dir=/home/jeff/workspace/holos-run/holos/docs/examples/platforms/reference/clusters/foundation/cloud/github/arc/runner
9:53PM DBG result.go:61 ExternalSecret/controller-manager version=0.60.1 kind=ExternalSecret name=controller-manager
9:53PM DBG builder.go:122 cue: building instance version=0.60.1 dir=/home/jeff/workspace/holos-run/holos/docs/examples/platforms/reference/clusters/foundation/cloud/github/arc/system
9:53PM DBG builder.go:127 cue: validating instance version=0.60.1 dir=/home/jeff/workspace/holos-run/holos/docs/examples/platforms/reference/clusters/foundation/cloud/github/arc/system
9:53PM DBG builder.go:131 cue: decoding holos build plan version=0.60.1 dir=/home/jeff/workspace/holos-run/holos/docs/examples/platforms/reference/clusters/foundation/cloud/github/arc/system
9:53PM DBG helm.go:95 helm: wrote values version=0.60.1 chart=oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller path=/tmp/holos1163326896/values.yaml bytes=653
9:53PM DBG run.go:40 running: helm version=0.60.1 name=helm args="[template --no-hooks --include-crds --values /tmp/holos1163326896/values.yaml --namespace arc-system --kubeconfig /dev/null --version 0.8.3 gha-rs-controller /home/jeff/workspace/holos-run/holos/docs/examples/platforms/reference/clusters/foundation/cloud/github/arc/system/vendor/gha-runner-scale-set-controller]"
9:53PM DBG remove.go:15 tmp: removed version=0.60.1 path=/tmp/holos1163326896
9:53PM DBG result.go:95 wrote: /tmp/holos.kustomize3569816247/resources.yaml version=0.60.1 op=write path=/tmp/holos.kustomize3569816247/resources.yaml bytes=2019229
9:53PM DBG result.go:108 wrote: /tmp/holos.kustomize3569816247/kustomization.yaml version=0.60.1 op=write path=/tmp/holos.kustomize3569816247/kustomization.yaml bytes=94
9:53PM DBG run.go:40 running: kubectl version=0.60.1 name=kubectl args="[kustomize /tmp/holos.kustomize3569816247]"
9:53PM DBG remove.go:15 tmp: removed version=0.60.1 path=/tmp/holos.kustomize3569816247
9:53PM DBG result.go:135 out: wrote deploy/clusters/k2/components/prod-github-arc-runner/prod-github-arc-runner.gen.yaml version=0.60.1 action=write path=deploy/clusters/k2/components/prod-github-arc-runner/prod-github-arc-runner.gen.yaml status=ok
9:53PM DBG result.go:135 out: wrote deploy/clusters/k2/holos/components/prod-github-arc-runner-kustomization.gen.yaml version=0.60.1 action=write path=deploy/clusters/k2/holos/components/prod-github-arc-runner-kustomization.gen.yaml status=ok
9:53PM INF render.go:43 rendered prod-github-arc-runner version=0.60.1 status=ok action=rendered name=prod-github-arc-runner
9:53PM DBG result.go:135 out: wrote deploy/clusters/k2/components/prod-github-arc-system/prod-github-arc-system.gen.yaml version=0.60.1 action=write path=deploy/clusters/k2/components/prod-github-arc-system/prod-github-arc-system.gen.yaml status=ok
9:53PM DBG result.go:135 out: wrote deploy/clusters/k2/holos/components/prod-github-arc-system-kustomization.gen.yaml version=0.60.1 action=write path=deploy/clusters/k2/holos/components/prod-github-arc-system-kustomization.gen.yaml status=ok
9:53PM INF render.go:43 rendered prod-github-arc-system version=0.60.1 status=ok action=rendered name=prod-github-arc-system
```
2024-03-22 10:14:04 -07:00
Jeff McCune
6f0928b12c (#71) Add go BuildPlan type as the CUE<->Holos API
This patch establishes the BuildPlan struct as the single API contract
between CUE and Holos.  A BuildPlan spec contains a list of each of the
support holos component types.

The purpose of this data structure is to support the use case of one CUE
instance generating 1 build plan that contains 0..N of each type of
holos component.

The need for multiple components per one CUE instance is to support the
generation of a collection of N~4 flux kustomization resources per
project and P~6 projects built from one CUE instance.

Tested with:

    holos render --cluster-name=k2 ~/workspace/holos-run/holos/docs/examples/platforms/reference/clusters/foundation/cloud/init/namespaces/...

Common labels are removed because they're too tightly coupled to the
model of one component per one cue instance.
2024-03-21 16:13:36 -07:00
Jeff McCune
c6e9250d60 (#69) Refactor clean up go types
Separate out the Kustomization and Kustomize types commonly used in
holos components.  Embed HolosComponent into Result.
2024-03-21 08:57:02 -07:00
Jeff McCune
104bda459f (#69) Go Types for CUE/Holos API contract
This patch refactors the go structs used to decode cue output for
processing by the holos cli.  For context, the purpose of the structs
are to inform holos how the data from cue should be modeled and
processed as a rendering pipeline that provides rendered yaml to
configure kubernetes api objects.

The structs share common fields in the form of the HolosComponent
embedded struct.  The three main holos component kinds today are:

 1. KubernetesObjects - CUE outputs a nested map where each value is a
    single rendered api object (resource).
 2. HelmChart - CUE outputs the chart name and values.  Holos calls helm
    template to render the chart.  Additional api objects may be
    overlaid into the rendered output.  Kustomize may also optionally be
    called at the end of the render pipeline.
 3. KustomizeBuild - CUE outputs data to construct a kustomize
    kustomization build.  The holos component contains raw yaml files to
    use as kustomization resources.  CUE optionally defines additional
    patches, common labels, etc.

With the Go structs, cue may directly import the definitions to more
easily keep the CUE definitions in sync with what the holos cli expects
to receive.

The holos component types may be imported into cue using:

    cue get go github.com/holos-run/holos/api/v1alpha1/...
2024-03-20 17:21:10 -07:00
Jeff McCune
bd2effa183 (#61) Improve ks prod-iam-zitadel robustness with flux health checks
Without this patch ks/prod-iam-zitadel often gets blocked waiting for
jobs that will never complete.  In addition, flux should not manage the
zitadel-test-connection Pod which is an unnecessary artifact of the
upstream helm chart.

We'd disable helm hooks, but they're necessary to create the init and
setup jobs.

This patch also changes the default behavior of Kustomizations from
wait: true to wait: false.  Waiting is expensive for the api server and
slows down the reconciliation process considerably.

Component authors should use ks.spec.healthChecks to target specific
important resources to watch and wait for.
2024-03-15 15:56:43 -07:00
Jeff McCune
562412fbe7 (#57) Run gha-rs scale set only on the primary cluster
This patch fixes the problem of the actions runner scale set listener
pod failing every 3 seconds.  See
https://github.com/actions/actions-runner-controller/issues/3351

The solution is not ideal, if the primary cluster is down workflows will
not execute.  The primary cluster shouldn't go down though so this is
the trade off.  Lower log spam and resource usage by eliminating the
failing pods on other clusters for lower availability if the primary
cluster is not available.

We could let the pods loop and if the primary is unavailable another
would quickly pick up the role, but it doesn't seem worth it.
2024-03-15 13:13:25 -07:00
Jeff McCune
fd6fbe5598 (#57) Allow gha-rs scale set to fail on all but one clusters
The effect of this patch is limited to refreshing credentials only for
namespaces that exist in the local cluster.  There is structure in place
in the CUE code to allow for namespaces bound to specific clusters, but
this is used only by the optional Vault component.

This patch was an attempt to work around
https://github.com/actions/actions-runner-controller/issues/3351 by
deploying the runner scale sets into unique namespaces.

This effort was a waste of time, only one listener pod successfully
registered for a given scale set name / group combination.

Because we have only one group named Default we can only have one
listener pod globally for a given scale set name.

Because we want our workflows to execute regardless of the availability
of a single cluster, we're going to let this fail for now.  The pod
retries every 3 seconds.  When a cluster is destroyed, another cluster
will quickly register.

A follow up patch will look to expand this retry behavior.
2024-03-15 12:53:16 -07:00
Jeff McCune
67472e1e1c (#60) Disable flux reconciliation of deployment/zitadel on standby clusters 2024-03-14 21:58:32 -07:00
Jeff McCune
d64c3e8c66 (#58) Zitadel Failover RunBook 2024-03-14 15:25:38 -07:00
Jeff McCune
f344f97374 (#58) Restore last zitadel database backup
When the cluster is provisioned, restore the most recent backup instead
of a fixed point in time.
2024-03-14 11:40:17 -07:00
Jeff McCune
770088b912 (#53) Clean up nested if statements with && 2024-03-13 10:35:20 -07:00
Jeff McCune
cb9b39c3ca (#53) Add Vault as an optional service on the core clusters
This patch migrates the vault component from [holos-infra][1] to a cue
based component.  Vault is optional in the reference platform, so this
patch also defines an `#OptionalServices` struct to conditionally manage
a service across multiple clusters in the platform.

The primary use case for optional services is managing a namespace to
provision and provide secrets across clusters.

[1]: https://github.com/holos-run/holos-infra/tree/v0.5.0/components/core/core/vault
2024-03-12 17:18:38 -07:00
Jeff McCune
0f34b20546 (#54) Disable helm hooks when rendering components
Pods are unnecessarily created when deploying helm based holos
components and often fail.  Prevent these test pods by disabling helm
hooks with the `--no-hooks` flag.

Closes: #54
2024-03-12 14:14:20 -07:00
Jeff McCune
0d7bbbb659 (#48) Disable pg spec.dataSource for standby cluster
Problem:
The standby cluster on k2 fails to start.  A pgbackrest pod first
restores the database from S3, then the pgha nodes try to replay the WAL
as part of the standby initialization process.  This fails because the
PGDATA directory is not empty.

Solution:
Specify the spec.dataSource field only when the cluster is configured as
a primary cluster.

Result:
Non-primary clusters are standby, they skip the pgbackrest job to
restore from S3 and move straight to patroni replaying the WAL from S3
as part of the pgha pods.

One of the two pgha pods becomes the "standby leader" and restores the
WAL from S3.  The other is a cascading standby and then restores the
same WAL from the standby leader.

After 8 minutes both pods are ready.

```
❯ k get pods
NAME                               READY   STATUS    RESTARTS   AGE
zitadel-pgbouncer-d9f8cffc-j469g   2/2     Running   0          11m
zitadel-pgbouncer-d9f8cffc-xq29g   2/2     Running   0          11m
zitadel-pgha1-27w7-0               4/4     Running   0          11m
zitadel-pgha1-c5qj-0               4/4     Running   0          11m
zitadel-repo-host-0                2/2     Running   0          11m
```
2024-03-11 17:56:47 -07:00
Jeff McCune
3f3e36bbe9 (#48) Split workload into foundation and accounts
Problem:
The k3 and k4 clusters are getting the Zitadel components which are
really only intended for the core cluster pair.

Solution:
Split the workload subtree into two, named foundation and accounts.  The
core cluster pair gets foundation+accounts while the kX clusters get
just the foundation subtree.

Result:
prod-zitadel-iam is no longer managed on k3 and k4
2024-03-11 15:20:35 -07:00
Jeff McCune
9f41478d33 (#48) Restore from Monday morning after Gary and Nate registered
Set the restore point to time="2024-03-11T17:08:58Z" level=info
msg="crunchy-pgbackrest ends" which is just after Gary and Nate
registered and were granted the cluster-admin role.
2024-03-11 10:18:45 -07:00
Jeff McCune
b86fee04fc (#48) v0.55.4 to rebuild k3, k4, k5 2024-03-11 08:48:07 -07:00
Jeff McCune
c78da6949f Merge pull request #51 from holos-run/jeff/48-zitadel-backups
(#48) Custom PGO Certs for Zitadel
2024-03-10 23:08:29 -07:00
Jeff McCune
7b215bb8f1 (#48) Custom PGO Certs for Zitadel
The [Streaming Standby][standby] architecture requires custom tls certs
for two clusters in two regions to connect to each other.

This patch manages the custom certs following the configuration
described in the article [Using Cert Manager to Deploy TLS for Postgres
on Kubernetes][article].

NOTE: One thing not mentioned anywhere in the crunchy documentation is
how custom tls certs work with pgbouncer.  The pgbouncer service uses a
tls certificate issued by the pgo root cert, not by the custom
certificate authority.

For this reason, we use kustomize to patch the zitadel Deployment and
the zitadel-init and zitadel-setup Jobs.  The patch projects the ca
bundle from the `zitadel-pgbouncer` secret into the zitadel pods at
/pgbouncer/ca.crt

[standby]: https://access.crunchydata.com/documentation/postgres-operator/latest/architecture/disaster-recovery#streaming-standby-with-an-external-repo
[article]: https://www.crunchydata.com/blog/using-cert-manager-to-deploy-tls-for-postgres-on-kubernetes
2024-03-10 22:54:06 -07:00
Jeff McCune
78cec76a96 (#48) Restore ZITADEL from point in time full backup
A full backup was taken using:

```
kubectl annotate postgrescluster zitadel postgres-operator.crunchydata.com/pgbackrest-backup="$(date)"
```

And completed with:

```
❯ k logs -f zitadel-backup-5r6v-v5jnm
time="2024-03-10T21:52:15Z" level=info msg="crunchy-pgbackrest starts"
time="2024-03-10T21:52:15Z" level=info msg="debug flag set to false"
time="2024-03-10T21:52:15Z" level=info msg="backrest backup command requested"
time="2024-03-10T21:52:15Z" level=info msg="command to execute is [pgbackrest backup --stanza=db --repo=2 --type=full]"
time="2024-03-10T21:55:18Z" level=info msg="crunchy-pgbackrest ends"
```

This patch verifies the point in time backup is robust in the face of
the following operations:

1. pg cluster zitadel was deleted (whole namespace emptied)
2. pg cluster zitadel was re-created _without_ a `dataSource`
3. pgo initailized a new database and backed up the blank database to
   S3.
4. pg cluster zitadel was deleted again.
5. pg cluster zitadel was re-created with `dataSource` `options: ["--type=time", "--target=\"2024-03-10 21:56:00+00\""]` (Just after the full backup completed)
6. Restore completed successfully.
7. Applied the holos zitadel component.
8. Zitadel came up successfully and user login worked as expected.

- [x] Perform an in place [restore][restore] from [s3][bucket].
- [x] Set repo1-retention-full to clear warning

[restore]: https://access.crunchydata.com/documentation/postgres-operator/latest/tutorials/backups-disaster-recovery/disaster-recovery#restore-properties
[bucket]: https://access.crunchydata.com/documentation/postgres-operator/latest/tutorials/backups-disaster-recovery/disaster-recovery#cloud-based-data-source
2024-03-10 17:42:54 -07:00
Jeff McCune
0e98ad2ecb (#48) Zitadel Backups
This patch configures backups suitable to support the [Streaming Standby
with an External Repo][0] architecture.

- [x] PGO [Multiple Backup Repositories][1] to k8s pv and s3.
- [x] [Encryption][2] of backups to S3.
- [x] [Remove SUPERUSER][3] role from zitadel-admin pg user to work with pgbouncer.  Resolves zitadel-init job failure.
- [x] Take a [Manual Backup][5]

[0]: https://access.crunchydata.com/documentation/postgres-operator/latest/architecture/disaster-recovery#streaming-standby-with-an-external-repo
[1]: https://access.crunchydata.com/documentation/postgres-operator/latest/tutorials/backups-disaster-recovery/backups#set-up-multiple-backup-repositories
[2]: https://access.crunchydata.com/documentation/postgres-operator/latest/tutorials/backups-disaster-recovery/backups#encryption
[3]: https://github.com/CrunchyData/postgres-operator/issues/3095#issuecomment-1904712211
[4]: https://access.crunchydata.com/documentation/postgres-operator/latest/tutorials/backups-disaster-recovery/disaster-recovery#streaming-standby-with-an-external-repo
[5]: https://access.crunchydata.com/documentation/postgres-operator/latest/tutorials/backups-disaster-recovery/backup-management#taking-a-one-off-backup
2024-03-10 16:38:56 -07:00
Jeff McCune
30bb3f183a (#50) Describe type as strings to match others 2024-03-10 11:29:19 -07:00
Jeff McCune
1369338f3c (#50) Add -n shorthand for --namespace for secrets
It's annoying holos get secret -n foo doesn't work like kubectl get
secret -n foo works.

Closes: #50
2024-03-10 10:45:49 -07:00
Jeff McCune
ac03f64724 (#45) Configure ZITADEL to use pgbouncer 2024-03-09 09:44:33 -08:00
Jeff McCune
bea4468972 (#42) Remove cert manager db ca components
Simpler to let postgres manage the certs.  TLS is in verify-full mode
with the pgo configured certs.
2024-03-08 21:34:26 -08:00
Jeff McCune
224adffa15 (#42) Add holos components for zitadel with postgres
To establish the canonical https://login.ois.run identity issuer on the
core cluster pair.

Custom resources for PGO have been imported with:

    timoni mod vendor crds -f deploy/clusters/core2/components/prod-pgo-crds/prod-pgo-crds.gen.yaml

Note, the zitadel tls connection took some considerable effort to get
working.  We intentionally use pgo issued certs to reduce the toil of
managing certs issued by cert manager.

The default tls configuration of pgo is pretty good with verify full
enabled.
2024-03-08 21:29:25 -08:00
156 changed files with 15833 additions and 1461 deletions

1
.gitignore vendored
View File

@@ -5,3 +5,4 @@ coverage.out
dist/
*.hold/
/deploy/
.vscode/

View File

@@ -44,13 +44,18 @@ tidy: ## Tidy go module.
go mod tidy
.PHONY: fmt
fmt: ## Format Go code.
fmt: ## Format code.
cd docs/examples && cue fmt ./...
go fmt ./...
.PHONY: vet
vet: ## Vet Go code.
go vet ./...
.PHONY: gencue
gencue: ## Generate CUE definitions
cd docs/examples && cue get go github.com/holos-run/holos/api/...
.PHONY: generate
generate: ## Generate code.
go generate ./...

40
api/v1alpha1/buildplan.go Normal file
View File

@@ -0,0 +1,40 @@
package v1alpha1
import (
"fmt"
"strings"
)
// BuildPlan is the primary interface between CUE and the Holos cli.
type BuildPlan struct {
TypeMeta `json:",inline" yaml:",inline"`
// Metadata represents the holos component name
Metadata ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
Spec BuildPlanSpec `json:"spec,omitempty" yaml:"spec,omitempty"`
}
type BuildPlanSpec struct {
Disabled bool `json:"disabled,omitempty" yaml:"disabled,omitempty"`
Components BuildPlanComponents `json:"components,omitempty" yaml:"components,omitempty"`
}
type BuildPlanComponents struct {
HelmChartList []HelmChart `json:"helmChartList,omitempty" yaml:"helmChartList,omitempty"`
KubernetesObjectsList []KubernetesObjects `json:"kubernetesObjectsList,omitempty" yaml:"kubernetesObjectsList,omitempty"`
KustomizeBuildList []KustomizeBuild `json:"kustomizeBuildList,omitempty" yaml:"kustomizeBuildList,omitempty"`
Resources map[string]KubernetesObjects `json:"resources,omitempty" yaml:"resources,omitempty"`
}
func (bp *BuildPlan) Validate() error {
errs := make([]string, 0, 2)
if bp.Kind != BuildPlanKind {
errs = append(errs, fmt.Sprintf("kind invalid: want: %s have: %s", BuildPlanKind, bp.Kind))
}
if bp.APIVersion != APIVersion {
errs = append(errs, fmt.Sprintf("apiVersion invalid: want: %s have: %s", APIVersion, bp.APIVersion))
}
if len(errs) > 0 {
return fmt.Errorf("invalid BuildPlan: " + strings.Join(errs, ", "))
}
return nil
}

22
api/v1alpha1/component.go Normal file
View File

@@ -0,0 +1,22 @@
package v1alpha1
// HolosComponent defines the fields common to all holos component kinds including the Render Result.
type HolosComponent struct {
TypeMeta `json:",inline" yaml:",inline"`
// Metadata represents the holos component name
Metadata ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
// APIObjectMap holds the marshalled representation of api objects. Think of
// these as resources overlaid at the back of the render pipeline.
APIObjectMap APIObjectMap `json:"apiObjectMap,omitempty" yaml:"apiObjectMap,omitempty"`
// Kustomization holds the marshalled representation of the flux kustomization
// which reconciles resources in git with the api server.
Kustomization `json:",inline" yaml:",inline"`
// Kustomize represents a kubectl kustomize build post-processing step.
Kustomize `json:",inline" yaml:",inline"`
// Skip causes holos to take no action regarding the component.
Skip bool
}
func (hc *HolosComponent) NewResult() *Result {
return &Result{HolosComponent: *hc}
}

11
api/v1alpha1/constants.go Normal file
View File

@@ -0,0 +1,11 @@
package v1alpha1
const (
APIVersion = "holos.run/v1alpha1"
BuildPlanKind = "BuildPlan"
HelmChartKind = "HelmChart"
// ChartDir is the directory name created in the holos component directory to cache a chart.
ChartDir = "vendor"
// ResourcesFile is the file name used to store component output when post-processing with kustomize.
ResourcesFile = "resources.yaml"
)

2
api/v1alpha1/doc.go Normal file
View File

@@ -0,0 +1,2 @@
// Package v1alpha1 defines the api boundary between CUE and Holos.
package v1alpha1

154
api/v1alpha1/helm.go Normal file
View File

@@ -0,0 +1,154 @@
package v1alpha1
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"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"
)
// A HelmChart represents a helm command to provide chart values in order to render kubernetes api objects.
type HelmChart struct {
HolosComponent `json:",inline" yaml:",inline"`
// Namespace is the namespace to install into. TODO: Use metadata.namespace instead.
Namespace string `json:"namespace"`
Chart Chart `json:"chart"`
ValuesContent string `json:"valuesContent"`
EnableHooks bool `json:"enableHooks"`
}
type Chart struct {
Name string `json:"name"`
Version string `json:"version"`
Release string `json:"release"`
Repository Repository `json:"repository,omitempty"`
}
type Repository struct {
Name string `json:"name"`
URL string `json:"url"`
}
func (hc *HelmChart) Render(ctx context.Context, path holos.InstancePath) (*Result, error) {
result := Result{HolosComponent: hc.HolosComponent}
if err := hc.helm(ctx, &result, path); err != nil {
return nil, err
}
result.addObjectMap(ctx, hc.APIObjectMap)
if err := result.kustomize(ctx); err != nil {
return nil, wrapper.Wrap(fmt.Errorf("could not kustomize: %w", err))
}
return &result, nil
}
// runHelm provides the values produced by CUE to helm template and returns
// the rendered kubernetes api objects in the result.
func (hc *HelmChart) helm(ctx context.Context, r *Result, path holos.InstancePath) error {
log := logger.FromContext(ctx).With("chart", hc.Chart.Name)
if hc.Chart.Name == "" {
log.WarnContext(ctx, "skipping helm: no chart name specified, use a different component type")
return nil
}
cachedChartPath := filepath.Join(string(path), ChartDir, filepath.Base(hc.Chart.Name))
if isNotExist(cachedChartPath) {
// Add repositories
repo := hc.Chart.Repository
if 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())
return wrapper.Wrap(fmt.Errorf("could not run helm repo add: %w", err))
}
// Update repository
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())
return wrapper.Wrap(fmt.Errorf("could not run helm repo update: %w", err))
}
} else {
log.DebugContext(ctx, "no chart repository url proceeding assuming oci chart")
}
// Cache the chart
if err := cacheChart(ctx, path, ChartDir, hc.Chart); err != nil {
return fmt.Errorf("could not cache chart: %w", err)
}
}
// Write values file
tempDir, err := os.MkdirTemp("", "holos")
if err != nil {
return wrapper.Wrap(fmt.Errorf("could not make temp dir: %w", err))
}
defer util.Remove(ctx, tempDir)
valuesPath := filepath.Join(tempDir, "values.yaml")
if err := os.WriteFile(valuesPath, []byte(hc.ValuesContent), 0644); err != nil {
return wrapper.Wrap(fmt.Errorf("could not write values: %w", err))
}
log.DebugContext(ctx, "helm: wrote values", "path", valuesPath, "bytes", len(hc.ValuesContent))
// Run charts
chart := hc.Chart
args := []string{"template"}
if !hc.EnableHooks {
args = append(args, "--no-hooks")
}
namespace := hc.Namespace
args = append(args, "--include-crds", "--values", valuesPath, "--namespace", namespace, "--kubeconfig", "/dev/null", "--version", chart.Version, chart.Release, cachedChartPath)
helmOut, err := util.RunCmd(ctx, "helm", args...)
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.accumulatedOutput = helmOut.Stdout.String()
return nil
}
// cacheChart stores a cached copy of Chart in the chart subdirectory of path.
func cacheChart(ctx context.Context, path holos.InstancePath, chartDir string, chart Chart) error {
log := logger.FromContext(ctx)
cacheTemp, err := os.MkdirTemp(string(path), chartDir)
if err != nil {
return wrapper.Wrap(fmt.Errorf("could not make temp dir: %w", err))
}
defer util.Remove(ctx, cacheTemp)
chartName := chart.Name
if chart.Repository.Name != "" {
chartName = fmt.Sprintf("%s/%s", chart.Repository.Name, chart.Name)
}
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)
cachePath := filepath.Join(string(path), chartDir)
if err := os.Rename(cacheTemp, cachePath); err != nil {
return wrapper.Wrap(fmt.Errorf("could not rename: %w", err))
}
log.InfoContext(ctx, "cached", "chart", chart.Name, "version", chart.Version, "path", cachePath)
return nil
}
func isNotExist(path string) bool {
_, err := os.Stat(path)
return os.IsNotExist(err)
}

View File

@@ -0,0 +1,21 @@
package v1alpha1
import (
"context"
"github.com/holos-run/holos"
)
const KubernetesObjectsKind = "KubernetesObjects"
// KubernetesObjects represents CUE output which directly provides Kubernetes api objects to holos.
type KubernetesObjects struct {
HolosComponent `json:",inline" yaml:",inline"`
}
// Render produces kubernetes api objects from the APIObjectMap
func (o *KubernetesObjects) Render(ctx context.Context, path holos.InstancePath) (*Result, error) {
result := Result{HolosComponent: o.HolosComponent}
result.addObjectMap(ctx, o.APIObjectMap)
return &result, nil
}

View File

@@ -0,0 +1,7 @@
package v1alpha1
// Kustomization holds the rendered flux kustomization api object content for git ops.
type Kustomization struct {
// KsContent is the yaml representation of the flux kustomization for gitops.
KsContent string `json:"ksContent,omitempty" yaml:"ksContent,omitempty"`
}

47
api/v1alpha1/kustomize.go Normal file
View File

@@ -0,0 +1,47 @@
package v1alpha1
import (
"context"
"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"
)
const KustomizeBuildKind = "KustomizeBuild"
// Kustomize represents resources necessary to execute a kustomize build.
// Intended for at least two use cases:
//
// 1. Process raw yaml file resources in a holos component directory.
// 2. Post process a HelmChart to inject istio, add custom labels, etc...
type Kustomize struct {
// KustomizeFiles holds file contents for kustomize, e.g. patch files.
KustomizeFiles FileContentMap `json:"kustomizeFiles,omitempty" yaml:"kustomizeFiles,omitempty"`
// ResourcesFile is the file name used for api objects in kustomization.yaml
ResourcesFile string `json:"resourcesFile,omitempty" yaml:"resourcesFile,omitempty"`
}
// KustomizeBuild renders plain yaml files in the holos component directory using kubectl kustomize build.
type KustomizeBuild struct {
HolosComponent `json:",inline" yaml:",inline"`
}
// Render produces a Result by executing kubectl kustomize on the holos
// component path. Useful for processing raw yaml files.
func (kb *KustomizeBuild) Render(ctx context.Context, path holos.InstancePath) (*Result, error) {
log := logger.FromContext(ctx)
result := Result{HolosComponent: kb.HolosComponent}
// Run kustomize.
kOut, err := util.RunCmd(ctx, "kubectl", "kustomize", string(path))
if err != nil {
log.ErrorContext(ctx, kOut.Stderr.String())
return nil, wrapper.Wrap(err)
}
// Replace the accumulated output
result.accumulatedOutput = kOut.Stdout.String()
// Add CUE based api objects.
result.addObjectMap(ctx, kb.APIObjectMap)
return &result, nil
}

14
api/v1alpha1/objectmap.go Normal file
View File

@@ -0,0 +1,14 @@
package v1alpha1
// Label is an arbitrary unique identifier. Defined as a type for clarity and type checking.
type Label string
// Kind is a kubernetes api object kind. Defined as a type for clarity and type checking.
type Kind string
// APIObjectMap is the shape of marshalled api objects returned from cue to the
// holos cli. A map is used to improve the clarity of error messages from cue.
type APIObjectMap map[Kind]map[Label]string
// FileContentMap is a map of file names to file contents.
type FileContentMap map[string]string

View File

@@ -0,0 +1,15 @@
package v1alpha1
// ObjectMeta represents metadata of a holos component object. The fields are a
// copy of upstream kubernetes api machinery but are by holos objects distinct
// from kubernetes api objects.
type ObjectMeta struct {
// Name uniquely identifies the holos component instance and must be suitable as a file name.
Name string `json:"name,omitempty" yaml:"name,omitempty"`
// Namespace confines a holos component to a single namespace via kustomize if set.
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
// Labels are not used but are copied from api machinery ObjectMeta for completeness.
Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
// Annotations are not used but are copied from api machinery ObjectMeta for completeness.
Annotations map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty"`
}

22
api/v1alpha1/render.go Normal file
View File

@@ -0,0 +1,22 @@
package v1alpha1
import (
"context"
"github.com/holos-run/holos"
)
type Renderer interface {
GetKind() string
Render(ctx context.Context, path holos.InstancePath) (*Result, error)
}
// Render produces a Result representing the kubernetes api objects to
// configure. Each of the various holos component types, e.g. Helm, Kustomize,
// et al, should implement the Renderer interface. This process is best
// conceptualized as a data pipeline, for example a component may render a
// result by first calling helm template, then passing the result through
// kustomize, then mixing in overlay api objects.
func Render(ctx context.Context, r Renderer, path holos.InstancePath) (*Result, error) {
return r.Render(ctx, path)
}

138
api/v1alpha1/result.go Normal file
View File

@@ -0,0 +1,138 @@
package v1alpha1
import (
"context"
"fmt"
"os"
"path/filepath"
"slices"
"github.com/holos-run/holos/pkg/logger"
"github.com/holos-run/holos/pkg/util"
"github.com/holos-run/holos/pkg/wrapper"
)
// Result is the build result for display or writing. Holos components Render the Result as a data pipeline.
type Result struct {
HolosComponent
// accumulatedOutput accumulates rendered api objects.
accumulatedOutput string
}
func (r *Result) Name() string {
return r.Metadata.Name
}
func (r *Result) Filename(writeTo string, cluster string) string {
name := r.Metadata.Name
return filepath.Join(writeTo, "clusters", cluster, "components", name, name+".gen.yaml")
}
func (r *Result) KustomizationFilename(writeTo string, cluster string) string {
return filepath.Join(writeTo, "clusters", cluster, "holos", "components", r.Metadata.Name+"-kustomization.gen.yaml")
}
// AccumulatedOutput returns the accumulated rendered output.
func (r *Result) AccumulatedOutput() string {
return r.accumulatedOutput
}
// addObjectMap renders the provided APIObjectMap into the accumulated output.
func (r *Result) addObjectMap(ctx context.Context, objectMap APIObjectMap) {
log := logger.FromContext(ctx)
b := []byte(r.AccumulatedOutput())
kinds := make([]Kind, 0, len(objectMap))
// Sort the keys
for kind := range objectMap {
kinds = append(kinds, kind)
}
slices.Sort(kinds)
for _, kind := range kinds {
v := objectMap[kind]
// Sort the keys
names := make([]Label, 0, len(v))
for name := range v {
names = append(names, name)
}
slices.Sort(names)
for _, name := range names {
yamlString := v[name]
log.Debug(fmt.Sprintf("%s/%s", kind, name), "kind", kind, "name", name)
b = util.EnsureNewline(b)
header := fmt.Sprintf("---\n# Source: CUE apiObjects.%s.%s\n", kind, name)
b = append(b, []byte(header+yamlString)...)
b = util.EnsureNewline(b)
}
}
r.accumulatedOutput = string(b)
}
// kustomize replaces the accumulated output with the output of kustomize build
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 util.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.
func (r *Result) Save(ctx context.Context, path string, content string) error {
log := logger.FromContext(ctx)
dir := filepath.Dir(path)
if err := os.MkdirAll(dir, os.FileMode(0775)); err != nil {
log.WarnContext(ctx, "could not mkdir", "path", dir, "err", err)
return wrapper.Wrap(err)
}
// Write the kube api objects
if err := os.WriteFile(path, []byte(content), os.FileMode(0644)); err != nil {
log.WarnContext(ctx, "could not write", "path", path, "err", err)
return wrapper.Wrap(err)
}
log.DebugContext(ctx, "out: wrote "+path, "action", "write", "path", path, "status", "ok")
return nil
}

10
api/v1alpha1/typemeta.go Normal file
View File

@@ -0,0 +1,10 @@
package v1alpha1
type TypeMeta struct {
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
}
func (tm *TypeMeta) GetKind() string {
return tm.Kind
}

View File

@@ -1,7 +1,6 @@
# Want support for intermediary constraints
exec holos build ./foo/... --log-level debug
stdout '^bf2bc7f9-9ba0-4f9e-9bd2-9a205627eb0b$'
stderr 'processing holos component kind Skip'
-- cue.mod --
package holos
@@ -12,31 +11,21 @@ metadata: name: "jeff"
-- foo/bar/bar.cue --
package holos
#KubernetesObjects & {
apiObjectMap: foo: bar: "bf2bc7f9-9ba0-4f9e-9bd2-9a205627eb0b"
}
spec: components: KubernetesObjectsList: [
#KubernetesObjects & {
apiObjectMap: foo: bar: "bf2bc7f9-9ba0-4f9e-9bd2-9a205627eb0b"
}
]
-- schema.cue --
package holos
cluster: string @tag(cluster, string)
// #OutputTypeMeta is shared among all output types
#OutputTypeMeta: {
apiVersion: "holos.run/v1alpha1"
kind: #KubernetesObjects.kind | #NoOutput.kind
metadata: name: string
}
_cluster: string @tag(cluster, string)
#KubernetesObjects: {
#OutputTypeMeta
apiVersion: "holos.run/v1alpha1"
kind: "KubernetesObjects"
apiObjectMap: {...}
}
#NoOutput: {
#OutputTypeMeta
kind: string | *"Skip"
metadata: name: string | *"skipped"
}
#NoOutput & {}
apiVersion: "holos.run/v1alpha1"
kind: "BuildPlan"

View File

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

View File

@@ -9,15 +9,17 @@ package holos
package holos
apiVersion: "holos.run/v1alpha1"
kind: "KubernetesObjects"
cluster: string @tag(cluster, string)
kind: "BuildPlan"
spec: components: KubernetesObjectsList: [{apiObjectMap: #APIObjects.apiObjectMap}]
_cluster: string @tag(cluster, string)
#SecretStore: {
kind: string
metadata: name: string
}
#APIObjects & {
#APIObjects: {
apiObjects: {
SecretStore: {
default: #SecretStore & { metadata: name: "default" }
@@ -54,4 +56,3 @@ import "encoding/yaml"
}
}
}

View File

@@ -10,15 +10,17 @@ package holos
package holos
apiVersion: "holos.run/v1alpha1"
kind: "HelmChart"
cluster: string @tag(cluster, string)
kind: "BuildPlan"
spec: components: HelmChartList: [{apiObjectMap: #APIObjects.apiObjectMap}]
_cluster: string @tag(cluster, string)
#SecretStore: {
kind: string
metadata: name: string
}
#APIObjects & {
#APIObjects: {
apiObjects: {
SecretStore: {
default: #SecretStore & { metadata: name: "default" }
@@ -55,4 +57,3 @@ import "encoding/yaml"
}
}
}

View File

@@ -7,22 +7,27 @@ 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"
release: name
repository: {
name: "zitadel"
url: "https://charts.zitadel.com"
}
}
kind: "BuildPlan"
spec: components: HelmChartList: [_HelmChart]
_cluster: string @tag(cluster, string)
_HelmChart: {
apiVersion: "holos.run/v1alpha1"
kind: "HelmChart"
metadata: name: "zitadel"
namespace: "zitadel"
chart: {
name: "zitadel"
version: "7.9.0"
release: name
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)) ) }}

View File

@@ -9,22 +9,25 @@ package holos
-- component.cue --
package holos
cluster: string @tag(cluster, string)
_cluster: string @tag(cluster, string)
apiVersion: "holos.run/v1alpha1"
kind: "KustomizeBuild"
metadata: name: "kstest"
kind: "BuildPlan"
spec: components: KustomizeBuildList: [{metadata: name: "kstest"}]
-- kustomization.yaml --
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: mynamespace
resources:
- serviceaccount.yaml
-- serviceaccount.yaml --
apiVersion: v1
kind: ServiceAccount
metadata:
name: test
-- want.yaml --
apiVersion: v1
kind: ServiceAccount

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

@@ -0,0 +1,26 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go github.com/holos-run/holos/api/v1alpha1
package v1alpha1
// BuildPlan is the primary interface between CUE and the Holos cli.
#BuildPlan: {
#TypeMeta
// Metadata represents the holos component name
metadata?: #ObjectMeta @go(Metadata)
spec?: #BuildPlanSpec @go(Spec)
}
#BuildPlanSpec: {
disabled?: bool @go(Disabled)
components?: #BuildPlanComponents @go(Components)
}
#BuildPlanComponents: {
helmChartList?: [...#HelmChart] @go(HelmChartList,[]HelmChart)
kubernetesObjectsList?: [...#KubernetesObjects] @go(KubernetesObjectsList,[]KubernetesObjects)
kustomizeBuildList?: [...#KustomizeBuild] @go(KustomizeBuildList,[]KustomizeBuild)
resources?: {[string]: #KubernetesObjects} @go(Resources,map[string]KubernetesObjects)
}

View File

@@ -0,0 +1,24 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go github.com/holos-run/holos/api/v1alpha1
package v1alpha1
// HolosComponent defines the fields common to all holos component kinds including the Render Result.
#HolosComponent: {
#TypeMeta
// Metadata represents the holos component name
metadata?: #ObjectMeta @go(Metadata)
// APIObjectMap holds the marshalled representation of api objects. Think of
// these as resources overlaid at the back of the render pipeline.
apiObjectMap?: #APIObjectMap @go(APIObjectMap)
#Kustomization
#Kustomize
// Skip causes holos to take no action regarding the component.
Skip: bool
}

View File

@@ -0,0 +1,15 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go github.com/holos-run/holos/api/v1alpha1
package v1alpha1
#APIVersion: "holos.run/v1alpha1"
#BuildPlanKind: "BuildPlan"
#HelmChartKind: "HelmChart"
// ChartDir is the directory name created in the holos component directory to cache a chart.
#ChartDir: "vendor"
// ResourcesFile is the file name used to store component output when post-processing with kustomize.
#ResourcesFile: "resources.yaml"

View File

@@ -0,0 +1,6 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go github.com/holos-run/holos/api/v1alpha1
// Package v1alpha1 defines the api boundary between CUE and Holos.
package v1alpha1

View File

@@ -0,0 +1,28 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go github.com/holos-run/holos/api/v1alpha1
package v1alpha1
// A HelmChart represents a helm command to provide chart values in order to render kubernetes api objects.
#HelmChart: {
#HolosComponent
// Namespace is the namespace to install into. TODO: Use metadata.namespace instead.
namespace: string @go(Namespace)
chart: #Chart @go(Chart)
valuesContent: string @go(ValuesContent)
enableHooks: bool @go(EnableHooks)
}
#Chart: {
name: string @go(Name)
version: string @go(Version)
release: string @go(Release)
repository?: #Repository @go(Repository)
}
#Repository: {
name: string @go(Name)
url: string @go(URL)
}

View File

@@ -0,0 +1,12 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go github.com/holos-run/holos/api/v1alpha1
package v1alpha1
#KubernetesObjectsKind: "KubernetesObjects"
// KubernetesObjects represents CUE output which directly provides Kubernetes api objects to holos.
#KubernetesObjects: {
#HolosComponent
}

View File

@@ -0,0 +1,11 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go github.com/holos-run/holos/api/v1alpha1
package v1alpha1
// Kustomization holds the rendered flux kustomization api object content for git ops.
#Kustomization: {
// KsContent is the yaml representation of the flux kustomization for gitops.
ksContent?: string @go(KsContent)
}

View File

@@ -0,0 +1,25 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go github.com/holos-run/holos/api/v1alpha1
package v1alpha1
#KustomizeBuildKind: "KustomizeBuild"
// Kustomize represents resources necessary to execute a kustomize build.
// Intended for at least two use cases:
//
// 1. Process raw yaml file resources in a holos component directory.
// 2. Post process a HelmChart to inject istio, add custom labels, etc...
#Kustomize: {
// KustomizeFiles holds file contents for kustomize, e.g. patch files.
kustomizeFiles?: #FileContentMap @go(KustomizeFiles)
// ResourcesFile is the file name used for api objects in kustomization.yaml
resourcesFile?: string @go(ResourcesFile)
}
// KustomizeBuild renders plain yaml files in the holos component directory using kubectl kustomize build.
#KustomizeBuild: {
#HolosComponent
}

View File

@@ -0,0 +1,12 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go github.com/holos-run/holos/api/v1alpha1
package v1alpha1
#KustomizeBuildKind: "KustomizeBuild"
// KustomizeBuild
#KustomizeBuild: {
#HolosComponent
}

View File

@@ -0,0 +1,18 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go github.com/holos-run/holos/api/v1alpha1
package v1alpha1
// Label is an arbitrary unique identifier. Defined as a type for clarity and type checking.
#Label: string
// Kind is a kubernetes api object kind. Defined as a type for clarity and type checking.
#Kind: string
// APIObjectMap is the shape of marshalled api objects returned from cue to the
// holos cli. A map is used to improve the clarity of error messages from cue.
#APIObjectMap: {[string]: [string]: string}
// FileContentMap is a map of file names to file contents.
#FileContentMap: {[string]: string}

View File

@@ -0,0 +1,22 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go github.com/holos-run/holos/api/v1alpha1
package v1alpha1
// ObjectMeta represents metadata of a holos component object. The fields are a
// copy of upstream kubernetes api machinery but are by holos objects distinct
// from kubernetes api objects.
#ObjectMeta: {
// Name uniquely identifies the holos component instance and must be suitable as a file name.
name?: string @go(Name)
// Namespace confines a holos component to a single namespace via kustomize if set.
namespace?: string @go(Namespace)
// Labels are not used but are copied from api machinery ObjectMeta for completeness.
labels?: {[string]: string} @go(Labels,map[string]string)
// Annotations are not used but are copied from api machinery ObjectMeta for completeness.
annotations?: {[string]: string} @go(Annotations,map[string]string)
}

View File

@@ -0,0 +1,7 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go github.com/holos-run/holos/api/v1alpha1
package v1alpha1
#Renderer: _

View File

@@ -0,0 +1,10 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go github.com/holos-run/holos/api/v1alpha1
package v1alpha1
// Result is the build result for display or writing. Holos components Render the Result as a data pipeline.
#Result: {
HolosComponent: #HolosComponent
}

View File

@@ -0,0 +1,10 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go github.com/holos-run/holos/api/v1alpha1
package v1alpha1
#TypeMeta: {
kind?: string @go(Kind)
apiVersion?: string @go(APIVersion)
}

View File

@@ -0,0 +1,975 @@
// Code generated by timoni. DO NOT EDIT.
//timoni:generate timoni vendor crd -f /home/jeff/workspace/holos-run/holos-infra/deploy/clusters/core2/components/prod-pgo-crds/prod-pgo-crds.gen.yaml
package v1beta1
import "strings"
// PGAdmin is the Schema for the pgadmins API
#PGAdmin: {
// APIVersion defines the versioned schema of this representation
// of an object. Servers should convert recognized schemas to the
// latest internal value, and may reject unrecognized values.
// More info:
// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
apiVersion: "postgres-operator.crunchydata.com/v1beta1"
// Kind is a string value representing the REST resource this
// object represents. Servers may infer this from the endpoint
// the client submits requests to. Cannot be updated. In
// CamelCase. More info:
// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
kind: "PGAdmin"
metadata!: {
name!: strings.MaxRunes(253) & strings.MinRunes(1) & {
string
}
namespace!: strings.MaxRunes(63) & strings.MinRunes(1) & {
string
}
labels?: {
[string]: string
}
annotations?: {
[string]: string
}
}
// PGAdminSpec defines the desired state of PGAdmin
spec!: #PGAdminSpec
}
// PGAdminSpec defines the desired state of PGAdmin
#PGAdminSpec: {
// Scheduling constraints of the PGAdmin pod. More info:
// https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node
affinity?: {
// Describes node affinity scheduling rules for the pod.
nodeAffinity?: {
// The scheduler will prefer to schedule pods to nodes that
// satisfy the affinity expressions specified by this field, but
// it may choose a node that violates one or more of the
// expressions. The node that is most preferred is the one with
// the greatest sum of weights, i.e. for each node that meets all
// of the scheduling requirements (resource request,
// requiredDuringScheduling affinity expressions, etc.), compute
// a sum by iterating through the elements of this field and
// adding "weight" to the sum if the node matches the
// corresponding matchExpressions; the node(s) with the highest
// sum are the most preferred.
preferredDuringSchedulingIgnoredDuringExecution?: [...{
// A node selector term, associated with the corresponding weight.
preference: {
// A list of node selector requirements by node's labels.
matchExpressions?: [...{
// The label key that the selector applies to.
key: string
// Represents a key's relationship to a set of values. Valid
// operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.
operator: string
// An array of string values. If the operator is In or NotIn, the
// values array must be non-empty. If the operator is Exists or
// DoesNotExist, the values array must be empty. If the operator
// is Gt or Lt, the values array must have a single element,
// which will be interpreted as an integer. This array is
// replaced during a strategic merge patch.
values?: [...string]
}]
// A list of node selector requirements by node's fields.
matchFields?: [...{
// The label key that the selector applies to.
key: string
// Represents a key's relationship to a set of values. Valid
// operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.
operator: string
// An array of string values. If the operator is In or NotIn, the
// values array must be non-empty. If the operator is Exists or
// DoesNotExist, the values array must be empty. If the operator
// is Gt or Lt, the values array must have a single element,
// which will be interpreted as an integer. This array is
// replaced during a strategic merge patch.
values?: [...string]
}]
}
// Weight associated with matching the corresponding
// nodeSelectorTerm, in the range 1-100.
weight: int
}]
requiredDuringSchedulingIgnoredDuringExecution?: {
// Required. A list of node selector terms. The terms are ORed.
nodeSelectorTerms: [...{
// A list of node selector requirements by node's labels.
matchExpressions?: [...{
// The label key that the selector applies to.
key: string
// Represents a key's relationship to a set of values. Valid
// operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.
operator: string
// An array of string values. If the operator is In or NotIn, the
// values array must be non-empty. If the operator is Exists or
// DoesNotExist, the values array must be empty. If the operator
// is Gt or Lt, the values array must have a single element,
// which will be interpreted as an integer. This array is
// replaced during a strategic merge patch.
values?: [...string]
}]
// A list of node selector requirements by node's fields.
matchFields?: [...{
// The label key that the selector applies to.
key: string
// Represents a key's relationship to a set of values. Valid
// operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.
operator: string
// An array of string values. If the operator is In or NotIn, the
// values array must be non-empty. If the operator is Exists or
// DoesNotExist, the values array must be empty. If the operator
// is Gt or Lt, the values array must have a single element,
// which will be interpreted as an integer. This array is
// replaced during a strategic merge patch.
values?: [...string]
}]
}]
}
}
// Describes pod affinity scheduling rules (e.g. co-locate this
// pod in the same node, zone, etc. as some other pod(s)).
podAffinity?: {
// The scheduler will prefer to schedule pods to nodes that
// satisfy the affinity expressions specified by this field, but
// it may choose a node that violates one or more of the
// expressions. The node that is most preferred is the one with
// the greatest sum of weights, i.e. for each node that meets all
// of the scheduling requirements (resource request,
// requiredDuringScheduling affinity expressions, etc.), compute
// a sum by iterating through the elements of this field and
// adding "weight" to the sum if the node has pods which matches
// the corresponding podAffinityTerm; the node(s) with the
// highest sum are the most preferred.
preferredDuringSchedulingIgnoredDuringExecution?: [...{
// Required. A pod affinity term, associated with the
// corresponding weight.
podAffinityTerm: {
// A label query over a set of resources, in this case pods.
labelSelector?: {
// matchExpressions is a list of label selector requirements. The
// requirements are ANDed.
matchExpressions?: [...{
// key is the label key that the selector applies to.
key: string
// operator represents a key's relationship to a set of values.
// Valid operators are In, NotIn, Exists and DoesNotExist.
operator: string
// values is an array of string values. If the operator is In or
// NotIn, the values array must be non-empty. If the operator is
// Exists or DoesNotExist, the values array must be empty. This
// array is replaced during a strategic merge patch.
values?: [...string]
}]
// matchLabels is a map of {key,value} pairs. A single {key,value}
// in the matchLabels map is equivalent to an element of
// matchExpressions, whose key field is "key", the operator is
// "In", and the values array contains only "value". The
// requirements are ANDed.
matchLabels?: {
[string]: string
}
}
// A label query over the set of namespaces that the term applies
// to. The term is applied to the union of the namespaces
// selected by this field and the ones listed in the namespaces
// field. null selector and null or empty namespaces list means
// "this pod's namespace". An empty selector ({}) matches all
// namespaces.
namespaceSelector?: {
// matchExpressions is a list of label selector requirements. The
// requirements are ANDed.
matchExpressions?: [...{
// key is the label key that the selector applies to.
key: string
// operator represents a key's relationship to a set of values.
// Valid operators are In, NotIn, Exists and DoesNotExist.
operator: string
// values is an array of string values. If the operator is In or
// NotIn, the values array must be non-empty. If the operator is
// Exists or DoesNotExist, the values array must be empty. This
// array is replaced during a strategic merge patch.
values?: [...string]
}]
// matchLabels is a map of {key,value} pairs. A single {key,value}
// in the matchLabels map is equivalent to an element of
// matchExpressions, whose key field is "key", the operator is
// "In", and the values array contains only "value". The
// requirements are ANDed.
matchLabels?: {
[string]: string
}
}
// namespaces specifies a static list of namespace names that the
// term applies to. The term is applied to the union of the
// namespaces listed in this field and the ones selected by
// namespaceSelector. null or empty namespaces list and null
// namespaceSelector means "this pod's namespace".
namespaces?: [...string]
// This pod should be co-located (affinity) or not co-located
// (anti-affinity) with the pods matching the labelSelector in
// the specified namespaces, where co-located is defined as
// running on a node whose value of the label with key
// topologyKey matches that of any node on which any of the
// selected pods is running. Empty topologyKey is not allowed.
topologyKey: string
}
// weight associated with matching the corresponding
// podAffinityTerm, in the range 1-100.
weight: int
}]
// If the affinity requirements specified by this field are not
// met at scheduling time, the pod will not be scheduled onto the
// node. If the affinity requirements specified by this field
// cease to be met at some point during pod execution (e.g. due
// to a pod label update), the system may or may not try to
// eventually evict the pod from its node. When there are
// multiple elements, the lists of nodes corresponding to each
// podAffinityTerm are intersected, i.e. all terms must be
// satisfied.
requiredDuringSchedulingIgnoredDuringExecution?: [...{
// A label query over a set of resources, in this case pods.
labelSelector?: {
// matchExpressions is a list of label selector requirements. The
// requirements are ANDed.
matchExpressions?: [...{
// key is the label key that the selector applies to.
key: string
// operator represents a key's relationship to a set of values.
// Valid operators are In, NotIn, Exists and DoesNotExist.
operator: string
// values is an array of string values. If the operator is In or
// NotIn, the values array must be non-empty. If the operator is
// Exists or DoesNotExist, the values array must be empty. This
// array is replaced during a strategic merge patch.
values?: [...string]
}]
// matchLabels is a map of {key,value} pairs. A single {key,value}
// in the matchLabels map is equivalent to an element of
// matchExpressions, whose key field is "key", the operator is
// "In", and the values array contains only "value". The
// requirements are ANDed.
matchLabels?: {
[string]: string
}
}
// A label query over the set of namespaces that the term applies
// to. The term is applied to the union of the namespaces
// selected by this field and the ones listed in the namespaces
// field. null selector and null or empty namespaces list means
// "this pod's namespace". An empty selector ({}) matches all
// namespaces.
namespaceSelector?: {
// matchExpressions is a list of label selector requirements. The
// requirements are ANDed.
matchExpressions?: [...{
// key is the label key that the selector applies to.
key: string
// operator represents a key's relationship to a set of values.
// Valid operators are In, NotIn, Exists and DoesNotExist.
operator: string
// values is an array of string values. If the operator is In or
// NotIn, the values array must be non-empty. If the operator is
// Exists or DoesNotExist, the values array must be empty. This
// array is replaced during a strategic merge patch.
values?: [...string]
}]
// matchLabels is a map of {key,value} pairs. A single {key,value}
// in the matchLabels map is equivalent to an element of
// matchExpressions, whose key field is "key", the operator is
// "In", and the values array contains only "value". The
// requirements are ANDed.
matchLabels?: {
[string]: string
}
}
// namespaces specifies a static list of namespace names that the
// term applies to. The term is applied to the union of the
// namespaces listed in this field and the ones selected by
// namespaceSelector. null or empty namespaces list and null
// namespaceSelector means "this pod's namespace".
namespaces?: [...string]
// This pod should be co-located (affinity) or not co-located
// (anti-affinity) with the pods matching the labelSelector in
// the specified namespaces, where co-located is defined as
// running on a node whose value of the label with key
// topologyKey matches that of any node on which any of the
// selected pods is running. Empty topologyKey is not allowed.
topologyKey: string
}]
}
// Describes pod anti-affinity scheduling rules (e.g. avoid
// putting this pod in the same node, zone, etc. as some other
// pod(s)).
podAntiAffinity?: {
// The scheduler will prefer to schedule pods to nodes that
// satisfy the anti-affinity expressions specified by this field,
// but it may choose a node that violates one or more of the
// expressions. The node that is most preferred is the one with
// the greatest sum of weights, i.e. for each node that meets all
// of the scheduling requirements (resource request,
// requiredDuringScheduling anti-affinity expressions, etc.),
// compute a sum by iterating through the elements of this field
// and adding "weight" to the sum if the node has pods which
// matches the corresponding podAffinityTerm; the node(s) with
// the highest sum are the most preferred.
preferredDuringSchedulingIgnoredDuringExecution?: [...{
// Required. A pod affinity term, associated with the
// corresponding weight.
podAffinityTerm: {
// A label query over a set of resources, in this case pods.
labelSelector?: {
// matchExpressions is a list of label selector requirements. The
// requirements are ANDed.
matchExpressions?: [...{
// key is the label key that the selector applies to.
key: string
// operator represents a key's relationship to a set of values.
// Valid operators are In, NotIn, Exists and DoesNotExist.
operator: string
// values is an array of string values. If the operator is In or
// NotIn, the values array must be non-empty. If the operator is
// Exists or DoesNotExist, the values array must be empty. This
// array is replaced during a strategic merge patch.
values?: [...string]
}]
// matchLabels is a map of {key,value} pairs. A single {key,value}
// in the matchLabels map is equivalent to an element of
// matchExpressions, whose key field is "key", the operator is
// "In", and the values array contains only "value". The
// requirements are ANDed.
matchLabels?: {
[string]: string
}
}
// A label query over the set of namespaces that the term applies
// to. The term is applied to the union of the namespaces
// selected by this field and the ones listed in the namespaces
// field. null selector and null or empty namespaces list means
// "this pod's namespace". An empty selector ({}) matches all
// namespaces.
namespaceSelector?: {
// matchExpressions is a list of label selector requirements. The
// requirements are ANDed.
matchExpressions?: [...{
// key is the label key that the selector applies to.
key: string
// operator represents a key's relationship to a set of values.
// Valid operators are In, NotIn, Exists and DoesNotExist.
operator: string
// values is an array of string values. If the operator is In or
// NotIn, the values array must be non-empty. If the operator is
// Exists or DoesNotExist, the values array must be empty. This
// array is replaced during a strategic merge patch.
values?: [...string]
}]
// matchLabels is a map of {key,value} pairs. A single {key,value}
// in the matchLabels map is equivalent to an element of
// matchExpressions, whose key field is "key", the operator is
// "In", and the values array contains only "value". The
// requirements are ANDed.
matchLabels?: {
[string]: string
}
}
// namespaces specifies a static list of namespace names that the
// term applies to. The term is applied to the union of the
// namespaces listed in this field and the ones selected by
// namespaceSelector. null or empty namespaces list and null
// namespaceSelector means "this pod's namespace".
namespaces?: [...string]
// This pod should be co-located (affinity) or not co-located
// (anti-affinity) with the pods matching the labelSelector in
// the specified namespaces, where co-located is defined as
// running on a node whose value of the label with key
// topologyKey matches that of any node on which any of the
// selected pods is running. Empty topologyKey is not allowed.
topologyKey: string
}
// weight associated with matching the corresponding
// podAffinityTerm, in the range 1-100.
weight: int
}]
// If the anti-affinity requirements specified by this field are
// not met at scheduling time, the pod will not be scheduled onto
// the node. If the anti-affinity requirements specified by this
// field cease to be met at some point during pod execution (e.g.
// due to a pod label update), the system may or may not try to
// eventually evict the pod from its node. When there are
// multiple elements, the lists of nodes corresponding to each
// podAffinityTerm are intersected, i.e. all terms must be
// satisfied.
requiredDuringSchedulingIgnoredDuringExecution?: [...{
// A label query over a set of resources, in this case pods.
labelSelector?: {
// matchExpressions is a list of label selector requirements. The
// requirements are ANDed.
matchExpressions?: [...{
// key is the label key that the selector applies to.
key: string
// operator represents a key's relationship to a set of values.
// Valid operators are In, NotIn, Exists and DoesNotExist.
operator: string
// values is an array of string values. If the operator is In or
// NotIn, the values array must be non-empty. If the operator is
// Exists or DoesNotExist, the values array must be empty. This
// array is replaced during a strategic merge patch.
values?: [...string]
}]
// matchLabels is a map of {key,value} pairs. A single {key,value}
// in the matchLabels map is equivalent to an element of
// matchExpressions, whose key field is "key", the operator is
// "In", and the values array contains only "value". The
// requirements are ANDed.
matchLabels?: {
[string]: string
}
}
// A label query over the set of namespaces that the term applies
// to. The term is applied to the union of the namespaces
// selected by this field and the ones listed in the namespaces
// field. null selector and null or empty namespaces list means
// "this pod's namespace". An empty selector ({}) matches all
// namespaces.
namespaceSelector?: {
// matchExpressions is a list of label selector requirements. The
// requirements are ANDed.
matchExpressions?: [...{
// key is the label key that the selector applies to.
key: string
// operator represents a key's relationship to a set of values.
// Valid operators are In, NotIn, Exists and DoesNotExist.
operator: string
// values is an array of string values. If the operator is In or
// NotIn, the values array must be non-empty. If the operator is
// Exists or DoesNotExist, the values array must be empty. This
// array is replaced during a strategic merge patch.
values?: [...string]
}]
// matchLabels is a map of {key,value} pairs. A single {key,value}
// in the matchLabels map is equivalent to an element of
// matchExpressions, whose key field is "key", the operator is
// "In", and the values array contains only "value". The
// requirements are ANDed.
matchLabels?: {
[string]: string
}
}
// namespaces specifies a static list of namespace names that the
// term applies to. The term is applied to the union of the
// namespaces listed in this field and the ones selected by
// namespaceSelector. null or empty namespaces list and null
// namespaceSelector means "this pod's namespace".
namespaces?: [...string]
// This pod should be co-located (affinity) or not co-located
// (anti-affinity) with the pods matching the labelSelector in
// the specified namespaces, where co-located is defined as
// running on a node whose value of the label with key
// topologyKey matches that of any node on which any of the
// selected pods is running. Empty topologyKey is not allowed.
topologyKey: string
}]
}
}
// Configuration settings for the pgAdmin process. Changes to any
// of these values will be loaded without validation. Be careful,
// as you may put pgAdmin into an unusable state.
config?: {
// Files allows the user to mount projected volumes into the
// pgAdmin container so that files can be referenced by pgAdmin
// as needed.
files?: [...{
// configMap information about the configMap data to project
configMap?: {
// items if unspecified, each key-value pair in the Data field of
// the referenced ConfigMap will be projected into the volume as
// a file whose name is the key and content is the value. If
// specified, the listed keys will be projected into the
// specified paths, and unlisted keys will not be present. If a
// key is specified which is not present in the ConfigMap, the
// volume setup will error unless it is marked optional. Paths
// must be relative and may not contain the '..' path or start
// with '..'.
items?: [...{
// key is the key to project.
key: string
// mode is Optional: mode bits used to set permissions on this
// file. Must be an octal value between 0000 and 0777 or a
// decimal value between 0 and 511. YAML accepts both octal and
// decimal values, JSON requires decimal values for mode bits. If
// not specified, the volume defaultMode will be used. This might
// be in conflict with other options that affect the file mode,
// like fsGroup, and the result can be other mode bits set.
mode?: int
// path is the relative path of the file to map the key to. May
// not be an absolute path. May not contain the path element
// '..'. May not start with the string '..'.
path: string
}]
// Name of the referent. More info:
// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
name?: string
// optional specify whether the ConfigMap or its keys must be
// defined
optional?: bool
}
downwardAPI?: {
// Items is a list of DownwardAPIVolume file
items?: [...{
// Required: Selects a field of the pod: only annotations, labels,
// name and namespace are supported.
fieldRef?: {
// Version of the schema the FieldPath is written in terms of,
// defaults to "v1".
apiVersion?: string
// Path of the field to select in the specified API version.
fieldPath: string
}
// Optional: mode bits used to set permissions on this file, must
// be an octal value between 0000 and 0777 or a decimal value
// between 0 and 511. YAML accepts both octal and decimal values,
// JSON requires decimal values for mode bits. If not specified,
// the volume defaultMode will be used. This might be in conflict
// with other options that affect the file mode, like fsGroup,
// and the result can be other mode bits set.
mode?: int
// Required: Path is the relative path name of the file to be
// created. Must not be absolute or contain the '..' path. Must
// be utf-8 encoded. The first item of the relative path must not
// start with '..'
path: string
// Selects a resource of the container: only resources limits and
// requests (limits.cpu, limits.memory, requests.cpu and
// requests.memory) are currently supported.
resourceFieldRef?: {
// Container name: required for volumes, optional for env vars
containerName?: string
// Specifies the output format of the exposed resources, defaults
// to "1"
divisor?: (int | string) & {
=~"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$"
}
// Required: resource to select
resource: string
}
}]
}
// secret information about the secret data to project
secret?: {
// items if unspecified, each key-value pair in the Data field of
// the referenced Secret will be projected into the volume as a
// file whose name is the key and content is the value. If
// specified, the listed keys will be projected into the
// specified paths, and unlisted keys will not be present. If a
// key is specified which is not present in the Secret, the
// volume setup will error unless it is marked optional. Paths
// must be relative and may not contain the '..' path or start
// with '..'.
items?: [...{
// key is the key to project.
key: string
// mode is Optional: mode bits used to set permissions on this
// file. Must be an octal value between 0000 and 0777 or a
// decimal value between 0 and 511. YAML accepts both octal and
// decimal values, JSON requires decimal values for mode bits. If
// not specified, the volume defaultMode will be used. This might
// be in conflict with other options that affect the file mode,
// like fsGroup, and the result can be other mode bits set.
mode?: int
// path is the relative path of the file to map the key to. May
// not be an absolute path. May not contain the path element
// '..'. May not start with the string '..'.
path: string
}]
// Name of the referent. More info:
// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
name?: string
// optional field specify whether the Secret or its key must be
// defined
optional?: bool
}
// serviceAccountToken is information about the
// serviceAccountToken data to project
serviceAccountToken?: {
// audience is the intended audience of the token. A recipient of
// a token must identify itself with an identifier specified in
// the audience of the token, and otherwise should reject the
// token. The audience defaults to the identifier of the
// apiserver.
audience?: string
// expirationSeconds is the requested duration of validity of the
// service account token. As the token approaches expiration, the
// kubelet volume plugin will proactively rotate the service
// account token. The kubelet will start trying to rotate the
// token if the token is older than 80 percent of its time to
// live or if the token is older than 24 hours.Defaults to 1 hour
// and must be at least 10 minutes.
expirationSeconds?: int
// path is the path relative to the mount point of the file to
// project the token into.
path: string
}
}]
// A Secret containing the value for the LDAP_BIND_PASSWORD
// setting. More info:
// https://www.pgadmin.org/docs/pgadmin4/latest/ldap.html
ldapBindPassword?: {
// The key of the secret to select from. Must be a valid secret
// key.
key: string
// Name of the referent. More info:
// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
name?: string
// Specify whether the Secret or its key must be defined
optional?: bool
}
// Settings for the pgAdmin server process. Keys should be
// uppercase and values must be constants. More info:
// https://www.pgadmin.org/docs/pgadmin4/latest/config_py.html
settings?: {
...
}
}
// Defines a PersistentVolumeClaim for pgAdmin data. More info:
// https://kubernetes.io/docs/concepts/storage/persistent-volumes
dataVolumeClaimSpec: {
// accessModes contains the desired access modes the volume should
// have. More info:
// https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1
accessModes?: [...string]
// dataSource field can be used to specify either: * An existing
// VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot)
// * An existing PVC (PersistentVolumeClaim) If the provisioner
// or an external controller can support the specified data
// source, it will create a new volume based on the contents of
// the specified data source. If the AnyVolumeDataSource feature
// gate is enabled, this field will always have the same contents
// as the DataSourceRef field.
dataSource?: {
// APIGroup is the group for the resource being referenced. If
// APIGroup is not specified, the specified Kind must be in the
// core API group. For any other third-party types, APIGroup is
// required.
apiGroup?: string
// Kind is the type of resource being referenced
kind: string
// Name is the name of resource being referenced
name: string
}
// dataSourceRef specifies the object from which to populate the
// volume with data, if a non-empty volume is desired. This may
// be any local object from a non-empty API group (non core
// object) or a PersistentVolumeClaim object. When this field is
// specified, volume binding will only succeed if the type of the
// specified object matches some installed volume populator or
// dynamic provisioner. This field will replace the functionality
// of the DataSource field and as such if both fields are
// non-empty, they must have the same value. For backwards
// compatibility, both fields (DataSource and DataSourceRef) will
// be set to the same value automatically if one of them is empty
// and the other is non-empty. There are two important
// differences between DataSource and DataSourceRef: * While
// DataSource only allows two specific types of objects,
// DataSourceRef allows any non-core object, as well as
// PersistentVolumeClaim objects. * While DataSource ignores
// disallowed values (dropping them), DataSourceRef preserves all
// values, and generates an error if a disallowed value is
// specified. (Beta) Using this field requires the
// AnyVolumeDataSource feature gate to be enabled.
dataSourceRef?: {
// APIGroup is the group for the resource being referenced. If
// APIGroup is not specified, the specified Kind must be in the
// core API group. For any other third-party types, APIGroup is
// required.
apiGroup?: string
// Kind is the type of resource being referenced
kind: string
// Name is the name of resource being referenced
name: string
}
// resources represents the minimum resources the volume should
// have. If RecoverVolumeExpansionFailure feature is enabled
// users are allowed to specify resource requirements that are
// lower than previous value but must still be higher than
// capacity recorded in the status field of the claim. More info:
// https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources
resources?: {
// Limits describes the maximum amount of compute resources
// allowed. More info:
// https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
limits?: {
[string]: (int | string) & =~"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$"
}
// Requests describes the minimum amount of compute resources
// required. If Requests is omitted for a container, it defaults
// to Limits if that is explicitly specified, otherwise to an
// implementation-defined value. More info:
// https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
requests?: {
[string]: (int | string) & =~"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$"
}
}
// selector is a label query over volumes to consider for binding.
selector?: {
// matchExpressions is a list of label selector requirements. The
// requirements are ANDed.
matchExpressions?: [...{
// key is the label key that the selector applies to.
key: string
// operator represents a key's relationship to a set of values.
// Valid operators are In, NotIn, Exists and DoesNotExist.
operator: string
// values is an array of string values. If the operator is In or
// NotIn, the values array must be non-empty. If the operator is
// Exists or DoesNotExist, the values array must be empty. This
// array is replaced during a strategic merge patch.
values?: [...string]
}]
// matchLabels is a map of {key,value} pairs. A single {key,value}
// in the matchLabels map is equivalent to an element of
// matchExpressions, whose key field is "key", the operator is
// "In", and the values array contains only "value". The
// requirements are ANDed.
matchLabels?: {
[string]: string
}
}
// storageClassName is the name of the StorageClass required by
// the claim. More info:
// https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1
storageClassName?: string
// volumeMode defines what type of volume is required by the
// claim. Value of Filesystem is implied when not included in
// claim spec.
volumeMode?: string
// volumeName is the binding reference to the PersistentVolume
// backing this claim.
volumeName?: string
}
// The image name to use for pgAdmin instance.
image?: string
// ImagePullPolicy is used to determine when Kubernetes will
// attempt to pull (download) container images. More info:
// https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy
imagePullPolicy?: "Always" | "Never" | "IfNotPresent"
// The image pull secrets used to pull from a private registry.
// Changing this value causes all running PGAdmin pods to
// restart.
// https://k8s.io/docs/tasks/configure-pod-container/pull-image-private-registry/
imagePullSecrets?: [...{
// Name of the referent. More info:
// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
name?: string
}]
// Metadata contains metadata for custom resources
metadata?: {
annotations?: {
[string]: string
}
labels?: {
[string]: string
}
}
// Priority class name for the PGAdmin pod. Changing this value
// causes PGAdmin pod to restart. More info:
// https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/
priorityClassName?: string
// Resource requirements for the PGAdmin container.
resources?: {
// Limits describes the maximum amount of compute resources
// allowed. More info:
// https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
limits?: {
[string]: (int | string) & =~"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$"
}
// Requests describes the minimum amount of compute resources
// required. If Requests is omitted for a container, it defaults
// to Limits if that is explicitly specified, otherwise to an
// implementation-defined value. More info:
// https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
requests?: {
[string]: (int | string) & =~"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$"
}
}
// ServerGroups for importing PostgresClusters to pgAdmin. To
// create a pgAdmin with no selectors, leave this field empty. A
// pgAdmin created with no `ServerGroups` will not automatically
// add any servers through discovery. PostgresClusters can still
// be added manually.
serverGroups?: [...{
// The name for the ServerGroup in pgAdmin. Must be unique in the
// pgAdmin's ServerGroups since it becomes the ServerGroup name
// in pgAdmin.
name: string
// PostgresClusterSelector selects clusters to dynamically add to
// pgAdmin by matching labels. An empty selector like `{}` will
// select ALL clusters in the namespace.
postgresClusterSelector: {
// matchExpressions is a list of label selector requirements. The
// requirements are ANDed.
matchExpressions?: [...{
// key is the label key that the selector applies to.
key: string
// operator represents a key's relationship to a set of values.
// Valid operators are In, NotIn, Exists and DoesNotExist.
operator: string
// values is an array of string values. If the operator is In or
// NotIn, the values array must be non-empty. If the operator is
// Exists or DoesNotExist, the values array must be empty. This
// array is replaced during a strategic merge patch.
values?: [...string]
}]
// matchLabels is a map of {key,value} pairs. A single {key,value}
// in the matchLabels map is equivalent to an element of
// matchExpressions, whose key field is "key", the operator is
// "In", and the values array contains only "value". The
// requirements are ANDed.
matchLabels?: {
[string]: string
}
}
}]
// Tolerations of the PGAdmin pod. More info:
// https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration
tolerations?: [...{
// Effect indicates the taint effect to match. Empty means match
// all taint effects. When specified, allowed values are
// NoSchedule, PreferNoSchedule and NoExecute.
effect?: string
// Key is the taint key that the toleration applies to. Empty
// means match all taint keys. If the key is empty, operator must
// be Exists; this combination means to match all values and all
// keys.
key?: string
// Operator represents a key's relationship to the value. Valid
// operators are Exists and Equal. Defaults to Equal. Exists is
// equivalent to wildcard for value, so that a pod can tolerate
// all taints of a particular category.
operator?: string
// TolerationSeconds represents the period of time the toleration
// (which must be of effect NoExecute, otherwise this field is
// ignored) tolerates the taint. By default, it is not set, which
// means tolerate the taint forever (do not evict). Zero and
// negative values will be treated as 0 (evict immediately) by
// the system.
tolerationSeconds?: int
// Value is the taint value the toleration matches to. If the
// operator is Exists, the value should be empty, otherwise just
// a regular string.
value?: string
}]
}

View File

@@ -0,0 +1,632 @@
// Code generated by timoni. DO NOT EDIT.
//timoni:generate timoni vendor crd -f /home/jeff/workspace/holos-run/holos-infra/deploy/clusters/core2/components/prod-pgo-crds/prod-pgo-crds.gen.yaml
package v1beta1
import "strings"
// PGUpgrade is the Schema for the pgupgrades API
#PGUpgrade: {
// APIVersion defines the versioned schema of this representation
// of an object. Servers should convert recognized schemas to the
// latest internal value, and may reject unrecognized values.
// More info:
// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
apiVersion: "postgres-operator.crunchydata.com/v1beta1"
// Kind is a string value representing the REST resource this
// object represents. Servers may infer this from the endpoint
// the client submits requests to. Cannot be updated. In
// CamelCase. More info:
// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
kind: "PGUpgrade"
metadata!: {
name!: strings.MaxRunes(253) & strings.MinRunes(1) & {
string
}
namespace!: strings.MaxRunes(63) & strings.MinRunes(1) & {
string
}
labels?: {
[string]: string
}
annotations?: {
[string]: string
}
}
// PGUpgradeSpec defines the desired state of PGUpgrade
spec!: #PGUpgradeSpec
}
// PGUpgradeSpec defines the desired state of PGUpgrade
#PGUpgradeSpec: {
// Scheduling constraints of the PGUpgrade pod. More info:
// https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node
affinity?: {
// Describes node affinity scheduling rules for the pod.
nodeAffinity?: {
// The scheduler will prefer to schedule pods to nodes that
// satisfy the affinity expressions specified by this field, but
// it may choose a node that violates one or more of the
// expressions. The node that is most preferred is the one with
// the greatest sum of weights, i.e. for each node that meets all
// of the scheduling requirements (resource request,
// requiredDuringScheduling affinity expressions, etc.), compute
// a sum by iterating through the elements of this field and
// adding "weight" to the sum if the node matches the
// corresponding matchExpressions; the node(s) with the highest
// sum are the most preferred.
preferredDuringSchedulingIgnoredDuringExecution?: [...{
// A node selector term, associated with the corresponding weight.
preference: {
// A list of node selector requirements by node's labels.
matchExpressions?: [...{
// The label key that the selector applies to.
key: string
// Represents a key's relationship to a set of values. Valid
// operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.
operator: string
// An array of string values. If the operator is In or NotIn, the
// values array must be non-empty. If the operator is Exists or
// DoesNotExist, the values array must be empty. If the operator
// is Gt or Lt, the values array must have a single element,
// which will be interpreted as an integer. This array is
// replaced during a strategic merge patch.
values?: [...string]
}]
// A list of node selector requirements by node's fields.
matchFields?: [...{
// The label key that the selector applies to.
key: string
// Represents a key's relationship to a set of values. Valid
// operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.
operator: string
// An array of string values. If the operator is In or NotIn, the
// values array must be non-empty. If the operator is Exists or
// DoesNotExist, the values array must be empty. If the operator
// is Gt or Lt, the values array must have a single element,
// which will be interpreted as an integer. This array is
// replaced during a strategic merge patch.
values?: [...string]
}]
}
// Weight associated with matching the corresponding
// nodeSelectorTerm, in the range 1-100.
weight: int
}]
requiredDuringSchedulingIgnoredDuringExecution?: {
// Required. A list of node selector terms. The terms are ORed.
nodeSelectorTerms: [...{
// A list of node selector requirements by node's labels.
matchExpressions?: [...{
// The label key that the selector applies to.
key: string
// Represents a key's relationship to a set of values. Valid
// operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.
operator: string
// An array of string values. If the operator is In or NotIn, the
// values array must be non-empty. If the operator is Exists or
// DoesNotExist, the values array must be empty. If the operator
// is Gt or Lt, the values array must have a single element,
// which will be interpreted as an integer. This array is
// replaced during a strategic merge patch.
values?: [...string]
}]
// A list of node selector requirements by node's fields.
matchFields?: [...{
// The label key that the selector applies to.
key: string
// Represents a key's relationship to a set of values. Valid
// operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.
operator: string
// An array of string values. If the operator is In or NotIn, the
// values array must be non-empty. If the operator is Exists or
// DoesNotExist, the values array must be empty. If the operator
// is Gt or Lt, the values array must have a single element,
// which will be interpreted as an integer. This array is
// replaced during a strategic merge patch.
values?: [...string]
}]
}]
}
}
// Describes pod affinity scheduling rules (e.g. co-locate this
// pod in the same node, zone, etc. as some other pod(s)).
podAffinity?: {
// The scheduler will prefer to schedule pods to nodes that
// satisfy the affinity expressions specified by this field, but
// it may choose a node that violates one or more of the
// expressions. The node that is most preferred is the one with
// the greatest sum of weights, i.e. for each node that meets all
// of the scheduling requirements (resource request,
// requiredDuringScheduling affinity expressions, etc.), compute
// a sum by iterating through the elements of this field and
// adding "weight" to the sum if the node has pods which matches
// the corresponding podAffinityTerm; the node(s) with the
// highest sum are the most preferred.
preferredDuringSchedulingIgnoredDuringExecution?: [...{
// Required. A pod affinity term, associated with the
// corresponding weight.
podAffinityTerm: {
// A label query over a set of resources, in this case pods.
labelSelector?: {
// matchExpressions is a list of label selector requirements. The
// requirements are ANDed.
matchExpressions?: [...{
// key is the label key that the selector applies to.
key: string
// operator represents a key's relationship to a set of values.
// Valid operators are In, NotIn, Exists and DoesNotExist.
operator: string
// values is an array of string values. If the operator is In or
// NotIn, the values array must be non-empty. If the operator is
// Exists or DoesNotExist, the values array must be empty. This
// array is replaced during a strategic merge patch.
values?: [...string]
}]
// matchLabels is a map of {key,value} pairs. A single {key,value}
// in the matchLabels map is equivalent to an element of
// matchExpressions, whose key field is "key", the operator is
// "In", and the values array contains only "value". The
// requirements are ANDed.
matchLabels?: {
[string]: string
}
}
// A label query over the set of namespaces that the term applies
// to. The term is applied to the union of the namespaces
// selected by this field and the ones listed in the namespaces
// field. null selector and null or empty namespaces list means
// "this pod's namespace". An empty selector ({}) matches all
// namespaces.
namespaceSelector?: {
// matchExpressions is a list of label selector requirements. The
// requirements are ANDed.
matchExpressions?: [...{
// key is the label key that the selector applies to.
key: string
// operator represents a key's relationship to a set of values.
// Valid operators are In, NotIn, Exists and DoesNotExist.
operator: string
// values is an array of string values. If the operator is In or
// NotIn, the values array must be non-empty. If the operator is
// Exists or DoesNotExist, the values array must be empty. This
// array is replaced during a strategic merge patch.
values?: [...string]
}]
// matchLabels is a map of {key,value} pairs. A single {key,value}
// in the matchLabels map is equivalent to an element of
// matchExpressions, whose key field is "key", the operator is
// "In", and the values array contains only "value". The
// requirements are ANDed.
matchLabels?: {
[string]: string
}
}
// namespaces specifies a static list of namespace names that the
// term applies to. The term is applied to the union of the
// namespaces listed in this field and the ones selected by
// namespaceSelector. null or empty namespaces list and null
// namespaceSelector means "this pod's namespace".
namespaces?: [...string]
// This pod should be co-located (affinity) or not co-located
// (anti-affinity) with the pods matching the labelSelector in
// the specified namespaces, where co-located is defined as
// running on a node whose value of the label with key
// topologyKey matches that of any node on which any of the
// selected pods is running. Empty topologyKey is not allowed.
topologyKey: string
}
// weight associated with matching the corresponding
// podAffinityTerm, in the range 1-100.
weight: int
}]
// If the affinity requirements specified by this field are not
// met at scheduling time, the pod will not be scheduled onto the
// node. If the affinity requirements specified by this field
// cease to be met at some point during pod execution (e.g. due
// to a pod label update), the system may or may not try to
// eventually evict the pod from its node. When there are
// multiple elements, the lists of nodes corresponding to each
// podAffinityTerm are intersected, i.e. all terms must be
// satisfied.
requiredDuringSchedulingIgnoredDuringExecution?: [...{
// A label query over a set of resources, in this case pods.
labelSelector?: {
// matchExpressions is a list of label selector requirements. The
// requirements are ANDed.
matchExpressions?: [...{
// key is the label key that the selector applies to.
key: string
// operator represents a key's relationship to a set of values.
// Valid operators are In, NotIn, Exists and DoesNotExist.
operator: string
// values is an array of string values. If the operator is In or
// NotIn, the values array must be non-empty. If the operator is
// Exists or DoesNotExist, the values array must be empty. This
// array is replaced during a strategic merge patch.
values?: [...string]
}]
// matchLabels is a map of {key,value} pairs. A single {key,value}
// in the matchLabels map is equivalent to an element of
// matchExpressions, whose key field is "key", the operator is
// "In", and the values array contains only "value". The
// requirements are ANDed.
matchLabels?: {
[string]: string
}
}
// A label query over the set of namespaces that the term applies
// to. The term is applied to the union of the namespaces
// selected by this field and the ones listed in the namespaces
// field. null selector and null or empty namespaces list means
// "this pod's namespace". An empty selector ({}) matches all
// namespaces.
namespaceSelector?: {
// matchExpressions is a list of label selector requirements. The
// requirements are ANDed.
matchExpressions?: [...{
// key is the label key that the selector applies to.
key: string
// operator represents a key's relationship to a set of values.
// Valid operators are In, NotIn, Exists and DoesNotExist.
operator: string
// values is an array of string values. If the operator is In or
// NotIn, the values array must be non-empty. If the operator is
// Exists or DoesNotExist, the values array must be empty. This
// array is replaced during a strategic merge patch.
values?: [...string]
}]
// matchLabels is a map of {key,value} pairs. A single {key,value}
// in the matchLabels map is equivalent to an element of
// matchExpressions, whose key field is "key", the operator is
// "In", and the values array contains only "value". The
// requirements are ANDed.
matchLabels?: {
[string]: string
}
}
// namespaces specifies a static list of namespace names that the
// term applies to. The term is applied to the union of the
// namespaces listed in this field and the ones selected by
// namespaceSelector. null or empty namespaces list and null
// namespaceSelector means "this pod's namespace".
namespaces?: [...string]
// This pod should be co-located (affinity) or not co-located
// (anti-affinity) with the pods matching the labelSelector in
// the specified namespaces, where co-located is defined as
// running on a node whose value of the label with key
// topologyKey matches that of any node on which any of the
// selected pods is running. Empty topologyKey is not allowed.
topologyKey: string
}]
}
// Describes pod anti-affinity scheduling rules (e.g. avoid
// putting this pod in the same node, zone, etc. as some other
// pod(s)).
podAntiAffinity?: {
// The scheduler will prefer to schedule pods to nodes that
// satisfy the anti-affinity expressions specified by this field,
// but it may choose a node that violates one or more of the
// expressions. The node that is most preferred is the one with
// the greatest sum of weights, i.e. for each node that meets all
// of the scheduling requirements (resource request,
// requiredDuringScheduling anti-affinity expressions, etc.),
// compute a sum by iterating through the elements of this field
// and adding "weight" to the sum if the node has pods which
// matches the corresponding podAffinityTerm; the node(s) with
// the highest sum are the most preferred.
preferredDuringSchedulingIgnoredDuringExecution?: [...{
// Required. A pod affinity term, associated with the
// corresponding weight.
podAffinityTerm: {
// A label query over a set of resources, in this case pods.
labelSelector?: {
// matchExpressions is a list of label selector requirements. The
// requirements are ANDed.
matchExpressions?: [...{
// key is the label key that the selector applies to.
key: string
// operator represents a key's relationship to a set of values.
// Valid operators are In, NotIn, Exists and DoesNotExist.
operator: string
// values is an array of string values. If the operator is In or
// NotIn, the values array must be non-empty. If the operator is
// Exists or DoesNotExist, the values array must be empty. This
// array is replaced during a strategic merge patch.
values?: [...string]
}]
// matchLabels is a map of {key,value} pairs. A single {key,value}
// in the matchLabels map is equivalent to an element of
// matchExpressions, whose key field is "key", the operator is
// "In", and the values array contains only "value". The
// requirements are ANDed.
matchLabels?: {
[string]: string
}
}
// A label query over the set of namespaces that the term applies
// to. The term is applied to the union of the namespaces
// selected by this field and the ones listed in the namespaces
// field. null selector and null or empty namespaces list means
// "this pod's namespace". An empty selector ({}) matches all
// namespaces.
namespaceSelector?: {
// matchExpressions is a list of label selector requirements. The
// requirements are ANDed.
matchExpressions?: [...{
// key is the label key that the selector applies to.
key: string
// operator represents a key's relationship to a set of values.
// Valid operators are In, NotIn, Exists and DoesNotExist.
operator: string
// values is an array of string values. If the operator is In or
// NotIn, the values array must be non-empty. If the operator is
// Exists or DoesNotExist, the values array must be empty. This
// array is replaced during a strategic merge patch.
values?: [...string]
}]
// matchLabels is a map of {key,value} pairs. A single {key,value}
// in the matchLabels map is equivalent to an element of
// matchExpressions, whose key field is "key", the operator is
// "In", and the values array contains only "value". The
// requirements are ANDed.
matchLabels?: {
[string]: string
}
}
// namespaces specifies a static list of namespace names that the
// term applies to. The term is applied to the union of the
// namespaces listed in this field and the ones selected by
// namespaceSelector. null or empty namespaces list and null
// namespaceSelector means "this pod's namespace".
namespaces?: [...string]
// This pod should be co-located (affinity) or not co-located
// (anti-affinity) with the pods matching the labelSelector in
// the specified namespaces, where co-located is defined as
// running on a node whose value of the label with key
// topologyKey matches that of any node on which any of the
// selected pods is running. Empty topologyKey is not allowed.
topologyKey: string
}
// weight associated with matching the corresponding
// podAffinityTerm, in the range 1-100.
weight: int
}]
// If the anti-affinity requirements specified by this field are
// not met at scheduling time, the pod will not be scheduled onto
// the node. If the anti-affinity requirements specified by this
// field cease to be met at some point during pod execution (e.g.
// due to a pod label update), the system may or may not try to
// eventually evict the pod from its node. When there are
// multiple elements, the lists of nodes corresponding to each
// podAffinityTerm are intersected, i.e. all terms must be
// satisfied.
requiredDuringSchedulingIgnoredDuringExecution?: [...{
// A label query over a set of resources, in this case pods.
labelSelector?: {
// matchExpressions is a list of label selector requirements. The
// requirements are ANDed.
matchExpressions?: [...{
// key is the label key that the selector applies to.
key: string
// operator represents a key's relationship to a set of values.
// Valid operators are In, NotIn, Exists and DoesNotExist.
operator: string
// values is an array of string values. If the operator is In or
// NotIn, the values array must be non-empty. If the operator is
// Exists or DoesNotExist, the values array must be empty. This
// array is replaced during a strategic merge patch.
values?: [...string]
}]
// matchLabels is a map of {key,value} pairs. A single {key,value}
// in the matchLabels map is equivalent to an element of
// matchExpressions, whose key field is "key", the operator is
// "In", and the values array contains only "value". The
// requirements are ANDed.
matchLabels?: {
[string]: string
}
}
// A label query over the set of namespaces that the term applies
// to. The term is applied to the union of the namespaces
// selected by this field and the ones listed in the namespaces
// field. null selector and null or empty namespaces list means
// "this pod's namespace". An empty selector ({}) matches all
// namespaces.
namespaceSelector?: {
// matchExpressions is a list of label selector requirements. The
// requirements are ANDed.
matchExpressions?: [...{
// key is the label key that the selector applies to.
key: string
// operator represents a key's relationship to a set of values.
// Valid operators are In, NotIn, Exists and DoesNotExist.
operator: string
// values is an array of string values. If the operator is In or
// NotIn, the values array must be non-empty. If the operator is
// Exists or DoesNotExist, the values array must be empty. This
// array is replaced during a strategic merge patch.
values?: [...string]
}]
// matchLabels is a map of {key,value} pairs. A single {key,value}
// in the matchLabels map is equivalent to an element of
// matchExpressions, whose key field is "key", the operator is
// "In", and the values array contains only "value". The
// requirements are ANDed.
matchLabels?: {
[string]: string
}
}
// namespaces specifies a static list of namespace names that the
// term applies to. The term is applied to the union of the
// namespaces listed in this field and the ones selected by
// namespaceSelector. null or empty namespaces list and null
// namespaceSelector means "this pod's namespace".
namespaces?: [...string]
// This pod should be co-located (affinity) or not co-located
// (anti-affinity) with the pods matching the labelSelector in
// the specified namespaces, where co-located is defined as
// running on a node whose value of the label with key
// topologyKey matches that of any node on which any of the
// selected pods is running. Empty topologyKey is not allowed.
topologyKey: string
}]
}
}
// The major version of PostgreSQL before the upgrade.
fromPostgresVersion: uint & >=10 & <=16
// The image name to use for major PostgreSQL upgrades.
image?: string
// ImagePullPolicy is used to determine when Kubernetes will
// attempt to pull (download) container images. More info:
// https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy
imagePullPolicy?: "Always" | "Never" | "IfNotPresent"
// The image pull secrets used to pull from a private registry.
// Changing this value causes all running PGUpgrade pods to
// restart.
// https://k8s.io/docs/tasks/configure-pod-container/pull-image-private-registry/
imagePullSecrets?: [...{
// Name of the referent. More info:
// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
name?: string
}]
// Metadata contains metadata for custom resources
metadata?: {
annotations?: {
[string]: string
}
labels?: {
[string]: string
}
}
// The name of the cluster to be updated
postgresClusterName: strings.MinRunes(1)
// Priority class name for the PGUpgrade pod. Changing this value
// causes PGUpgrade pod to restart. More info:
// https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/
priorityClassName?: string
// Resource requirements for the PGUpgrade container.
resources?: {
// Limits describes the maximum amount of compute resources
// allowed. More info:
// https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
limits?: {
[string]: (int | string) & =~"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$"
}
// Requests describes the minimum amount of compute resources
// required. If Requests is omitted for a container, it defaults
// to Limits if that is explicitly specified, otherwise to an
// implementation-defined value. More info:
// https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
requests?: {
[string]: (int | string) & =~"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$"
}
}
// The image name to use for PostgreSQL containers after upgrade.
// When omitted, the value comes from an operator environment
// variable.
toPostgresImage?: string
// The major version of PostgreSQL to be upgraded to.
toPostgresVersion: uint & >=10 & <=16
// Tolerations of the PGUpgrade pod. More info:
// https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration
tolerations?: [...{
// Effect indicates the taint effect to match. Empty means match
// all taint effects. When specified, allowed values are
// NoSchedule, PreferNoSchedule and NoExecute.
effect?: string
// Key is the taint key that the toleration applies to. Empty
// means match all taint keys. If the key is empty, operator must
// be Exists; this combination means to match all values and all
// keys.
key?: string
// Operator represents a key's relationship to the value. Valid
// operators are Exists and Equal. Defaults to Equal. Exists is
// equivalent to wildcard for value, so that a pod can tolerate
// all taints of a particular category.
operator?: string
// TolerationSeconds represents the period of time the toleration
// (which must be of effect NoExecute, otherwise this field is
// ignored) tolerates the taint. By default, it is not set, which
// means tolerate the taint forever (do not evict). Zero and
// negative values will be treated as 0 (evict immediately) by
// the system.
tolerationSeconds?: int
// Value is the taint value the toleration matches to. If the
// operator is Exists, the value should be empty, otherwise just
// a regular string.
value?: string
}]
}

View File

@@ -0,0 +1,8 @@
package v1alpha1
// #BuildPlan is the API contract between CUE and the Holos cli.
// Holos requires CUE to evaluate and provide a valid #BuildPlan.
#BuildPlan: {
kind: #BuildPlanKind
apiVersion: #APIVersion
}

View File

@@ -0,0 +1 @@
package v1alpha1

View File

@@ -0,0 +1,5 @@
package v1alpha1
#HolosComponent: Skip: true | *false
#HelmChart: enableHooks: true | *false

35
docs/examples/helpers.cue Normal file
View File

@@ -0,0 +1,35 @@
package holos
import "encoding/yaml"
// #APIObjects is the output type for api objects produced by cue.
#APIObjects: {
// apiObjects holds each the api objects produced by cue.
apiObjects: {
[Kind=_]: {
[string]: {
kind: Kind
...
}
}
Namespace?: [Name=_]: #Namespace & {metadata: name: Name}
SecretStore?: [Name=_]: #SecretStore & {_namespace: Name}
ExternalSecret?: [Name=_]: #ExternalSecret & {_name: Name}
VirtualService?: [Name=_]: #VirtualService & {metadata: name: Name}
Issuer?: [Name=_]: #Issuer & {metadata: name: Name}
Gateway?: [Name=_]: #Gateway & {metadata: name: Name}
Certificate?: [Name=_]: #Certificate & {metadata: name: Name}
}
// apiObjectMap holds the marshalled representation of apiObjects
apiObjectMap: {
for kind, v in apiObjects {
"\(kind)": {
for name, obj in v {
"\(name)": yaml.Marshal(obj)
}
}
}
...
}
}

117
docs/examples/holos.cue Normal file
View File

@@ -0,0 +1,117 @@
package holos
import (
"encoding/yaml"
h "github.com/holos-run/holos/api/v1alpha1"
kc "sigs.k8s.io/kustomize/api/types"
ksv1 "kustomize.toolkit.fluxcd.io/kustomization/v1"
)
// The overall structure of the data is:
// 1 CUE Instance => 1 BuildPlan => 0..N HolosComponents
// Holos requires CUE to evaluate and provide a valid BuildPlan.
// Constrain each CUE instance to output a BuildPlan.
{} & h.#BuildPlan
let DependsOn = {[Name=_]: name: string & Name}
// #HolosComponent defines struct fields common to all holos component types.
#HolosComponent: {
h.#HolosComponent
_dependsOn: DependsOn
let DEPENDS_ON = _dependsOn
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
})
// Leave the HolosComponent open for components with additional fields like HelmChart.
// Refer to https://cuelang.org/docs/tour/types/closed/
...
}
//#KustomizeFiles represents resources for holos to write into files for kustomize post-processing.
#KustomizeFiles: {
// Objects collects files for Holos to write for kustomize post-processing.
Objects: "kustomization.yaml": #Kustomize
// Files holds the marshaled output of Objects holos writes to the filesystem before calling the kustomize post-processor.
Files: {
for filename, obj in Objects {
"\(filename)": yaml.Marshal(obj)
}
}
}
// Holos component types.
#HelmChart: #HolosComponent & h.#HelmChart & {
_values: {...}
_kustomizeFiles: #KustomizeFiles
// Render the values to yaml for holos to provide to helm.
valuesContent: yaml.Marshal(_values)
// Kustomize post-processor
// resources is the intermediate file name for api objects.
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
// #ClusterName is the cluster name for cluster scoped resources.
#ClusterName: #InputKeys.cluster
// Flux Kustomization CRDs
#Kustomization: #NamespaceObject & ksv1.#Kustomization & {
_dependsOn: DependsOn
metadata: {
name: string
namespace: string | *"flux-system"
}
spec: ksv1.#KustomizationSpec & {
interval: string | *"30m0s"
path: string | *"deploy/clusters/\(#InputKeys.cluster)/components/\(metadata.name)"
prune: bool | *true
retryInterval: string | *"2m0s"
sourceRef: {
kind: string | *"GitRepository"
name: string | *"flux-system"
}
suspend?: bool
targetNamespace?: string
timeout: string | *"3m0s"
// wait performs health checks for all reconciled resources. If set to true, .spec.healthChecks is ignored.
// Setting this to true for all components generates considerable load on the api server from watches.
// Operations are additionally more complicated when all resources are watched. Consider setting wait true for
// relatively simple components, otherwise target specific resources with spec.healthChecks.
wait: true | *false
dependsOn: [for k, v in _dependsOn {v}, ...]
}
}
// #Kustomize represents the kustomize post processor.
#Kustomize: kc.#Kustomization & {
_patches: {[_]: kc.#Patch}
apiVersion: "kustomize.config.k8s.io/v1beta1"
kind: "Kustomization"
// resources are file names holos will use to store intermediate component output for kustomize to post-process (i.e. helm template | kubectl kustomize)
// See the related resourcesFile field of the holos component.
resources: [h.#ResourcesFile]
if len(_patches) > 0 {
patches: [for v in _patches {v}]
}
}
// So components don't need to import the package.
#Patch: kc.#Patch

View File

@@ -0,0 +1,46 @@
// Controls optional feature flags for services distributed across multiple holos components.
// For example, enable issuing certificates in the provisioner cluster when an optional service is
// enabled for a workload cluster.
package holos
import "list"
#OptionalService: {
name: string
enabled: true | *false
clusters: [Name=_]: #Platform.clusters[Name]
clusterNames: [for c in clusters {c.name}]
managedNamespaces: [Name=_]: #ManagedNamespace & {
namespace: metadata: name: Name
clusterNames: ["provisioner", for c in clusters {c.name}]
}
// servers represents istio Gateway.spec.servers.hosts entries
// Refer to istio/gateway/gateway.cue
servers: [Name=_]: {
hosts: [...string]
port: name: Name
port: number: 443
port: protocol: "HTTPS"
tls: credentialName: string
tls: mode: "SIMPLE"
}
// public tls certs should align to hosts.
certs: [Name=_]: #Certificate & {
metadata: name: Name
}
}
#OptionalServices: {
[Name=_]: #OptionalService & {
name: Name
}
}
for svc in #OptionalServices {
for nsName, ns in svc.managedNamespaces {
if svc.enabled && list.Contains(ns.clusterNames, #ClusterName) {
#ManagedNamespaces: "\(nsName)": ns
}
}
}

View File

@@ -0,0 +1,56 @@
package holos
let CoreDomain = "core.\(#Platform.org.domain)"
let TargetNamespace = "prod-core-vault"
#OptionalServices: {
vault: {
enabled: true
clusters: core1: _
clusters: core2: _
managedNamespaces: "prod-core-vault": {
namespace: metadata: labels: "istio-injection": "enabled"
}
certs: "vault-core": #Certificate & {
metadata: name: "vault-core"
metadata: namespace: "istio-ingress"
spec: {
commonName: "vault.\(CoreDomain)"
dnsNames: [commonName]
secretName: metadata.name
issuerRef: kind: "ClusterIssuer"
issuerRef: name: string | *"letsencrypt"
}
}
servers: "https-vault-core": {
hosts: ["\(TargetNamespace)/vault.\(CoreDomain)"]
tls: credentialName: certs."vault-core".spec.secretName
}
for k, v in clusters {
let obj = (Cert & {Name: "vault-core", Cluster: v.name}).APIObject
certs: "\(obj.metadata.name)": obj
servers: "https-\(obj.metadata.name)": {
hosts: [for host in obj.spec.dnsNames {"\(TargetNamespace)/\(host)"}]
tls: credentialName: obj.spec.secretName
}
}
}
}
// Cert provisions a cluster specific certificate.
let Cert = {
Name: string
Cluster: string
APIObject: #Certificate & {
metadata: name: "\(Cluster)-\(Name)"
metadata: namespace: string | *"istio-ingress"
spec: {
commonName: string | *"vault.\(Cluster).\(CoreDomain)"
dnsNames: [commonName]
secretName: metadata.name
issuerRef: kind: "ClusterIssuer"
issuerRef: name: string | *"letsencrypt"
}
}
}

View File

@@ -44,7 +44,8 @@ package holos
_name: string
_cluster: string
_wildcard: true | *false
metadata: name: string | *"\(_cluster)-\(_name)"
// Enforce this value
metadata: name: "\(_cluster)-\(_name)"
metadata: namespace: string | *"istio-ingress"
spec: {
commonName: string | *"\(_name).\(_cluster).\(#Platform.org.domain)"

View File

@@ -4,7 +4,4 @@ package holos
#InputKeys: project: "iam"
// Shared dependencies for all components in this collection.
#DependsOn: _Namespaces
// Common Dependencies
_Namespaces: Namespaces: name: "\(#StageName)-secrets-namespaces"
#DependsOn: namespaces: name: "\(#StageName)-secrets-namespaces"

View File

@@ -0,0 +1,39 @@
package holos
#InputKeys: component: "postgres-certs"
let SecretNames = {
[Name=_]: {name: Name}
"\(_DBName)-primary-tls": _
"\(_DBName)-repl-tls": _
"\(_DBName)-client-tls": _
"\(_DBName)-root-ca": _
}
#Kustomization: spec: targetNamespace: #TargetNamespace
#Kustomization: spec: healthChecks: [
for s in SecretNames {
apiVersion: "external-secrets.io/v1beta1"
kind: "ExternalSecret"
name: s.name
namespace: #TargetNamespace
},
]
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

@@ -0,0 +1,199 @@
package holos
#InputKeys: component: "postgres"
#DependsOn: "postgres-certs": _
let Cluster = #Platform.clusters[#ClusterName]
let S3Secret = "pgo-s3-creds"
let ZitadelUser = _DBName
let ZitadelAdmin = "\(_DBName)-admin"
// This must be an external storage bucket for our architecture.
let BucketRepoName = "repo2"
// Restore options. Set the timestamp to a known good point in time.
// time="2024-03-11T17:08:58Z" level=info msg="crunchy-pgbackrest ends"
// let RestoreOptions = ["--type=time", "--target=\"2024-03-11 17:10:00+00\""]
// Restore the most recent backup.
let RestoreOptions = []
#Kustomization: spec: healthChecks: [
{
apiVersion: "external-secrets.io/v1beta1"
kind: "ExternalSecret"
name: S3Secret
namespace: #TargetNamespace
},
{
apiVersion: "postgres-operator.crunchydata.com/v1beta1"
kind: "PostgresCluster"
name: _DBName
namespace: #TargetNamespace
},
]
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 & {
metadata: name: _DBName
metadata: namespace: #TargetNamespace
spec: {
image: "registry.developers.crunchydata.com/crunchydata/crunchy-postgres:ubi8-16.2-0"
postgresVersion: 16
// Custom certs are necessary for streaming standby replication which we use to replicate between two regions.
// Refer to https://access.crunchydata.com/documentation/postgres-operator/latest/tutorials/backups-disaster-recovery/disaster-recovery#streaming-standby
customTLSSecret: name: "\(_DBName)-primary-tls"
customReplicationTLSSecret: name: "\(_DBName)-repl-tls"
// Refer to https://access.crunchydata.com/documentation/postgres-operator/latest/references/crd/5.5.x/postgrescluster#postgresclusterspecusersindex
users: [
{name: ZitadelUser},
// NOTE: Users with SUPERUSER role cannot log in through pgbouncer. Use options that allow zitadel admin to use pgbouncer.
// Refer to: https://github.com/CrunchyData/postgres-operator/issues/3095#issuecomment-1904712211
{name: ZitadelAdmin, options: "CREATEDB CREATEROLE", databases: [_DBName, "postgres"]},
]
users: [...{databases: [_DBName, ...]}]
instances: [{
replicas: 2
dataVolumeClaimSpec: {
accessModes: ["ReadWriteOnce"]
resources: requests: storage: "10Gi"
}
}]
standby: {
repoName: BucketRepoName
if Cluster.primary {
enabled: false
}
if !Cluster.primary {
enabled: true
}
}
// Restore from backup if and only if the cluster is primary
if Cluster.primary {
dataSource: pgbackrest: {
stanza: "db"
configuration: backups.pgbackrest.configuration
// Restore from known good full backup taken
options: RestoreOptions
global: {
"\(BucketRepoName)-path": "/pgbackrest/\(#TargetNamespace)/\(metadata.name)/\(BucketRepoName)"
"\(BucketRepoName)-cipher-type": "aes-256-cbc"
}
repo: {
name: BucketRepoName
s3: backups.pgbackrest.repos[1].s3
}
}
}
// Refer to https://access.crunchydata.com/documentation/postgres-operator/latest/tutorials/backups-disaster-recovery/backups
backups: pgbackrest: {
configuration: [{secret: name: S3Secret}]
// Defines details for manual pgBackRest backup Jobs
manual: {
// Note: the repoName value must match the config keys in the S3Secret.
// This must be an external repository for backup / restore / regional failovers.
repoName: BucketRepoName
options: ["--type=full", ...]
}
// Defines details for performing an in-place restore using pgBackRest
restore: {
// Enables triggering a restore by annotating the postgrescluster with postgres-operator.crunchydata.com/pgbackrest-restore="$(date)"
enabled: true
repoName: BucketRepoName
}
global: {
// Store only one full backup in the PV because it's more expensive than object storage.
"\(repos[0].name)-retention-full": "1"
// Store 14 days of full backups in the bucket.
"\(BucketRepoName)-retention-full": string | *"14"
"\(BucketRepoName)-retention-full-type": "count" | *"time" // time in days
// Refer to https://access.crunchydata.com/documentation/postgres-operator/latest/tutorials/backups-disaster-recovery/backups#encryption
"\(BucketRepoName)-cipher-type": "aes-256-cbc"
// "The convention we recommend for setting this variable is /pgbackrest/$NAMESPACE/$CLUSTER_NAME/repoN"
// Ref: https://access.crunchydata.com/documentation/postgres-operator/latest/tutorials/backups-disaster-recovery/backups#understanding-backup-configuration-and-basic-operations
"\(BucketRepoName)-path": "/pgbackrest/\(#TargetNamespace)/\(metadata.name)/\(manual.repoName)"
}
repos: [
{
name: "repo1"
volume: volumeClaimSpec: {
accessModes: ["ReadWriteOnce"]
resources: requests: storage: string | *"4Gi"
}
},
{
name: BucketRepoName
// Full backup weekly on Sunday at 1am, differntial daily at 1am every day except Sunday.
schedules: full: string | *"0 1 * * 0"
schedules: differential: string | *"0 1 * * 1-6"
s3: {
bucket: string | *"\(#Platform.org.name)-zitadel-backups"
region: string | *#Backups.s3.region
endpoint: string | *"s3.dualstack.\(region).amazonaws.com"
}
},
]
}
}
}
}
}
// Refer to https://github.com/holos-run/postgres-operator-examples/blob/main/kustomize/high-availability/ha-postgres.yaml
let HighlyAvailable = {
apiVersion: "postgres-operator.crunchydata.com/v1beta1"
kind: "PostgresCluster"
metadata: name: string
spec: {
image: "registry.developers.crunchydata.com/crunchydata/crunchy-postgres:ubi8-16.2-0"
postgresVersion: 16
instances: [{
name: "pgha1"
replicas: 2
dataVolumeClaimSpec: {
accessModes: ["ReadWriteOnce"]
resources: requests: storage: string | *"10Gi"
}
affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: [{
weight: 1
podAffinityTerm: {
topologyKey: "kubernetes.io/hostname"
labelSelector: matchLabels: {
"postgres-operator.crunchydata.com/cluster": metadata.name
"postgres-operator.crunchydata.com/instance-set": name
}
}
}]
}]
backups: pgbackrest: {
image: "registry.developers.crunchydata.com/crunchydata/crunchy-pgbackrest:ubi8-2.49-0"
}
proxy: pgBouncer: {
image: "registry.developers.crunchydata.com/crunchydata/crunchy-pgbouncer:ubi8-1.21-3"
replicas: 2
affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: [{
weight: 1
podAffinityTerm: {
topologyKey: "kubernetes.io/hostname"
labelSelector: matchLabels: {
"postgres-operator.crunchydata.com/cluster": metadata.name
"postgres-operator.crunchydata.com/role": "pgbouncer"
}
}
}]
}
}
}

View File

@@ -0,0 +1,11 @@
package holos
#InstancePrefix: "prod-iam"
#TargetNamespace: #InstancePrefix + "-zitadel"
// _DBName is the database name used across multiple holos components in this project
_DBName: "zitadel"
// The canonical login domain for the entire platform. Zitadel will be active
// on a single cluster at a time, but always accessible from this domain.
#ExternalDomain: "login.\(#Platform.org.domain)"

View File

@@ -125,7 +125,7 @@ package holos
securityContext: {}
// Additional environment variables
env: []
env: [...]
// - name: ZITADEL_DATABASE_POSTGRES_HOST
// valueFrom:
// secretKeyRef:

View File

@@ -0,0 +1,89 @@
package holos
#Values: {
// Database credentials
// Refer to https://access.crunchydata.com/documentation/postgres-operator/5.2.0/architecture/user-management/
// Refer to https://zitadel.com/docs/self-hosting/manage/database#postgres
env: [
// Connection
{
name: "ZITADEL_DATABASE_POSTGRES_HOST"
valueFrom: secretKeyRef: name: "\(_DBName)-pguser-\(_DBName)"
valueFrom: secretKeyRef: key: "pgbouncer-host"
},
{
name: "ZITADEL_DATABASE_POSTGRES_PORT"
valueFrom: secretKeyRef: name: "\(_DBName)-pguser-\(_DBName)"
valueFrom: secretKeyRef: key: "pgbouncer-port"
},
{
name: "ZITADEL_DATABASE_POSTGRES_DATABASE"
valueFrom: secretKeyRef: name: "\(_DBName)-pguser-\(_DBName)"
valueFrom: secretKeyRef: key: "dbname"
},
// The <db>-pguser-<db> secret contains creds for the unpriviliged zitadel user
{
name: "ZITADEL_DATABASE_POSTGRES_USER_USERNAME"
valueFrom: secretKeyRef: name: "\(_DBName)-pguser-\(_DBName)"
valueFrom: secretKeyRef: key: "user"
},
{
name: "ZITADEL_DATABASE_POSTGRES_USER_PASSWORD"
valueFrom: secretKeyRef: name: "\(_DBName)-pguser-\(_DBName)"
valueFrom: secretKeyRef: key: "password"
},
// The postgres component configures privileged postgres user creds.
{
name: "ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME"
valueFrom: secretKeyRef: name: "\(_DBName)-pguser-\(_DBName)-admin"
valueFrom: secretKeyRef: key: "user"
},
{
name: "ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD"
valueFrom: secretKeyRef: name: "\(_DBName)-pguser-\(_DBName)-admin"
valueFrom: secretKeyRef: key: "password"
},
// CA Cert issued by PGO which issued the pgbouncer tls cert
{
name: "ZITADEL_DATABASE_POSTGRES_USER_SSL_ROOTCERT"
value: "/\(_PGBouncer)/ca.crt"
},
{
name: "ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_ROOTCERT"
value: "/\(_PGBouncer)/ca.crt"
},
]
// Refer to https://zitadel.com/docs/self-hosting/manage/database
zitadel: {
// Zitadel master key
masterkeySecretName: "zitadel-masterkey"
// dbSslCaCrtSecret: "pgo-root-cacert"
// All settings: https://zitadel.com/docs/self-hosting/manage/configure#runtime-configuration-file
// Helm interface: https://github.com/zitadel/zitadel-charts/blob/zitadel-7.4.0/charts/zitadel/values.yaml#L20-L21
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
ExternalSecure: true
ExternalDomain: #ExternalDomain
ExternalPort: 443
TLS: Enabled: false
// Database connection credentials are injected via environment variables from the db-pguser-db secret.
Database: postgres: {
MaxOpenConns: 25
MaxIdleConns: 10
MaxConnLifetime: "1h"
MaxConnIdleTime: "5m"
// verify-full verifies the host name matches cert dns names in addition to root ca signature
User: SSL: Mode: "verify-full"
Admin: SSL: Mode: "verify-full"
}
}
}
}

View File

@@ -0,0 +1,173 @@
package holos
import "encoding/yaml"
let Name = "zitadel"
#InputKeys: component: Name
spec: components: HelmChartList: [
#HelmChart & {
metadata: name: "\(#InstancePrefix)-zitadel"
_dependsOn: "prod-secrets-stores": _
_dependsOn: "\(#InstancePrefix)-postgres": _
namespace: #TargetNamespace
enableHooks: true
chart: {
name: Name
version: "7.9.0"
repository: {
name: Name
url: "https://charts.zitadel.com"
}
}
_values: #Values
apiObjectMap: OBJECTS.apiObjectMap
},
]
let OBJECTS = #APIObjects & {
apiObjects: {
ExternalSecret: "zitadel-masterkey": _
VirtualService: "\(Name)": {
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 IstioInject = [{op: "add", path: "/spec/template/metadata/labels/sidecar.istio.io~1inject", value: "true"}]
_PGBouncer: "pgbouncer"
let DatabaseCACertPatch = [
{
op: "add"
path: "/spec/template/spec/volumes/-"
value: {
name: _PGBouncer
secret: {
secretName: "\(_DBName)-pgbouncer"
items: [{key: "pgbouncer-frontend.ca-roots", path: "ca.crt"}]
}
}
},
{
op: "add"
path: "/spec/template/spec/containers/0/volumeMounts/-"
value: {
name: _PGBouncer
mountPath: "/" + _PGBouncer
}
},
]
let CAPatch = #Patch & {
target: {
group: "apps" | "batch"
version: "v1"
kind: "Job" | "Deployment"
name: string
}
patch: yaml.Marshal(DatabaseCACertPatch)
}
#Kustomize: _patches: {
mesh: {
target: {
group: "apps"
version: "v1"
kind: "Deployment"
name: Name
}
patch: yaml.Marshal(IstioInject)
}
deploymentCA: CAPatch & {
target: group: "apps"
target: kind: "Deployment"
target: name: Name
}
initJob: CAPatch & {
target: group: "batch"
target: kind: "Job"
target: name: "\(Name)-init"
}
setupJob: CAPatch & {
target: group: "batch"
target: kind: "Job"
target: name: "\(Name)-setup"
}
testDisable: {
target: {
version: "v1"
kind: "Pod"
name: "\(Name)-test-connection"
}
patch: yaml.Marshal(DisableFluxPatch)
}
if #IsPrimaryCluster == false {
fluxDisable: {
target: {
group: "apps"
version: "v1"
kind: "Deployment"
name: Name
}
patch: yaml.Marshal(DisableFluxPatch)
}
initDisable: {
target: {
group: "batch"
version: "v1"
kind: "Job"
name: "\(Name)-init"
}
patch: yaml.Marshal(DisableFluxPatch)
}
setupDisable: {
target: {
group: "batch"
version: "v1"
kind: "Job"
name: "\(Name)-setup"
}
patch: yaml.Marshal(DisableFluxPatch)
}
}
}
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

@@ -1,10 +0,0 @@
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

@@ -1,34 +0,0 @@
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

@@ -1,55 +0,0 @@
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

@@ -4,6 +4,6 @@ package holos
#InputKeys: project: "github"
#DependsOn: Namespaces: name: "prod-secrets-namespaces"
#TargetNamespace: #InputKeys.component
#ARCSystemNamespace: "arc-system"
#HelmChart: namespace: #TargetNamespace
#HelmChart: chart: version: "0.8.3"

View File

@@ -0,0 +1,51 @@
package holos
#TargetNamespace: "arc-runner"
#InputKeys: component: "arc-runner"
#Kustomization: spec: targetNamespace: #TargetNamespace
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: KubernetesObjectsList: [
#KubernetesObjects & {
metadata: name: "prod-github-arc-runner"
_dependsOn: "prod-secrets-namespaces": _
apiObjectMap: (#APIObjects & {
apiObjects: ExternalSecret: "\(GitHubConfigSecret)": _
}).apiObjectMap
},
]
}
// Put the scale set on the primary cluster.
if #IsPrimaryCluster == true {
spec: components: HelmChartList: [
#HelmChart & {
_dependsOn: "prod-secrets-namespaces": _
metadata: name: "prod-github-arc-runner"
_values: {
#Values
controllerServiceAccount: name: "gha-rs-controller"
controllerServiceAccount: namespace: "arc-system"
githubConfigSecret: GitHubConfigSecret
githubConfigUrl: "https://github.com/" + #Platform.org.github.orgs.primary.name
}
apiObjectMap: (#APIObjects & {apiObjects: ExternalSecret: "\(_values.githubConfigSecret)": _}).apiObjectMap
chart: {
// Match the gha-base-name in the chart _helpers.tpl to avoid long full names.
// NOTE: Unfortunately the INSTALLATION_NAME is used as the helm release
// name and GitHub removed support for runner labels, so the only way to
// specify which runner a workflow runs on is using this helm release name.
// The quote is "Update the INSTALLATION_NAME value carefully. You will use
// the installation name as the value of runs-on in your workflows." Refer to
// https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners-with-actions-runner-controller/quickstart-for-actions-runner-controller
release: "gha-rs"
name: "oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set"
}
},
]
}

View File

@@ -0,0 +1,20 @@
package holos
#TargetNamespace: #ARCSystemNamespace
#InputKeys: component: "arc-system"
spec: components: HelmChartList: [
#HelmChart & {
metadata: name: "prod-github-arc-system"
_dependsOn: "prod-secrets-namespaces": _
_values: #Values & #DefaultSecurityContext
namespace: #TargetNamespace
chart: {
// Match the gha-base-name in the chart _helpers.tpl to avoid long full names.
release: "gha-rs-controller"
name: "oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller"
version: "0.8.3"
}
},
]

View File

@@ -0,0 +1,24 @@
package holos
import "list"
spec: components: KubernetesObjectsList: [
#KubernetesObjects & {
metadata: name: "prod-secrets-namespaces"
apiObjectMap: (#APIObjects & {
apiObjects: {
// #ManagedNamespaces is the set of all namespaces across all clusters in the platform.
for k, ns in #ManagedNamespaces {
if list.Contains(ns.clusterNames, #ClusterName) {
Namespace: "\(k)": #Namespace & ns.namespace
}
}
// #PlatformNamespaces is deprecated in favor of #ManagedNamespaces.
for ns in #PlatformNamespaces {
Namespace: "\(ns.name)": #Namespace & {metadata: ns}
}
}
}).apiObjectMap
},
]

View File

@@ -0,0 +1,19 @@
package holos
spec: components: HelmChartList: [
#HelmChart & {
_dependsOn: "prod-secrets-namespaces": _
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
},
]

View File

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

View File

@@ -1,17 +1,26 @@
package holos
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"
@@ -31,5 +40,19 @@ let LoginCert = #PlatformCerts.login
},
]
}
for k, svc in #OptionalServices {
if svc.enabled && list.Contains(svc.clusterNames, #ClusterName) {
Gateway: "\(svc.name)": #Gateway & {
metadata: name: svc.name
metadata: namespace: #TargetNamespace
spec: selector: istio: "ingressgateway"
spec: servers: [for s in svc.servers {s}]
}
for k, s in svc.servers {
ExternalSecret: "\(s.tls.credentialName)": _
}
}
}
}
}

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,21 +17,29 @@ 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: httpbin: #ExternalSecret & {
_name: Cert.spec.secretName
}
ExternalSecret: "\(Cert.spec.secretName)": _
Deployment: httpbin: #Deployment & {
metadata: Metadata
spec: selector: matchLabels: MatchLabels
spec: template: {
metadata: labels: MatchLabels
metadata: labels: #CommonLabels
metadata: labels: #IstioSidecar
spec: securityContext: seccompProfile: type: "RuntimeDefault"
spec: containers: [{
@@ -56,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

@@ -0,0 +1,160 @@
package holos
import "encoding/json"
let ComponentName = "\(#InstancePrefix)-ingress"
#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
},
]
}
}
apiObjectMap: OBJECTS.apiObjectMap
},
]
_ProxyProtocol: gatewayTopology: proxyProtocol: {}
// Additional holos specific API Objects
let Name = #GatewayValues.name
let GatewayLabels = {
app: Name
istio: "ingressgateway"
}
let RedirectMetaName = {
name: Name + "-https-redirect"
namespace: #TargetNamespace
}
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
}
}]
}
}
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"}]
}
}
}
}
let LoopbackName = Name + "-loopback"
let LoopbackDescription = "Allows in-cluster traffic to stay in cluster via traffic routing"
let LoopbackLabels = {
app: LoopbackName
istio: "ingressgateway"
}
let LoopbackMetaName = {
name: LoopbackName
namespace: #TargetNamespace
}

View File

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

View File

@@ -0,0 +1,40 @@
package holos
import "encoding/yaml"
#InputKeys: component: "istiod"
#TargetNamespace: "istio-system"
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"
}
}
apiObjectMap: OBJECTS.apiObjectMap
},
]
let OBJECTS = #APIObjects & {apiObjects: ConfigMap: istio: #IstioConfigMap}
#IstioConfigMap: #ConfigMap & {
metadata: {
name: "istio"
namespace: #TargetNamespace
}
data: {
mesh: yaml.Marshal(_MeshConfig)
meshNetworks: "networks: {}"
}
}

View File

@@ -0,0 +1,3 @@
package holos
#InstancePrefix: "prod-mesh"

View File

@@ -0,0 +1,10 @@
package holos
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
@@ -93,7 +100,14 @@ provisioner get serviceaccount -A --selector=holos.run/job.name=\(NAME) --output
# Create the tokens
mkdir tokens
jq -r '.items[].metadata | "provisioner -n \\(.namespace) create token --duration=12h \\(.name) > tokens/\\(.namespace).\\(.name).jwt"' serviceaccounts.json | bash -x
kubectl get namespaces -o name > namespaces.txt
# Iterate over local namespaces
while IFS= read -r NAMESPACE; do
echo "Getting token for local cluster $NAMESPACE" >&2
jq -r '.items[] | select("namespace/"+.metadata.namespace == "'${NAMESPACE}'") | .metadata | "provisioner -n \\(.namespace) create token --duration=12h \\(.name) > tokens/\\(.namespace).\\(.name).jwt"' serviceaccounts.json | bash -x
done < namespaces.txt
# Create the secrets
mksecret tokens/*.jwt
@@ -124,6 +138,11 @@ kubectl apply --server-side=true -f secrets.yaml
resources: ["secrets"]
verbs: ["*"]
},
{
apiGroups: [""]
resources: ["namespaces"]
verbs: ["list"]
},
]
},
// Bind the Role to the ServiceAccount for the Job.

View File

@@ -0,0 +1,24 @@
package holos
// Manages the External Secrets Operator from the official upstream Helm chart.
#TargetNamespace: "external-secrets"
#Kustomization: spec: targetNamespace: #TargetNamespace
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

@@ -0,0 +1,4 @@
package holos
// Components under this directory are part of this collection
#InputKeys: project: "secrets"

Some files were not shown because too many files have changed in this diff Show More