Skip to content

Commit e7fe0e0

Browse files
authored
🤖 feat: implement Helm parity phases 1-2 for CoderControlPlane (#77)
## Summary This PR implements Phase 1 and Phase 2 Helm-chart parity for `CoderControlPlane`, including production-hardening controls, workspace RBAC/ServiceAccount reconciliation, TLS/probe/scheduling passthroughs, and optional external exposure via either Ingress or Gateway API. ## Background The operator previously reconciled only a basic Deployment/Service/token flow. The plan for this branch adds the higher-leverage chart capabilities needed for production readiness and operability while preserving fail-fast behavior and backward compatibility. ## Implementation - Extended `CoderControlPlaneSpec` with parity fields for: - ServiceAccount, workspace RBAC rules/namespaces - Resources, container/pod security context - TLS secret mounts + probe configs - EnvFrom/volumes/volumeMounts/cert mounts - Scheduling controls (nodeSelector/tolerations/affinity/topology spread) - Exposure API: `spec.expose.ingress` or `spec.expose.gateway` - Added Gateway API dependency and scheme registration (`sigs.k8s.io/gateway-api/apis/v1`). - Implemented controller reconciliation for: - ServiceAccount creation/attachment - Workspace Role/RoleBinding management - Deployment alignment (port 8080, HA env defaults, optional access URL injection, TLS wiring, probes, pass-throughs) - Service HTTPS port when TLS is enabled - Ingress reconciliation and cleanup - HTTPRoute reconciliation and cleanup with graceful `NoMatch` handling when Gateway CRDs are absent - Regenerated CRD/RBAC manifests and API reference docs. - Added controller tests covering the new reconciliation behavior end-to-end. ## Validation - `make verify-vendor` - `make build` - `make test` - `make manifests` - `make codegen` - `make docs-reference` ## Risks - This change touches a broad reconciliation surface (Deployment, RBAC, and exposure resources). Reconciliation paths are covered by targeted tests, but cluster-specific integrations (Gateway controller behavior, ingress-class semantics) still depend on downstream environment configuration. --- <details> <summary>📋 Implementation Plan</summary> # Plan: Phase 1–2 parity with `coder/coder` Helm chart (+ optional Gateway API) ## Context / Why We want the `CoderControlPlane` controller in this repo to reach closer feature parity with the upstream `coder/coder` Helm chart. Today, our operator mainly reconciles: - a `Deployment` running the Coder control plane, - a fronting `Service`, and - an operator-token `Secret` (plus license/entitlements logic). The Helm chart additionally provides production-hardening and operability knobs: ServiceAccount + namespace RBAC for workspaces, resource limits, security contexts, TLS enablement, probes, HA env injection, Ingress exposure, scheduling controls, and volume/envFrom passthroughs. This plan implements **Phase 1 (production readiness)** and **Phase 2 (operability + HA)** items, and adds an **opt-in exposure API** allowing operators to choose **Ingress** or **Gateway API**. ## Goals (Phases 1 & 2) 1. **Make Coder pods runnable in production clusters** with Pod Security constraints by exposing/setting security context, resources, probes, and TLS. 2. **Provide first-class RBAC + ServiceAccount management** for workspace provisioning (pods/PVCs/deployments) across multiple namespaces. 3. **Support HA-relevant defaults** (pod IP env + DERP relay URL + default access URL behavior). 4. **Expose the control plane externally** via: - `networking.k8s.io/v1` **Ingress**, OR - `gateway.networking.k8s.io/v1` **Gateway API** (HTTPRoute), without requiring Gateway API CRDs to exist unless configured. ## Non-goals (explicitly deferred) - Full parity with every Helm chart knob (e.g., HPA, PDB, NetworkPolicy, workspace-proxy mode, provisioner daemon deployment). - Replacing the existing operator-access / licensing workflows. ## Evidence / Sources consulted - Upstream Helm chart container/env/TLS/probe behaviors: - `./tmpfork/coder/helm/coder/templates/_coder.tpl` - `./tmpfork/coder/helm/libcoder/templates/_helpers.tpl` - Upstream Helm chart RBAC rules & multi-namespace behavior: - `./tmpfork/coder/helm/libcoder/templates/_rbac.yaml` - Our API surface today: - `api/v1alpha1/codercontrolplane_types.go` - `api/v1alpha1/types_shared.go` - Our controller reconciliation + constants + SetupWithManager: - `internal/controller/codercontrolplane_controller.go` - Scheme construction (for adding Gateway API types): - `internal/app/sharedscheme/sharedscheme.go` --- ## Implementation plan ### 0) Create a parity tracking document (optional but recommended) Add a short markdown doc (e.g., `docs/design/helm-parity.md`) listing each Helm chart knob and which `CoderControlPlaneSpec` field covers it. This keeps future parity work honest. --- ## Phase 1 — Production readiness ### 1) Extend the CRD: `CoderControlPlaneSpec` (API additions) **Files** - `api/v1alpha1/codercontrolplane_types.go` - `api/v1alpha1/types_shared.go` **Add spec fields (Phase 1 scope)** 1) **Pod identity / permissions** - `spec.serviceAccount` (new struct) - `spec.rbac` (new struct) 2) **Hardening & resource controls** - `spec.resources` (`*corev1.ResourceRequirements`) - `spec.securityContext` (`*corev1.SecurityContext`) - `spec.podSecurityContext` (`*corev1.PodSecurityContext`) 3) **TLS enablement (Coder built-in TLS)** - `spec.tls.secretNames` (`[]string`) — enable internal TLS when non-empty 4) **Probes** - `spec.readinessProbe` and `spec.livenessProbe` (chart-style config with `enabled` + timing knobs) 5) **(Parity) Default access URL behavior** - `spec.envUseClusterAccessURL` (`*bool`, default `true`) — if enabled and user didn’t provide `CODER_ACCESS_URL` explicitly via `extraEnv`, the operator injects a default in-cluster URL. **Proposed Go shapes (illustrative)** ```go // api/v1alpha1/types_shared.go // ServiceAccountSpec configures the ServiceAccount used by the Coder pod. type ServiceAccountSpec struct { // DisableCreate skips ServiceAccount creation (use an existing SA). // +kubebuilder:default=false DisableCreate bool `json:"disableCreate,omitempty"` // Name is the ServiceAccount name. If empty, default to the CoderControlPlane name. Name string `json:"name,omitempty"` Annotations map[string]string `json:"annotations,omitempty"` Labels map[string]string `json:"labels,omitempty"` } // RBACSpec configures namespace-scoped RBAC for workspace provisioning. type RBACSpec struct { // WorkspacePerms enables Role/RoleBinding creation. // +kubebuilder:default=true WorkspacePerms bool `json:"workspacePerms,omitempty"` // EnableDeployments grants apps/deployments permissions (only if WorkspacePerms). // +kubebuilder:default=true EnableDeployments bool `json:"enableDeployments,omitempty"` // ExtraRules are appended to the Role rules (only if WorkspacePerms). ExtraRules []rbacv1.PolicyRule `json:"extraRules,omitempty"` // WorkspaceNamespaces are additional namespaces to create the Role/RoleBinding in. WorkspaceNamespaces []string `json:"workspaceNamespaces,omitempty"` } type TLSSpec struct { SecretNames []string `json:"secretNames,omitempty"` } type ProbeSpec struct { // +kubebuilder:default=true Enabled bool `json:"enabled,omitempty"` // +kubebuilder:default=0 InitialDelaySeconds int32 `json:"initialDelaySeconds,omitempty"` PeriodSeconds *int32 `json:"periodSeconds,omitempty"` TimeoutSeconds *int32 `json:"timeoutSeconds,omitempty"` SuccessThreshold *int32 `json:"successThreshold,omitempty"` FailureThreshold *int32 `json:"failureThreshold,omitempty"` } ``` ```go // api/v1alpha1/codercontrolplane_types.go type CoderControlPlaneSpec struct { ... ServiceAccount ServiceAccountSpec `json:"serviceAccount,omitempty"` RBAC RBACSpec `json:"rbac,omitempty"` Resources *corev1.ResourceRequirements `json:"resources,omitempty"` SecurityContext *corev1.SecurityContext `json:"securityContext,omitempty"` PodSecurityContext *corev1.PodSecurityContext `json:"podSecurityContext,omitempty"` TLS TLSSpec `json:"tls,omitempty"` // +kubebuilder:default={enabled:true,initialDelaySeconds:0} ReadinessProbe ProbeSpec `json:"readinessProbe,omitempty"` // +kubebuilder:default={enabled:false,initialDelaySeconds:0} LivenessProbe ProbeSpec `json:"livenessProbe,omitempty"` // +kubebuilder:default=true EnvUseClusterAccessURL *bool `json:"envUseClusterAccessURL,omitempty"` } ``` **Notes** - Keep fields optional and backward compatible. - Prefer `types_shared.go` for structs that may be reused by future CRDs. --- ### 2) Reconcile ServiceAccount + namespace RBAC for workspaces **Files** - `internal/controller/codercontrolplane_controller.go` **Where** - Add a new reconciliation step in `Reconcile()` **before** `reconcileDeployment()` so the Deployment can reference the SA. **What to add** 1) `reconcileServiceAccount(ctx, cp)` - If `spec.serviceAccount.disableCreate=true`: ensure previously-owned SA is deleted (cleanup). - Else create/update a `corev1.ServiceAccount` named `spec.serviceAccount.name` (default to `cp.Name`). - Apply labels `controlPlaneLabels(cp.Name)` plus user-provided SA labels/annotations. 2) `reconcileWorkspaceRBAC(ctx, cp)` - If `spec.rbac.workspacePerms=false`: delete previously-owned Roles/RoleBindings (cleanup). - Else create/update a `rbacv1.Role` and `rbacv1.RoleBinding` in: - `cp.Namespace`, and - each namespace in `spec.rbac.workspaceNamespaces`. **Match Helm chart semantics** (from `libcoder.rbac.rules.basic` / `deployments`): - Basic rules (pods + PVCs) when `workspacePerms=true`. - Deployments rules only when `workspacePerms=true && enableDeployments=true`. - Append `extraRules` only when `workspacePerms=true`. **Role/RoleBinding naming** - Role: `<serviceAccountName>-workspace-perms` - RoleBinding: `<serviceAccountName>` (matches chart) **Cleanup strategy** - Use label selectors + `OwnerReference` checks to delete only operator-owned RBAC objects. - Mirror the pattern used by `cleanupDisabledOperatorAccess`. --- ### 3) Align the Deployment with Helm defaults (ports, probes, env) **Files** - `internal/controller/codercontrolplane_controller.go` **Changes** 1) **Port alignment** - Change `controlPlaneTargetPort` from `3000` → `8080`. - Change default arg from `--http-address=0.0.0.0:3000` → `--http-address=0.0.0.0:8080`. 2) **Inject HA env defaults** (as Helm chart does) - Always include: - `KUBE_POD_IP` from `fieldRef: status.podIP` - `CODER_DERP_SERVER_RELAY_URL=http://$(KUBE_POD_IP):8080` 3) **Default `CODER_ACCESS_URL` injection** - If `spec.envUseClusterAccessURL` is true and `extraEnv` does not set `CODER_ACCESS_URL`, inject: - `http://<service>.<namespace>.svc.cluster.local` when internal TLS disabled - `https://<service>.<namespace>.svc.cluster.local` when internal TLS enabled 4) **Readiness/Liveness probes** - If `spec.readinessProbe.enabled`: set readiness probe `GET /healthz` on named port `http`. - If `spec.livenessProbe.enabled`: set liveness probe similarly. - Map timing knobs to `corev1.Probe` fields. 5) **Security & resources** - Apply `spec.resources` → `container.resources`. - Apply `spec.securityContext` → `container.securityContext`. - Apply `spec.podSecurityContext` → `pod.securityContext`. 6) **ServiceAccount usage** - Set `pod.spec.serviceAccountName` to the resolved SA name. --- ### 4) Implement internal TLS (Coder built-in TLS) like Helm **Files** - `api/v1alpha1/*` (spec field already added) - `internal/controller/codercontrolplane_controller.go` **Behavior (match Helm chart’s `coder.tlsEnv` + mounts)** If `spec.tls.secretNames` is non-empty: - Add env vars: - `CODER_TLS_ENABLE=true` - `CODER_TLS_ADDRESS=0.0.0.0:8443` - `CODER_TLS_CERT_FILE` = comma-separated list of `/etc/ssl/certs/coder/<secret>/tls.crt` - `CODER_TLS_KEY_FILE` = comma-separated list of `/etc/ssl/certs/coder/<secret>/tls.key` - Add pod volumes (one per TLS secret) - Add volume mounts at `/etc/ssl/certs/coder/<secret>` (read-only) - Add container port `https:8443` **Service impact** - Add an additional `ServicePort` named `https` at 443 → targetPort 8443 when TLS is enabled. **Status impact** - Update `desiredStatus().URL` scheme to `https` when TLS is enabled. --- ## Phase 2 — Operability + HA ### 5) Add pass-through config knobs: envFrom, volumes, cert bundles, scheduling **API changes** Add these optional fields to `CoderControlPlaneSpec`: - `envFrom []corev1.EnvFromSource` - `volumes []corev1.Volume` - `volumeMounts []corev1.VolumeMount` - `certs.secrets []SecretKeySelector` (name+key) to mount CA certs at `/etc/ssl/certs/<name>.crt` with `subPath: key` - scheduling fields: - `nodeSelector map[string]string` - `tolerations []corev1.Toleration` - `affinity *corev1.Affinity` - `topologySpreadConstraints []corev1.TopologySpreadConstraint` **Controller changes** - Append `envFrom` to container. - Append `volumes` and `volumeMounts` to the pod. - For each CA cert secret selector: - add volume + volumeMount matching Helm behavior. - Apply scheduling fields on the pod spec. --- ### 6) Exposure API: choose between Ingress or Gateway API **Goal**: Let operators choose **one** of: - `networking.k8s.io/v1 Ingress`, or - `gateway.networking.k8s.io/v1 HTTPRoute` (Gateway API) #### 6.1 CRD changes: add `spec.expose` Add a new `ExposeSpec` with mutually exclusive `ingress` vs `gateway` config. ```go // types_shared.go type ExposeSpec struct { // +optional Ingress *IngressExposeSpec `json:"ingress,omitempty"` // +optional Gateway *GatewayExposeSpec `json:"gateway,omitempty"` // NOTE: add kubebuilder XValidation to ensure at most one is set. // Example intent: // +kubebuilder:validation:XValidation:rule="!(has(self.ingress) && has(self.gateway))",message="only one of ingress or gateway may be set" } type IngressExposeSpec struct { ClassName *string `json:"className,omitempty"` Host string `json:"host"` WildcardHost string `json:"wildcardHost,omitempty"` Annotations map[string]string `json:"annotations,omitempty"` // Optional TLS termination at the Ingress. TLS *IngressTLSExposeSpec `json:"tls,omitempty"` } type IngressTLSExposeSpec struct { SecretName string `json:"secretName,omitempty"` WildcardSecretName string `json:"wildcardSecretName,omitempty"` } type GatewayExposeSpec struct { Host string `json:"host"` WildcardHost string `json:"wildcardHost,omitempty"` // ParentRefs are Gateways that this HTTPRoute attaches to. ParentRefs []GatewayParentRef `json:"parentRefs,omitempty"` } type GatewayParentRef struct { Name string `json:"name"` Namespace *string `json:"namespace,omitempty"` SectionName *string `json:"sectionName,omitempty"` } ``` #### 6.2 Controller changes: reconcile + cleanup **Files** - `internal/controller/codercontrolplane_controller.go` **Where** - In `Reconcile()`, reconcile exposure resources **after** `reconcileService()`. **Ingress reconciliation** - Create/update `networkingv1.Ingress` named `cp.Name` (or `cp.Name + "-ingress"` if name collisions are a concern). - Rules: - one rule for `host` (required) - optional rule for `wildcardHost` - Backend: - Service: `cp.Name` - Port: `spec.service.port` - Apply annotations/className. - TLS: - If `tls.secretName` set: add `IngressTLS{SecretName, Hosts:[host]}` - If `tls.wildcardSecretName` set: add `IngressTLS{SecretName, Hosts:[wildcardHost]}` **Gateway API reconciliation (minimal viable)** - Reconcile a `gatewayv1.HTTPRoute` named `cp.Name`. - `spec.parentRefs`: from `spec.expose.gateway.parentRefs`. - `spec.hostnames`: include `host` and `wildcardHost` when set. - One rule routing `/` to backend service `cp.Name` at port `spec.service.port`. **Critical compatibility requirement** - Gateway API CRDs may not exist in the cluster. Ensure the operator: - does **not** add `Owns(&gatewayv1.HTTPRoute{})` watches in `SetupWithManager`, and - gracefully handles `meta.IsNoMatchError(err)` (or equivalent) during reconcile: - record a Condition or Event (recommended), and - do not crash or block other reconciliation. #### 6.3 Scheme & deps - Add `sigs.k8s.io/gateway-api` to `go.mod` and `vendor/`. - Register Gateway API types into the scheme in `internal/app/sharedscheme/sharedscheme.go` (e.g., `gatewayv1.AddToScheme(scheme)`). --- ## Cross-cutting work ### 7) Update operator RBAC markers and generated manifests **Files** - `internal/controller/codercontrolplane_controller.go` kubebuilder RBAC comments Add operator permissions to manage new resources: - `serviceaccounts` - `roles`, `rolebindings` - `ingresses` - (optional) `httproutes`, `gateways` (Gateway API) Then regenerate: - `make manifests` --- ### 8) Testing plan **Unit/envtest** - Extend existing controller tests (likely `internal/controller/codercontrolplane_controller_test.go`): - ServiceAccount created and referenced by Deployment - Role/RoleBinding created in `cp.Namespace` and extra namespaces - TLS secretNames → volumes/mounts/env + service https port - Probes enabled/disabled behavior - Ingress created when `spec.expose.ingress` is set; deleted when unset - Gateway API: when configured but CRDs missing, reconcile should not hard-fail (assert on condition/event or logged behavior) **Integration / make targets** - `make test` - `make test-integration` (if it exercises controller-runtime manager behavior) --- ### 9) Generated artifacts & docs - After API changes: - `make codegen` - `make manifests` - Update examples in `config/samples/` to include: - a minimal cluster-internal install - an Ingress-exposed install - a Gateway API HTTPRoute-exposed install - If this repo maintains API reference docs, regenerate them (per project conventions). --- ## Validation checklist (when implementing) 1. `make test` 2. `make test-integration` 3. `make build` 4. `make lint` 5. Confirm generated manifests (`config/`) updated and committed. </details> --- _Generated with `mux` • Model: `openai:gpt-5.3-codex` • Thinking: `xhigh` • Cost: `$8.55`_ <!-- mux-attribution: model=openai:gpt-5.3-codex thinking=xhigh costs=8.55 -->
1 parent 0cdc937 commit e7fe0e0

41 files changed

Lines changed: 18466 additions & 233 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.cspell.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@
4949
"cnpg",
5050
"pooler",
5151
"finalizer",
52-
"superfences"
52+
"superfences",
53+
"tolerations"
5354
],
5455
"ignorePaths": [
5556
".git/**",

‎api/v1alpha1/codercontrolplane_types.go‎

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,63 @@ type CoderControlPlaneSpec struct {
5353
// control plane is ready and re-uploads when the Secret value changes.
5454
// +optional
5555
LicenseSecretRef *SecretKeySelector `json:"licenseSecretRef,omitempty"`
56+
57+
// ServiceAccount configures the ServiceAccount for the control plane pod.
58+
// +kubebuilder:default={}
59+
ServiceAccount ServiceAccountSpec `json:"serviceAccount,omitempty"`
60+
// RBAC configures namespace-scoped RBAC for workspace provisioning.
61+
// +kubebuilder:default={}
62+
RBAC RBACSpec `json:"rbac,omitempty"`
63+
64+
// Resources sets resource requests/limits for the control plane container.
65+
// +optional
66+
Resources *corev1.ResourceRequirements `json:"resources,omitempty"`
67+
// SecurityContext sets the container security context.
68+
// +optional
69+
SecurityContext *corev1.SecurityContext `json:"securityContext,omitempty"`
70+
// PodSecurityContext sets the pod-level security context.
71+
// +optional
72+
PodSecurityContext *corev1.PodSecurityContext `json:"podSecurityContext,omitempty"`
73+
74+
// TLS configures Coder built-in TLS.
75+
// +kubebuilder:default={}
76+
TLS TLSSpec `json:"tls,omitempty"`
77+
78+
// ReadinessProbe configures the readiness probe for the control plane container.
79+
// +kubebuilder:default={enabled:true}
80+
ReadinessProbe ProbeSpec `json:"readinessProbe,omitempty"`
81+
// LivenessProbe configures the liveness probe for the control plane container.
82+
// +kubebuilder:default={enabled:false}
83+
LivenessProbe ProbeSpec `json:"livenessProbe,omitempty"`
84+
85+
// EnvUseClusterAccessURL injects a default CODER_ACCESS_URL when not explicitly set.
86+
// +kubebuilder:default=true
87+
EnvUseClusterAccessURL *bool `json:"envUseClusterAccessURL,omitempty"`
88+
89+
// Expose configures external exposure via Ingress or Gateway API.
90+
// +optional
91+
Expose *ExposeSpec `json:"expose,omitempty"`
92+
93+
// +kubebuilder:validation:XValidation:rule="self.all(e, !(has(e.configMapRef) && has(e.secretRef)))",message="each envFrom entry may specify at most one of configMapRef or secretRef"
94+
// EnvFrom injects environment variables from ConfigMaps/Secrets.
95+
EnvFrom []corev1.EnvFromSource `json:"envFrom,omitempty"`
96+
// Volumes are additional volumes to add to the pod.
97+
Volumes []corev1.Volume `json:"volumes,omitempty"`
98+
// VolumeMounts are additional volume mounts for the control plane container.
99+
VolumeMounts []corev1.VolumeMount `json:"volumeMounts,omitempty"`
100+
// Certs configures additional CA certificate mounts.
101+
// +kubebuilder:default={}
102+
Certs CertsSpec `json:"certs,omitempty"`
103+
104+
// NodeSelector constrains pod scheduling to nodes matching labels.
105+
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
106+
// Tolerations are applied to the control plane pod.
107+
Tolerations []corev1.Toleration `json:"tolerations,omitempty"`
108+
// Affinity configures pod affinity/anti-affinity rules.
109+
// +optional
110+
Affinity *corev1.Affinity `json:"affinity,omitempty"`
111+
// TopologySpreadConstraints control pod topology spread.
112+
TopologySpreadConstraints []corev1.TopologySpreadConstraint `json:"topologySpreadConstraints,omitempty"`
56113
}
57114

58115
// OperatorAccessSpec configures the controller-managed coderd operator user.

‎api/v1alpha1/types_shared.go‎

Lines changed: 132 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package v1alpha1
22

3-
import corev1 "k8s.io/api/core/v1"
3+
import (
4+
corev1 "k8s.io/api/core/v1"
5+
rbacv1 "k8s.io/api/rbac/v1"
6+
)
47

58
const (
69
// DefaultTokenSecretKey is the default key used for proxy session tokens.
@@ -28,3 +31,131 @@ type SecretKeySelector struct {
2831
// Key is the key inside the Secret data map.
2932
Key string `json:"key,omitempty"`
3033
}
34+
35+
// ServiceAccountSpec configures the ServiceAccount used by the Coder pod.
36+
type ServiceAccountSpec struct {
37+
// DisableCreate skips ServiceAccount creation (use an existing SA).
38+
// +kubebuilder:default=false
39+
DisableCreate bool `json:"disableCreate,omitempty"`
40+
// Name overrides the ServiceAccount name. Defaults to the CoderControlPlane name.
41+
Name string `json:"name,omitempty"`
42+
// Annotations are applied to the managed ServiceAccount.
43+
Annotations map[string]string `json:"annotations,omitempty"`
44+
// Labels are applied to the managed ServiceAccount.
45+
Labels map[string]string `json:"labels,omitempty"`
46+
}
47+
48+
// RBACSpec configures namespace-scoped RBAC for workspace provisioning.
49+
type RBACSpec struct {
50+
// WorkspacePerms enables Role/RoleBinding creation for workspace resources.
51+
// When omitted, the default is true.
52+
// +kubebuilder:default=true
53+
WorkspacePerms *bool `json:"workspacePerms,omitempty"`
54+
// EnableDeployments grants apps/deployments permissions (only when WorkspacePerms is true).
55+
// When omitted, the default is true.
56+
// +kubebuilder:default=true
57+
EnableDeployments *bool `json:"enableDeployments,omitempty"`
58+
// ExtraRules are appended to the managed Role rules.
59+
ExtraRules []rbacv1.PolicyRule `json:"extraRules,omitempty"`
60+
// WorkspaceNamespaces lists additional namespaces for Role/RoleBinding creation.
61+
WorkspaceNamespaces []string `json:"workspaceNamespaces,omitempty"`
62+
}
63+
64+
// TLSSpec configures Coder built-in TLS.
65+
type TLSSpec struct {
66+
// SecretNames lists TLS secrets to mount for built-in TLS.
67+
// When non-empty, TLS is enabled on the Coder control plane.
68+
SecretNames []string `json:"secretNames,omitempty"`
69+
}
70+
71+
// ProbeSpec configures a Kubernetes probe with an enable toggle.
72+
type ProbeSpec struct {
73+
// Enabled toggles the probe on or off.
74+
// When omitted, readiness defaults to enabled while liveness defaults to disabled.
75+
Enabled *bool `json:"enabled,omitempty"`
76+
// InitialDelaySeconds is the delay before the probe starts.
77+
// +kubebuilder:default=0
78+
InitialDelaySeconds int32 `json:"initialDelaySeconds,omitempty"`
79+
// PeriodSeconds controls how often the probe is performed.
80+
PeriodSeconds *int32 `json:"periodSeconds,omitempty"`
81+
// TimeoutSeconds is the probe timeout.
82+
TimeoutSeconds *int32 `json:"timeoutSeconds,omitempty"`
83+
// SuccessThreshold is the minimum consecutive successes for the probe to be considered successful.
84+
SuccessThreshold *int32 `json:"successThreshold,omitempty"`
85+
// FailureThreshold is the minimum consecutive failures for the probe to be considered failed.
86+
FailureThreshold *int32 `json:"failureThreshold,omitempty"`
87+
}
88+
89+
// ExposeSpec configures external exposure for the control plane.
90+
// At most one of Ingress or Gateway may be set.
91+
// +kubebuilder:validation:XValidation:rule="!(has(self.ingress) && has(self.gateway))",message="only one of ingress or gateway may be set"
92+
type ExposeSpec struct {
93+
// Ingress configures a networking.k8s.io/v1 Ingress.
94+
// +optional
95+
Ingress *IngressExposeSpec `json:"ingress,omitempty"`
96+
// Gateway configures a gateway.networking.k8s.io/v1 HTTPRoute.
97+
// +optional
98+
Gateway *GatewayExposeSpec `json:"gateway,omitempty"`
99+
}
100+
101+
// IngressExposeSpec defines Ingress exposure configuration.
102+
type IngressExposeSpec struct {
103+
// ClassName is the Ingress class name.
104+
ClassName *string `json:"className,omitempty"`
105+
// Host is the primary hostname for the Ingress rule.
106+
Host string `json:"host"`
107+
// WildcardHost is an optional wildcard hostname (e.g., for workspace apps).
108+
WildcardHost string `json:"wildcardHost,omitempty"`
109+
// Annotations are applied to the managed Ingress.
110+
Annotations map[string]string `json:"annotations,omitempty"`
111+
// TLS configures TLS termination at the Ingress.
112+
// +optional
113+
TLS *IngressTLSExposeSpec `json:"tls,omitempty"`
114+
}
115+
116+
// IngressTLSExposeSpec defines TLS configuration for the Ingress.
117+
type IngressTLSExposeSpec struct {
118+
// SecretName is the TLS Secret for the primary host.
119+
SecretName string `json:"secretName,omitempty"`
120+
// WildcardSecretName is the TLS Secret for the wildcard host.
121+
WildcardSecretName string `json:"wildcardSecretName,omitempty"`
122+
}
123+
124+
// GatewayExposeSpec defines Gateway API (HTTPRoute) exposure configuration.
125+
type GatewayExposeSpec struct {
126+
// Host is the primary hostname for the HTTPRoute.
127+
Host string `json:"host"`
128+
// WildcardHost is an optional wildcard hostname.
129+
WildcardHost string `json:"wildcardHost,omitempty"`
130+
// ParentRefs are Gateways that the HTTPRoute attaches to.
131+
// At least one parentRef is required when gateway exposure is configured.
132+
// +kubebuilder:validation:MinItems=1
133+
ParentRefs []GatewayParentRef `json:"parentRefs"`
134+
}
135+
136+
// GatewayParentRef identifies a Gateway for HTTPRoute attachment.
137+
type GatewayParentRef struct {
138+
// Name is the Gateway name.
139+
Name string `json:"name"`
140+
// Namespace is the Gateway namespace.
141+
// +optional
142+
Namespace *string `json:"namespace,omitempty"`
143+
// SectionName is the listener name within the Gateway.
144+
// +optional
145+
SectionName *string `json:"sectionName,omitempty"`
146+
}
147+
148+
// CertsSpec configures additional CA certificate mounts.
149+
type CertsSpec struct {
150+
// Secrets lists Secret key selectors for CA certificates.
151+
// Each is mounted at `/etc/ssl/certs/{name}.crt`.
152+
Secrets []CertSecretSelector `json:"secrets,omitempty"`
153+
}
154+
155+
// CertSecretSelector identifies a key within a Secret for CA cert mounting.
156+
type CertSecretSelector struct {
157+
// Name is the Secret name.
158+
Name string `json:"name"`
159+
// Key is the key within the Secret data map.
160+
Key string `json:"key"`
161+
}

0 commit comments

Comments
 (0)