Commit 968dd47
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
File tree
- api/v1alpha1
- config
- crd/bases
- rbac
- samples
- docs/reference/api
- internal
- app/controllerapp
- controller
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
| 22 | + | |
| 23 | + | |
22 | 24 | | |
23 | 25 | | |
24 | 26 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
13 | | - | |
| 13 | + | |
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
| |||
Lines changed: 7 additions & 7 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
71 | 71 | | |
72 | 72 | | |
73 | 73 | | |
74 | | - | |
| 74 | + | |
75 | 75 | | |
76 | 76 | | |
77 | | - | |
78 | | - | |
| 77 | + | |
| 78 | + | |
79 | 79 | | |
80 | 80 | | |
81 | 81 | | |
| |||
86 | 86 | | |
87 | 87 | | |
88 | 88 | | |
89 | | - | |
90 | | - | |
| 89 | + | |
| 90 | + | |
91 | 91 | | |
92 | 92 | | |
93 | | - | |
| 93 | + | |
94 | 94 | | |
95 | 95 | | |
96 | 96 | | |
97 | | - | |
| 97 | + | |
98 | 98 | | |
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 6 additions & 6 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
| 7 | + | |
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
11 | | - | |
12 | | - | |
13 | | - | |
14 | | - | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
20 | | - | |
| 20 | + | |
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
71 | 71 | | |
72 | 72 | | |
73 | 73 | | |
74 | | - | |
| 74 | + | |
75 | 75 | | |
76 | 76 | | |
77 | 77 | | |
| |||
85 | 85 | | |
86 | 86 | | |
87 | 87 | | |
88 | | - | |
| 88 | + | |
89 | 89 | | |
90 | 90 | | |
91 | 91 | | |
92 | 92 | | |
93 | 93 | | |
94 | 94 | | |
95 | 95 | | |
96 | | - | |
| 96 | + | |
97 | 97 | | |
98 | 98 | | |
99 | 99 | | |
| |||
Lines changed: 3 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | | - | |
| 2 | + | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
10 | | - | |
| 10 | + | |
11 | 11 | | |
Lines changed: 5 additions & 5 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
| 3 | + | |
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
8 | | - | |
9 | | - | |
| 8 | + | |
| 9 | + | |
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| |||
53 | 53 | | |
54 | 54 | | |
55 | 55 | | |
56 | | - | |
57 | | - | |
| 56 | + | |
| 57 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
95 | 95 | | |
96 | 96 | | |
97 | 97 | | |
98 | | - | |
| 98 | + | |
99 | 99 | | |
100 | 100 | | |
101 | 101 | | |
102 | 102 | | |
103 | | - | |
104 | | - | |
| 103 | + | |
| 104 | + | |
105 | 105 | | |
106 | 106 | | |
107 | 107 | | |
| |||
0 commit comments