Repository navigation
ate-setup: add a render subcommand for kustomize generation
#3411
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -122,6 +122,74 @@ apiserver, the controller, atenet, and atelet. It creates every `create` | |
| resource below on the way, so those subcommands are only needed to redo one on | ||
| a running cluster. | ||
|
|
||
| ## Render | ||
|
|
||
| | `ate-setup` | `hack/install-ate.sh` | | ||
| |---|---| | ||
| | `render ate-system` | (no shell equivalent) | | ||
|
|
||
| `render ate-system` writes the manifests `deploy ate-system` applies to a fresh | ||
| cluster into `--output-dir`, a new or empty directory, without contacting a | ||
| cluster. It needs `--image-repo` and `--image-tag`, and pins every image to a | ||
| digest: | ||
|
|
||
| ```bash | ||
| ate-setup render ate-system --kind --image-repo REPO --image-tag TAG \ | ||
| --output-dir DIR | ||
| ``` | ||
|
|
||
| Other clusters store snapshots on GCS today, so they also need the snapshot | ||
| location and the snapshot transfer service account: | ||
|
|
||
| ```bash | ||
| ate-setup render ate-system --image-repo REPO --image-tag TAG \ | ||
| --snapshot-location gs://BUCKET/ \ | ||
| --gcp-project-id PROJECT --snapshot-transfer-gsa-name NAME \ | ||
| --output-dir DIR | ||
| ``` | ||
|
|
||
| The files are numbered in apply order. Create the key pools after the | ||
| namespace, and apply the CRDs and wait for them before the rest, because a | ||
| later file holds a SandboxConfig: | ||
|
|
||
| ```bash | ||
| kubectl apply -f DIR/00-namespace.yaml | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On a cluster installed with these commands, a field that a later release removes (an env var, a volume, a sidecar) is still there after the runbook's Client-side Asked claude to rerpo this on kind with a ConfigMap key. Could these commands use |
||
| ate-setup create jwt-authority-pool | ||
| ate-setup create actor-id-ca-pool | ||
| ate-setup create actor-id-ca-certs | ||
| ate-setup create egress-mitm-ca-pool | ||
| ate-setup create podcertificate-controller-cas | ||
| kubectl apply -f DIR/01-crds.yaml | ||
| kubectl wait --for=condition=Established crd --all | ||
| kubectl apply -f DIR | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤖 nit 🟢 – The bundled provider needs
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This one from Claude: Same gap as the namespace policy, for the bundled database: |
||
| ``` | ||
|
|
||
| The CA and JWT pool Secrets are not written: `actor-id-jwt-pool`, | ||
| `actor-id-ca-pool`, `egress-mitm-ca-pool`, and the podcertificate controller's | ||
| `service-dns-ca-pool`, `pod-identity-ca-pool` and `postgres-ca-pool`. Each holds | ||
| a private key generated for one cluster. Rendered, the keys would end up | ||
| wherever the output goes, such as a release or a GitOps repository, and every | ||
| apply would replace the pools and invalidate every certificate and token issued | ||
| from them. The `create` commands above, like `deploy ate-system`, create each | ||
| pool only when it is absent. `actor-id-ca-certs` is the root certificate copied | ||
| from the `actor-id-ca-pool` Secret in the cluster, so it is created there too. | ||
|
|
||
| The `k8s-credential-provider` namespace policy is not written either. Operators | ||
| edit it, and reapplying it would reset their policy, so create it once with | ||
| `kubectl apply -f manifests/egress-credential-injection/namespace-policy.yaml`. | ||
| The bundled PostgreSQL identities are not created: `deploy ate-system` and | ||
| `deploy postgres` create them by running SQL in the postgres pod, which no | ||
| manifest does yet. The CSI drivers are not written. None of deploy's checks | ||
| against an existing install run, so do not apply the output over a running | ||
| install. The service account issuer is `EXPECTED_JWT_ISSUER`, | ||
| else the GKE issuer when the `--gcp-*` cluster settings are set, else the | ||
| in-cluster default. Without `--credential-provider`, the egress gateway uses | ||
| the bundled Kubernetes Secrets provider, `{"name":"k8s.io"}`, as kind installs | ||
| do. With an external database, `ate-api-server-secret-envvars` | ||
| is not written, so its connection strings stay out of the directory; create it | ||
| with the keys `ATE_API_POSTGRES_READ_WRITE_CONNECTION_STRING`, | ||
| `ATE_API_POSTGRES_OWNER_CONNECTION_STRING` and `ATE_API_POSTGRES_SCHEMA`. | ||
|
|
||
| ## Publish | ||
|
|
||
| | `ate-setup` | `hack/install-ate.sh` | | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| // Copyright 2026 Google LLC | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| package cmd | ||
|
|
||
| import ( | ||
| "github.com/spf13/cobra" | ||
|
|
||
| "go.ate.dev/substrate/cmd/ate-setup/internal/config" | ||
| ) | ||
|
|
||
| // renderAteSystemPath is the command path the render settings bind to. | ||
| const renderAteSystemPath = "render ate-system" | ||
|
|
||
| var renderCmd = &cobra.Command{ | ||
| Use: "render", | ||
| Short: "Write the manifests a deploy applies, without contacting a cluster", | ||
| // Rendering reads the checkout and the image registry and never a cluster, | ||
| // so it runs where there is no kubeconfig, such as a release job. | ||
| Annotations: map[string]string{clusterAnnotation: "no"}, | ||
| } | ||
|
|
||
| var renderAteSystemCmd = &cobra.Command{ | ||
| Use: "ate-system", | ||
| Short: "Write the manifests deploy ate-system applies into a directory", | ||
| Long: `Write the manifests "deploy ate-system" applies to a fresh cluster into | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| --output-dir, one file per apply step, with every image pinned to a digest. | ||
|
|
||
| ate-setup render ate-system --kind --image-repo REPO --image-tag TAG \ | ||
| --output-dir DIR | ||
|
|
||
| Other clusters store snapshots on GCS today, so they also need the snapshot | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| location and the snapshot transfer service account: | ||
|
|
||
| ate-setup render ate-system --image-repo REPO --image-tag TAG \ | ||
| --snapshot-location gs://BUCKET/ \ | ||
| --gcp-project-id PROJECT --snapshot-transfer-gsa-name NAME \ | ||
| --output-dir DIR | ||
|
|
||
| The files are numbered in apply order. Create the key pools after the | ||
| namespace, and apply the CRDs and wait for them before the rest, because a | ||
| later file holds a SandboxConfig: | ||
|
|
||
| kubectl apply -f DIR/00-namespace.yaml | ||
| ate-setup create jwt-authority-pool | ||
| ate-setup create actor-id-ca-pool | ||
| ate-setup create actor-id-ca-certs | ||
| ate-setup create egress-mitm-ca-pool | ||
| ate-setup create podcertificate-controller-cas | ||
| kubectl apply -f DIR/01-crds.yaml | ||
| kubectl wait --for=condition=Established crd --all | ||
| kubectl apply -f DIR | ||
|
|
||
| The same settings shape the output as shape a deploy, except that nothing is | ||
| read from a cluster: | ||
|
|
||
| - The service account issuer is EXPECTED_JWT_ISSUER, else the GKE issuer when | ||
| --gcp-project-id, --gcp-cluster-location and --gcp-cluster-name are set, | ||
| else https://kubernetes.default.svc. | ||
| - Without --credential-provider (ATE_CREDENTIAL_PROVIDER), the egress gateway | ||
| uses the bundled Kubernetes Secrets provider, {"name":"k8s.io"}, as kind | ||
| installs do. It runs in the cluster and needs nothing outside it. | ||
| - None of deploy's checks against an existing install run, and the settings | ||
| ConfigMaps are written whole. Do not apply the output over a running | ||
| install. | ||
| - The CA and JWT pool Secrets are not written. Each holds a private key | ||
| generated for one cluster, which would end up wherever the output goes, | ||
| and reapplying a rendered pool would replace it and invalidate every | ||
| certificate and token issued from it. The "ate-setup create" commands | ||
| above create each pool only when it is absent, as deploy ate-system does. | ||
| actor-id-ca-certs is copied from the actor-id-ca-pool Secret in the | ||
| cluster, so it is created there too. | ||
| - The k8s-credential-provider namespace policy is not written. Operators | ||
| edit it, and reapplying it would reset their policy. Create it once with | ||
| kubectl apply -f manifests/egress-credential-injection/namespace-policy.yaml. | ||
| - The bundled PostgreSQL identities are not created: deploy ate-system and | ||
| deploy postgres create them by running SQL in the postgres pod, which no | ||
| manifest does yet. The CSI drivers are not written either. | ||
| - With an external database (ATE_API_POSTGRES_READ_WRITE_CONNECTION_STRING), | ||
| ate-api-server-secret-envvars is not written, so its connection strings stay | ||
| out of the directory. Create it before applying, with keys | ||
| ATE_API_POSTGRES_READ_WRITE_CONNECTION_STRING, | ||
| ATE_API_POSTGRES_OWNER_CONNECTION_STRING and ATE_API_POSTGRES_SCHEMA.`, | ||
| Args: cobra.NoArgs, | ||
| RunE: func(cmd *cobra.Command, _ []string) error { | ||
| return env.RenderAteSystem(cmd.Context(), env.Cfg.Resolved().String("render.outputDir")) | ||
| }, | ||
| } | ||
|
|
||
| func init() { | ||
| rootCmd.AddCommand(renderCmd) | ||
| renderCmd.AddCommand(renderAteSystemCmd) | ||
|
|
||
| config.RegisterCommand(config.Setting{ | ||
| Key: "render.outputDir", Env: "ATE_RENDER_OUTPUT_DIR", Flag: "output-dir", | ||
| Kind: config.KindString, | ||
| Commands: []string{renderAteSystemPath}, | ||
| Usage: "Directory the rendered manifests are written to; it must be new or empty", | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Requiring the directory to be new or empty is safe, but may make it difficult to diff the config change. |
||
| }) | ||
| config.BindCommandFlags(renderAteSystemPath, renderAteSystemCmd.Flags()) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -131,7 +131,23 @@ func (e *Env) CreateAPIServerEnvVars(ctx context.Context) error { | |
| } | ||
| } | ||
| } | ||
| configVars := cloudSQLEnvVars(cloudsql) | ||
| configVars := e.apiServerConfigVars(readWriteRole, ownerRole, poolMaxConns, snapshotLocation) | ||
| maps.Copy(configVars, cloudSQLEnvVars(cloudsql)) | ||
| if err := e.Kube.ApplyConfigMap(ctx, e.Namespace(), ConfigMapAPIEnvVars, configVars); err != nil { | ||
| return err | ||
| } | ||
| if err := e.Kube.ApplySecret(ctx, e.Namespace(), SecretAPIEnvVars, | ||
| buildAPIServerEnvVars(readWriteDSN, ownerDSN, schema)); err != nil { | ||
| return err | ||
| } | ||
| return e.applyPostgresServerCA(ctx) | ||
| } | ||
|
|
||
| // apiServerConfigVars is the ate-api-server-envvars ConfigMap payload without | ||
| // the Cloud SQL settings, which only CreateAPIServerEnvVars adds. render ate-system | ||
| // writes this payload, and Cloud SQL is not part of the core install. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| func (e *Env) apiServerConfigVars(readWriteRole, ownerRole, poolMaxConns, snapshotLocation string) map[string]string { | ||
| configVars := map[string]string{} | ||
| configVars["ATE_API_POSTGRES_READ_WRITE_ROLE"] = readWriteRole | ||
| configVars["ATE_API_POSTGRES_OWNER_ROLE"] = ownerRole | ||
| if poolMaxConns != "" { | ||
|
|
@@ -144,14 +160,7 @@ func (e *Env) CreateAPIServerEnvVars(ctx context.Context) error { | |
| if snapshotLocation != "" { | ||
| configVars["ATE_API_SNAPSHOT_LOCATION"] = snapshotLocation | ||
| } | ||
| if err := e.Kube.ApplyConfigMap(ctx, e.Namespace(), ConfigMapAPIEnvVars, configVars); err != nil { | ||
| return err | ||
| } | ||
| if err := e.Kube.ApplySecret(ctx, e.Namespace(), SecretAPIEnvVars, | ||
| buildAPIServerEnvVars(readWriteDSN, ownerDSN, schema)); err != nil { | ||
| return err | ||
| } | ||
| return e.applyPostgresServerCA(ctx) | ||
| return configVars | ||
| } | ||
|
|
||
| // UpdateAPIServerEnvVars is CreateAPIServerEnvVars for an install that is not | ||
|
|
@@ -349,6 +358,15 @@ func (e *Env) applyWithAPIServerEnvHash(ctx context.Context, manifest []byte) er | |
| if err != nil { | ||
| return err | ||
| } | ||
| if err := e.setAPIServerDeploymentFields(objs, hash); err != nil { | ||
| return err | ||
| } | ||
| return e.Kube.Apply(ctx, objs) | ||
| } | ||
|
|
||
| // setAPIServerDeploymentFields stamps the environment digest and sets the | ||
| // authorization flags on the ate-api-server Deployment in objs. | ||
| func (e *Env) setAPIServerDeploymentFields(objs []*unstructured.Unstructured, hash string) error { | ||
| if err := stampAPIServerEnvHash(objs, hash); err != nil { | ||
| return err | ||
| } | ||
|
|
@@ -358,7 +376,7 @@ func (e *Env) applyWithAPIServerEnvHash(ctx context.Context, manifest []byte) er | |
| if msg := sharedBootstrapOwnerWarning(e.Cfg.APIAuthzBootstrapOwners, e.Namespace()); msg != "" { | ||
| log.Warnf("%s", msg) | ||
| } | ||
| return e.Kube.Apply(ctx, objs) | ||
| return nil | ||
| } | ||
|
|
||
| // apiServerAuthzFlag is the ate-api-server flag setAPIServerAuthzArgs owns. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤖 nit 🟢 – This section repeats the
Longhelp ofrender ate-systemalmost word for word, and theRenderAteSystemdoc comment insteps/render.golists the same exclusions a third time. The copies have already drifted, for example in how they describe the--gcp-*issuer settings. Other sections in this file are mapping tables with short notes. Could this keep the table row with a pointer toate-setup render ate-system --help, and could the Go doc comment shrink to what the function writes?