Skip to content

Commit 968dd47

Browse files
authored
🤖 feat: replace WorkspaceProxy with CoderWorkspaceProxy (#61)
## Summary Replace the unreleased `WorkspaceProxy` CRD/controller with `CoderWorkspaceProxy` as the single workspace-proxy API in `coder.com/v1alpha1`. ## Background `WorkspaceProxy` was still under development and had not been released. Keeping both Kinds introduced unnecessary API/controller duplication and naming inconsistency with other `Coder*` resources. ## Implementation - Added `CoderWorkspaceProxy` API roots and registration. - Switched controller wiring to `CoderWorkspaceProxyReconciler` only. - Renamed/generated CRD, sample, and API reference docs to `CoderWorkspaceProxy`. - Removed legacy `WorkspaceProxy` root kind/list, CRD, sample, docs, controller, and controller tests. - Updated RBAC and scheme tests to the new Kind. ## Validation - `make verify-vendor` - `make test` - `make build` - `make lint` - `make docs-check` (run with generated docs staged) ## Risks Low-to-moderate: this removes an unreleased API surface and consolidates onto one Kind. Main regression risk is controller behavior drift during the rename, mitigated by preserving reconciliation logic and running full local validation. --- <details> <summary>📋 Implementation Plan</summary> # Plan: Rename `WorkspaceProxy` → `CoderWorkspaceProxy` ## Context / Why Today the operator exposes a namespaced CRD Kind `WorkspaceProxy` in `coder.com/v1alpha1`, while most other public CRDs in this repo use a `Coder*` prefix (e.g. `CoderControlPlane`, `CoderProvisioner`, `CoderWorkspace`, `CoderTemplate`). This is inconsistent in docs and creates Go naming collisions/confusion with upstream Coder concepts (there is also a `codersdk.WorkspaceProxy`). Goal: introduce a `CoderWorkspaceProxy` CRD and controller as the long-term supported API, with a practical migration path from the existing `WorkspaceProxy`. ## Evidence (repo facts) - Go API type + kubebuilder markers live in `api/v1alpha1/workspaceproxy_types.go` (`type WorkspaceProxy struct { ... }`, `+kubebuilder:resource:scope=Namespaced`). - Controller lives in `internal/controller/workspaceproxy_controller.go` and is wired in `internal/app/controllerapp/controllerapp.go`. - Generated artifacts that will change: - CRD manifest: `config/crd/bases/coder.com_workspaceproxies.yaml` - Deepcopy: `api/v1alpha1/zz_generated.deepcopy.go` - API reference docs: `docs/reference/api/workspaceproxy.md` - Docs nav: `mkdocs.yml` - Kubernetes **does not allow changing** a CRD’s `spec.names.kind` / `plural` in-place (they are immutable). Therefore a pure “rename” requires either: 1) destructive delete/recreate of the existing CRD, or 2) introducing a *new* CRD (new plural) and migrating users. ## Recommended approach (non-destructive): new CRD + migration, then remove old ### Phase 1 (Release N): Add `CoderWorkspaceProxy` alongside `WorkspaceProxy` #### 1) Add new API types (new Kind + plural) - **File(s):** - Add `api/v1alpha1/coderworkspaceproxy_types.go` (or extend `workspaceproxy_types.go` if preferred). - **New root objects:** - `CoderWorkspaceProxy` - `CoderWorkspaceProxyList` - **Reuse existing spec/status structs** (`WorkspaceProxySpec`, `WorkspaceProxyStatus`, `ProxyBootstrapSpec`) to avoid duplicating schema and docs. - **kubebuilder markers:** ensure the new resource gets its own plural (so it can coexist with the old CRD): ```go // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +kubebuilder:object:root=true // +kubebuilder:resource:path=coderworkspaceproxies,scope=Namespaced // +kubebuilder:subresource:status // // CoderWorkspaceProxy is the schema for Coder workspace proxy resources. // (Replacement for WorkspaceProxy.) type CoderWorkspaceProxy struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` Spec WorkspaceProxySpec `json:"spec,omitempty"` Status WorkspaceProxyStatus `json:"status,omitempty"` } ``` - Register the new types with the scheme (either via a new `init()` or existing one): - `SchemeBuilder.Register(&CoderWorkspaceProxy{}, &CoderWorkspaceProxyList{})` #### 2) Generate new CRD + deepcopy - Run generators: - `make codegen` - `make manifests` - Expected outputs: - New CRD file: `config/crd/bases/coder.com_coderworkspaceproxies.yaml` - Existing CRD file remains for `workspaceproxies.coder.com`. #### 3) Add a new controller for `CoderWorkspaceProxy` - **File:** `internal/controller/coderworkspaceproxy_controller.go` - **Type:** `CoderWorkspaceProxyReconciler` - Implementation choices: - Prefer factoring shared logic out of the current `WorkspaceProxyReconciler` into helper functions (naming/labels, secret reads, deployment/service reconcile) so the behavior stays identical and future fixes apply to both during the transition. - **RBAC markers:** add rules for `coderworkspaceproxies` (+ status/finalizers). #### 4) Handle adoption of existing child resources (important for smooth migration) Existing `WorkspaceProxy` instances may already own a Deployment/Service/Secret via a controller ownerRef. When a user creates a new `CoderWorkspaceProxy` with the same name, the new controller should “adopt” those children instead of failing on `SetControllerReference`. - Add a small “adopt if owned-by WorkspaceProxy” helper used by all child reconcile paths: ```go func adoptIfOwnedByWorkspaceProxy(obj metav1.Object, legacy *coderv1alpha1.WorkspaceProxy) bool { // Pseudocode: // - Inspect obj.GetOwnerReferences() for controller=true entry. // - If controllerRef is WorkspaceProxy and matches legacy name/uid: replace it with ownerRef to new CoderWorkspaceProxy. // - Return true if adoption happened. // - If controllerRef exists but is NOT legacy WorkspaceProxy: return false and let caller error (defensive). } ``` - Defensive programming expectations: - If the existing controllerRef is *not* the legacy `WorkspaceProxy`, fail fast (this prevents accidental adoption of unrelated resources). #### 5) Wire controller + keep legacy behavior clear - Update `internal/app/controllerapp/controllerapp.go` to set up the new reconciler. - Decide legacy reconciliation policy: - **Recommended:** stop running the old `WorkspaceProxyReconciler` in the manager (so we don’t have two controllers fighting over the same child resources), but keep the CRD/types temporarily so users can still read/export their legacy objects. - Optionally add a minimal “legacy warning” reconciler that only sets a `Deprecated` Condition on `WorkspaceProxy` objects (no child management) to guide users. #### 6) Update/extend tests - Update controller tests to cover the new Kind: - Either parameterize the existing tests in `internal/controller/workspaceproxy_controller_test.go`, or add a parallel file (e.g. `internal/controller/coderworkspaceproxy_controller_test.go`). - Add a focused adoption test to prevent regressions: 1. Create a legacy `WorkspaceProxy` and reconcile once to create children. 2. Create a `CoderWorkspaceProxy` with the same name/namespace. 3. Reconcile the new controller and assert child resources’ controller ownerRef was moved from `WorkspaceProxy` → `CoderWorkspaceProxy`. ### Phase 2 (same release N): Update samples + docs to prefer `CoderWorkspaceProxy` #### 7) Samples / manifests - Add a new sample: - `config/samples/coder_v1alpha1_coderworkspaceproxy.yaml` (Kind `CoderWorkspaceProxy`). - Keep the old sample for one release, but mark it deprecated in comments. #### 8) Docs + docs nav - Update: - `README.md`: replace `WorkspaceProxy` mentions with `CoderWorkspaceProxy`. - `mkdocs.yml`: generated API nav entry should become `CoderWorkspaceProxy: reference/api/coderworkspaceproxy.md`. - Regenerate API reference docs: - `make docs-reference` - (and confirm `make docs-check` passes) ### Phase 3 (Release N+1): Remove legacy `WorkspaceProxy` #### 9) Remove legacy API + CRD - Delete/stop generating the legacy `WorkspaceProxy` root types and its CRD. - Remove legacy controller code and any remaining references. - Regenerate: - `make codegen` - `make manifests` - `make docs-reference` ## Migration guide (for Release N) 1. Upgrade the operator (now serving both CRDs). 2. For each legacy `WorkspaceProxy` named `X`: - Create a `CoderWorkspaceProxy` with the **same name/namespace** and the same spec. - Verify the new controller has adopted the existing Deployment/Service/Secret and the status becomes Ready. - Delete the legacy `WorkspaceProxy` once stable **after** adoption completes (otherwise Kubernetes GC may delete the child Deployment/Service/Secret). <details> <summary>Alternative (development-only): destructive rename</summary> Because CRD Kind/Plural are immutable, the only way to keep the *same* REST resource `workspaceproxies.coder.com` while changing the Kind to `CoderWorkspaceProxy` is: 1) export all existing `WorkspaceProxy` objects, 2) delete the CRD (which deletes all CRs), 3) recreate the CRD with the new Kind, and 4) re-apply the exported objects (updated Kind). This is not recommended for production clusters. </details> ## Validation (what to run when implementing) - `make test` - `make test-integration` (if controller behavior changes materially) - `make lint` - `make build` - `make verify-vendor` (should be a no-op unless deps changed) - `make manifests && git diff --exit-code config/` (confirm generated output is committed) - `make docs-reference && make docs-check` </details> --- _Generated with [`mux`](https://github.com/coder/mux) • Model: `openai:gpt-5.3-codex` • Thinking: `xhigh` • Cost: `$0.62`_ <!-- mux-attribution: model=openai:gpt-5.3-codex thinking=xhigh costs=0.62 -->
1 parent b40367d commit 968dd47

13 files changed

Lines changed: 130 additions & 128 deletions

.cspell.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
"codertemplates",
2020
"coderworkspace",
2121
"coderworkspaces",
22+
"coderworkspaceproxy",
23+
"coderworkspaceproxies",
2224
"coderprovisioner",
2325
"coderprovisioners",
2426
"controllerapp",

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
`coder-k8s` is a Go-based Kubernetes control-plane project with two app modes:
1111

1212
- A `controller-runtime` operator for managing `CoderControlPlane` and
13-
`WorkspaceProxy` resources (`coder.com/v1alpha1`).
13+
`CoderWorkspaceProxy` resources (`coder.com/v1alpha1`).
1414
- An aggregated API server for `CoderWorkspace` and `CoderTemplate` resources
1515
(`aggregation.coder.com/v1alpha1`).
1616

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,11 @@ type WorkspaceProxyStatus struct {
7171

7272
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
7373
// +kubebuilder:object:root=true
74-
// +kubebuilder:resource:scope=Namespaced
74+
// +kubebuilder:resource:path=coderworkspaceproxies,scope=Namespaced
7575
// +kubebuilder:subresource:status
7676

77-
// WorkspaceProxy is the schema for Coder workspace proxy resources.
78-
type WorkspaceProxy struct {
77+
// CoderWorkspaceProxy is the schema for Coder workspace proxy resources.
78+
type CoderWorkspaceProxy struct {
7979
metav1.TypeMeta `json:",inline"`
8080
metav1.ObjectMeta `json:"metadata,omitempty"`
8181

@@ -86,13 +86,13 @@ type WorkspaceProxy struct {
8686
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
8787
// +kubebuilder:object:root=true
8888

89-
// WorkspaceProxyList contains a list of WorkspaceProxy objects.
90-
type WorkspaceProxyList struct {
89+
// CoderWorkspaceProxyList contains a list of CoderWorkspaceProxy objects.
90+
type CoderWorkspaceProxyList struct {
9191
metav1.TypeMeta `json:",inline"`
9292
metav1.ListMeta `json:"metadata,omitempty"`
93-
Items []WorkspaceProxy `json:"items"`
93+
Items []CoderWorkspaceProxy `json:"items"`
9494
}
9595

9696
func init() {
97-
SchemeBuilder.Register(&WorkspaceProxy{}, &WorkspaceProxyList{})
97+
SchemeBuilder.Register(&CoderWorkspaceProxy{}, &CoderWorkspaceProxyList{})
9898
}

api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 59 additions & 59 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/coder.com_workspaceproxies.yaml renamed to config/crd/bases/coder.com_coderworkspaceproxies.yaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,20 @@ kind: CustomResourceDefinition
44
metadata:
55
annotations:
66
controller-gen.kubebuilder.io/version: v0.20.0
7-
name: workspaceproxies.coder.com
7+
name: coderworkspaceproxies.coder.com
88
spec:
99
group: coder.com
1010
names:
11-
kind: WorkspaceProxy
12-
listKind: WorkspaceProxyList
13-
plural: workspaceproxies
14-
singular: workspaceproxy
11+
kind: CoderWorkspaceProxy
12+
listKind: CoderWorkspaceProxyList
13+
plural: coderworkspaceproxies
14+
singular: coderworkspaceproxy
1515
scope: Namespaced
1616
versions:
1717
- name: v1alpha1
1818
schema:
1919
openAPIV3Schema:
20-
description: WorkspaceProxy is the schema for Coder workspace proxy resources.
20+
description: CoderWorkspaceProxy is the schema for Coder workspace proxy resources.
2121
properties:
2222
apiVersion:
2323
description: |-

config/rbac/role.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ rules:
7171
resources:
7272
- codercontrolplanes
7373
- coderprovisioners
74-
- workspaceproxies
74+
- coderworkspaceproxies
7575
verbs:
7676
- create
7777
- delete
@@ -85,15 +85,15 @@ rules:
8585
resources:
8686
- codercontrolplanes/finalizers
8787
- coderprovisioners/finalizers
88-
- workspaceproxies/finalizers
88+
- coderworkspaceproxies/finalizers
8989
verbs:
9090
- update
9191
- apiGroups:
9292
- coder.com
9393
resources:
9494
- codercontrolplanes/status
9595
- coderprovisioners/status
96-
- workspaceproxies/status
96+
- coderworkspaceproxies/status
9797
verbs:
9898
- get
9999
- patch
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
apiVersion: coder.com/v1alpha1
2-
kind: WorkspaceProxy
2+
kind: CoderWorkspaceProxy
33
metadata:
4-
name: workspaceproxy-sample
4+
name: coderworkspaceproxy-sample
55
namespace: default
66
spec:
77
image: "ghcr.io/coder/coder:latest"
88
primaryAccessURL: "https://coder.example.com"
99
proxySessionTokenSecretRef:
10-
name: workspaceproxy-token
10+
name: coderworkspaceproxy-token
1111
key: token
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
<!-- Code generated by hack/update-reference-docs.sh using github.com/elastic/crd-ref-docs. DO NOT EDIT. -->
22

3-
# `WorkspaceProxy`
3+
# `CoderWorkspaceProxy`
44

55
## API identity
66

77
- Group/version: `coder.com/v1alpha1`
8-
- Kind: `WorkspaceProxy`
9-
- Resource: `workspaceproxies`
8+
- Kind: `CoderWorkspaceProxy`
9+
- Resource: `coderworkspaceproxies`
1010
- Scope: namespaced
1111

1212
## Spec
@@ -53,5 +53,5 @@
5353

5454
## Source
5555

56-
- Go type: `api/v1alpha1/workspaceproxy_types.go`
57-
- Generated CRD: `config/crd/bases/coder.com_workspaceproxies.yaml`
56+
- Go type: `api/v1alpha1/coderworkspaceproxy_types.go`
57+
- Generated CRD: `config/crd/bases/coder.com_coderworkspaceproxies.yaml`

internal/app/controllerapp/controllerapp.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,13 @@ func SetupControllers(mgr manager.Manager) error {
9595
return fmt.Errorf("unable to create controller: %w", err)
9696
}
9797

98-
workspaceProxyReconciler := &controller.WorkspaceProxyReconciler{
98+
coderWorkspaceProxyReconciler := &controller.CoderWorkspaceProxyReconciler{
9999
Client: client,
100100
Scheme: managerScheme,
101101
BootstrapClient: coderbootstrap.NewSDKClient(),
102102
}
103-
if err := workspaceProxyReconciler.SetupWithManager(mgr); err != nil {
104-
return fmt.Errorf("unable to create workspace proxy controller: %w", err)
103+
if err := coderWorkspaceProxyReconciler.SetupWithManager(mgr); err != nil {
104+
return fmt.Errorf("unable to create coder workspace proxy controller: %w", err)
105105
}
106106

107107
provisionerReconciler := &controller.CoderProvisionerReconciler{

0 commit comments

Comments
 (0)