From 10780f531685010f7cc200e7d0d7e7b6a167abfd Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Thu, 12 Feb 2026 11:08:02 +0000 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=A4=96=20feat:=20replace=20WorkspaceP?= =?UTF-8?q?roxy=20with=20CoderWorkspaceProxy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the unreleased WorkspaceProxy legacy kind/controller path and keep CoderWorkspaceProxy as the single workspace-proxy API surface. - add CoderWorkspaceProxy API type, CRD, docs, sample, and controller/tests - remove WorkspaceProxy API roots, CRD, docs, sample, and controller/tests - update controller wiring, RBAC, and scheme tests to reference only the new kind --- _Generated with [`mux`](https://github.com/coder/mux) • Model: `openai:gpt-5.3-codex` • Thinking: `xhigh` • Cost: `$0.62`_ --- README.md | 2 +- api/v1alpha1/coderworkspaceproxy_types.go | 31 +++++ api/v1alpha1/workspaceproxy_types.go | 28 ----- api/v1alpha1/zz_generated.deepcopy.go | 118 +++++++++--------- ...l => coder.com_coderworkspaceproxies.yaml} | 12 +- config/rbac/role.yaml | 6 +- ...> coder_v1alpha1_coderworkspaceproxy.yaml} | 6 +- ...rkspaceproxy.md => coderworkspaceproxy.md} | 10 +- internal/app/controllerapp/controllerapp.go | 6 +- ...r.go => coderworkspaceproxy_controller.go} | 44 +++---- ...=> coderworkspaceproxy_controller_test.go} | 32 ++--- main_test.go | 4 +- mkdocs.yml | 2 +- 13 files changed, 152 insertions(+), 149 deletions(-) create mode 100644 api/v1alpha1/coderworkspaceproxy_types.go rename config/crd/bases/{coder.com_workspaceproxies.yaml => coder.com_coderworkspaceproxies.yaml} (98%) rename config/samples/{coder_v1alpha1_workspaceproxy.yaml => coder_v1alpha1_coderworkspaceproxy.yaml} (67%) rename docs/reference/api/{workspaceproxy.md => coderworkspaceproxy.md} (94%) rename internal/controller/{workspaceproxy_controller.go => coderworkspaceproxy_controller.go} (88%) rename internal/controller/{workspaceproxy_controller_test.go => coderworkspaceproxy_controller_test.go} (92%) diff --git a/README.md b/README.md index 20c6e845..632f2d5d 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ `coder-k8s` is a Go-based Kubernetes control-plane project with two app modes: - A `controller-runtime` operator for managing `CoderControlPlane` and - `WorkspaceProxy` resources (`coder.com/v1alpha1`). + `CoderWorkspaceProxy` resources (`coder.com/v1alpha1`). - An aggregated API server for `CoderWorkspace` and `CoderTemplate` resources (`aggregation.coder.com/v1alpha1`). diff --git a/api/v1alpha1/coderworkspaceproxy_types.go b/api/v1alpha1/coderworkspaceproxy_types.go new file mode 100644 index 00000000..029713a5 --- /dev/null +++ b/api/v1alpha1/coderworkspaceproxy_types.go @@ -0,0 +1,31 @@ +package v1alpha1 + +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +// +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. +type CoderWorkspaceProxy struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec WorkspaceProxySpec `json:"spec,omitempty"` + Status WorkspaceProxyStatus `json:"status,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:object:root=true + +// CoderWorkspaceProxyList contains a list of CoderWorkspaceProxy objects. +type CoderWorkspaceProxyList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []CoderWorkspaceProxy `json:"items"` +} + +func init() { + SchemeBuilder.Register(&CoderWorkspaceProxy{}, &CoderWorkspaceProxyList{}) +} diff --git a/api/v1alpha1/workspaceproxy_types.go b/api/v1alpha1/workspaceproxy_types.go index 1f8bc4ed..7e7a4d53 100644 --- a/api/v1alpha1/workspaceproxy_types.go +++ b/api/v1alpha1/workspaceproxy_types.go @@ -68,31 +68,3 @@ type WorkspaceProxyStatus struct { // Conditions are Kubernetes-standard conditions for this resource. Conditions []metav1.Condition `json:"conditions,omitempty"` } - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// +kubebuilder:object:root=true -// +kubebuilder:resource:scope=Namespaced -// +kubebuilder:subresource:status - -// WorkspaceProxy is the schema for Coder workspace proxy resources. -type WorkspaceProxy struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec WorkspaceProxySpec `json:"spec,omitempty"` - Status WorkspaceProxyStatus `json:"status,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// +kubebuilder:object:root=true - -// WorkspaceProxyList contains a list of WorkspaceProxy objects. -type WorkspaceProxyList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []WorkspaceProxy `json:"items"` -} - -func init() { - SchemeBuilder.Register(&WorkspaceProxy{}, &WorkspaceProxyList{}) -} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index f231cf81..59eab117 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -317,138 +317,138 @@ func (in *CoderProvisionerStatus) DeepCopy() *CoderProvisionerStatus { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *OperatorAccessSpec) DeepCopyInto(out *OperatorAccessSpec) { +func (in *CoderWorkspaceProxy) DeepCopyInto(out *CoderWorkspaceProxy) { *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OperatorAccessSpec. -func (in *OperatorAccessSpec) DeepCopy() *OperatorAccessSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CoderWorkspaceProxy. +func (in *CoderWorkspaceProxy) DeepCopy() *CoderWorkspaceProxy { if in == nil { return nil } - out := new(OperatorAccessSpec) + out := new(CoderWorkspaceProxy) in.DeepCopyInto(out) return out } +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CoderWorkspaceProxy) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ProxyBootstrapSpec) DeepCopyInto(out *ProxyBootstrapSpec) { +func (in *CoderWorkspaceProxyList) DeepCopyInto(out *CoderWorkspaceProxyList) { *out = *in - out.CredentialsSecretRef = in.CredentialsSecretRef + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]CoderWorkspaceProxy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyBootstrapSpec. -func (in *ProxyBootstrapSpec) DeepCopy() *ProxyBootstrapSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CoderWorkspaceProxyList. +func (in *CoderWorkspaceProxyList) DeepCopy() *CoderWorkspaceProxyList { if in == nil { return nil } - out := new(ProxyBootstrapSpec) + out := new(CoderWorkspaceProxyList) in.DeepCopyInto(out) return out } +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CoderWorkspaceProxyList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SecretKeySelector) DeepCopyInto(out *SecretKeySelector) { +func (in *OperatorAccessSpec) DeepCopyInto(out *OperatorAccessSpec) { *out = *in return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretKeySelector. -func (in *SecretKeySelector) DeepCopy() *SecretKeySelector { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OperatorAccessSpec. +func (in *OperatorAccessSpec) DeepCopy() *OperatorAccessSpec { if in == nil { return nil } - out := new(SecretKeySelector) + out := new(OperatorAccessSpec) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ServiceSpec) DeepCopyInto(out *ServiceSpec) { +func (in *ProxyBootstrapSpec) DeepCopyInto(out *ProxyBootstrapSpec) { *out = *in - if in.Annotations != nil { - in, out := &in.Annotations, &out.Annotations - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } + out.CredentialsSecretRef = in.CredentialsSecretRef return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceSpec. -func (in *ServiceSpec) DeepCopy() *ServiceSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyBootstrapSpec. +func (in *ProxyBootstrapSpec) DeepCopy() *ProxyBootstrapSpec { if in == nil { return nil } - out := new(ServiceSpec) + out := new(ProxyBootstrapSpec) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *WorkspaceProxy) DeepCopyInto(out *WorkspaceProxy) { +func (in *SecretKeySelector) DeepCopyInto(out *SecretKeySelector) { *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkspaceProxy. -func (in *WorkspaceProxy) DeepCopy() *WorkspaceProxy { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretKeySelector. +func (in *SecretKeySelector) DeepCopy() *SecretKeySelector { if in == nil { return nil } - out := new(WorkspaceProxy) + out := new(SecretKeySelector) in.DeepCopyInto(out) return out } -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *WorkspaceProxy) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *WorkspaceProxyList) DeepCopyInto(out *WorkspaceProxyList) { +func (in *ServiceSpec) DeepCopyInto(out *ServiceSpec) { *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]WorkspaceProxy, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val } } return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkspaceProxyList. -func (in *WorkspaceProxyList) DeepCopy() *WorkspaceProxyList { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceSpec. +func (in *ServiceSpec) DeepCopy() *ServiceSpec { if in == nil { return nil } - out := new(WorkspaceProxyList) + out := new(ServiceSpec) in.DeepCopyInto(out) return out } -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *WorkspaceProxyList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WorkspaceProxySpec) DeepCopyInto(out *WorkspaceProxySpec) { *out = *in diff --git a/config/crd/bases/coder.com_workspaceproxies.yaml b/config/crd/bases/coder.com_coderworkspaceproxies.yaml similarity index 98% rename from config/crd/bases/coder.com_workspaceproxies.yaml rename to config/crd/bases/coder.com_coderworkspaceproxies.yaml index fe9041d9..6731e867 100644 --- a/config/crd/bases/coder.com_workspaceproxies.yaml +++ b/config/crd/bases/coder.com_coderworkspaceproxies.yaml @@ -4,20 +4,20 @@ kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.20.0 - name: workspaceproxies.coder.com + name: coderworkspaceproxies.coder.com spec: group: coder.com names: - kind: WorkspaceProxy - listKind: WorkspaceProxyList - plural: workspaceproxies - singular: workspaceproxy + kind: CoderWorkspaceProxy + listKind: CoderWorkspaceProxyList + plural: coderworkspaceproxies + singular: coderworkspaceproxy scope: Namespaced versions: - name: v1alpha1 schema: openAPIV3Schema: - description: WorkspaceProxy is the schema for Coder workspace proxy resources. + description: CoderWorkspaceProxy is the schema for Coder workspace proxy resources. properties: apiVersion: description: |- diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index c428c573..9dd3a4dc 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -71,7 +71,7 @@ rules: resources: - codercontrolplanes - coderprovisioners - - workspaceproxies + - coderworkspaceproxies verbs: - create - delete @@ -85,7 +85,7 @@ rules: resources: - codercontrolplanes/finalizers - coderprovisioners/finalizers - - workspaceproxies/finalizers + - coderworkspaceproxies/finalizers verbs: - update - apiGroups: @@ -93,7 +93,7 @@ rules: resources: - codercontrolplanes/status - coderprovisioners/status - - workspaceproxies/status + - coderworkspaceproxies/status verbs: - get - patch diff --git a/config/samples/coder_v1alpha1_workspaceproxy.yaml b/config/samples/coder_v1alpha1_coderworkspaceproxy.yaml similarity index 67% rename from config/samples/coder_v1alpha1_workspaceproxy.yaml rename to config/samples/coder_v1alpha1_coderworkspaceproxy.yaml index f00a3fe8..ac33e030 100644 --- a/config/samples/coder_v1alpha1_workspaceproxy.yaml +++ b/config/samples/coder_v1alpha1_coderworkspaceproxy.yaml @@ -1,11 +1,11 @@ apiVersion: coder.com/v1alpha1 -kind: WorkspaceProxy +kind: CoderWorkspaceProxy metadata: - name: workspaceproxy-sample + name: coderworkspaceproxy-sample namespace: default spec: image: "ghcr.io/coder/coder:latest" primaryAccessURL: "https://coder.example.com" proxySessionTokenSecretRef: - name: workspaceproxy-token + name: coderworkspaceproxy-token key: token diff --git a/docs/reference/api/workspaceproxy.md b/docs/reference/api/coderworkspaceproxy.md similarity index 94% rename from docs/reference/api/workspaceproxy.md rename to docs/reference/api/coderworkspaceproxy.md index 462712c7..5ddc9a88 100644 --- a/docs/reference/api/workspaceproxy.md +++ b/docs/reference/api/coderworkspaceproxy.md @@ -1,12 +1,12 @@ -# `WorkspaceProxy` +# `CoderWorkspaceProxy` ## API identity - Group/version: `coder.com/v1alpha1` -- Kind: `WorkspaceProxy` -- Resource: `workspaceproxies` +- Kind: `CoderWorkspaceProxy` +- Resource: `coderworkspaceproxies` - Scope: namespaced ## Spec @@ -53,5 +53,5 @@ ## Source -- Go type: `api/v1alpha1/workspaceproxy_types.go` -- Generated CRD: `config/crd/bases/coder.com_workspaceproxies.yaml` +- Go type: `api/v1alpha1/coderworkspaceproxy_types.go` +- Generated CRD: `config/crd/bases/coder.com_coderworkspaceproxies.yaml` diff --git a/internal/app/controllerapp/controllerapp.go b/internal/app/controllerapp/controllerapp.go index 1003e93a..e54ccf5d 100644 --- a/internal/app/controllerapp/controllerapp.go +++ b/internal/app/controllerapp/controllerapp.go @@ -95,13 +95,13 @@ func SetupControllers(mgr manager.Manager) error { return fmt.Errorf("unable to create controller: %w", err) } - workspaceProxyReconciler := &controller.WorkspaceProxyReconciler{ + coderWorkspaceProxyReconciler := &controller.CoderWorkspaceProxyReconciler{ Client: client, Scheme: managerScheme, BootstrapClient: coderbootstrap.NewSDKClient(), } - if err := workspaceProxyReconciler.SetupWithManager(mgr); err != nil { - return fmt.Errorf("unable to create workspace proxy controller: %w", err) + if err := coderWorkspaceProxyReconciler.SetupWithManager(mgr); err != nil { + return fmt.Errorf("unable to create coder workspace proxy controller: %w", err) } provisionerReconciler := &controller.CoderProvisionerReconciler{ diff --git a/internal/controller/workspaceproxy_controller.go b/internal/controller/coderworkspaceproxy_controller.go similarity index 88% rename from internal/controller/workspaceproxy_controller.go rename to internal/controller/coderworkspaceproxy_controller.go index faa48e2d..9e2c9948 100644 --- a/internal/controller/workspaceproxy_controller.go +++ b/internal/controller/coderworkspaceproxy_controller.go @@ -29,22 +29,22 @@ const ( workspaceProxyNamePrefix = "wsproxy-" ) -// WorkspaceProxyReconciler reconciles a WorkspaceProxy object. -type WorkspaceProxyReconciler struct { +// CoderWorkspaceProxyReconciler reconciles a CoderWorkspaceProxy object. +type CoderWorkspaceProxyReconciler struct { client.Client Scheme *runtime.Scheme BootstrapClient coderbootstrap.Client } -// +kubebuilder:rbac:groups=coder.com,resources=workspaceproxies,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=coder.com,resources=workspaceproxies/status,verbs=get;update;patch -// +kubebuilder:rbac:groups=coder.com,resources=workspaceproxies/finalizers,verbs=update +// +kubebuilder:rbac:groups=coder.com,resources=coderworkspaceproxies,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=coder.com,resources=coderworkspaceproxies/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=coder.com,resources=coderworkspaceproxies/finalizers,verbs=update // +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch;delete -// Reconcile converges the desired WorkspaceProxy spec into Deployment and Service resources. -func (r *WorkspaceProxyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { +// Reconcile converges the desired CoderWorkspaceProxy spec into Deployment and Service resources. +func (r *CoderWorkspaceProxyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { if r.Client == nil { return ctrl.Result{}, fmt.Errorf("assertion failed: reconciler client must not be nil") } @@ -52,12 +52,12 @@ func (r *WorkspaceProxyReconciler) Reconcile(ctx context.Context, req ctrl.Reque return ctrl.Result{}, fmt.Errorf("assertion failed: reconciler scheme must not be nil") } - workspaceProxy := &coderv1alpha1.WorkspaceProxy{} + workspaceProxy := &coderv1alpha1.CoderWorkspaceProxy{} if err := r.Get(ctx, req.NamespacedName, workspaceProxy); err != nil { if apierrors.IsNotFound(err) { return ctrl.Result{}, nil } - return ctrl.Result{}, fmt.Errorf("get workspaceproxy %s: %w", req.NamespacedName, err) + return ctrl.Result{}, fmt.Errorf("get coderworkspaceproxy %s: %w", req.NamespacedName, err) } if workspaceProxy.Name != req.Name || workspaceProxy.Namespace != req.Namespace { @@ -92,9 +92,9 @@ func (r *WorkspaceProxyReconciler) Reconcile(ctx context.Context, req ctrl.Reque return ctrl.Result{}, nil } -func (r *WorkspaceProxyReconciler) resolveProxyCredentials( +func (r *CoderWorkspaceProxyReconciler) resolveProxyCredentials( ctx context.Context, - workspaceProxy *coderv1alpha1.WorkspaceProxy, + workspaceProxy *coderv1alpha1.CoderWorkspaceProxy, ) (string, *coderv1alpha1.SecretKeySelector, bool, error) { if workspaceProxy.Spec.Bootstrap == nil { tokenRef := workspaceProxy.Spec.ProxySessionTokenSecretRef @@ -162,9 +162,9 @@ func (r *WorkspaceProxyReconciler) resolveProxyCredentials( return primary, &coderv1alpha1.SecretKeySelector{Name: tokenSecretName, Key: proxyTokenKey}, true, nil } -func (r *WorkspaceProxyReconciler) reconcileDeployment( +func (r *CoderWorkspaceProxyReconciler) reconcileDeployment( ctx context.Context, - workspaceProxy *coderv1alpha1.WorkspaceProxy, + workspaceProxy *coderv1alpha1.CoderWorkspaceProxy, primaryAccessURL string, tokenRef *coderv1alpha1.SecretKeySelector, ) (*appsv1.Deployment, error) { @@ -232,7 +232,7 @@ func (r *WorkspaceProxyReconciler) reconcileDeployment( return deployment, nil } -func (r *WorkspaceProxyReconciler) reconcileService(ctx context.Context, workspaceProxy *coderv1alpha1.WorkspaceProxy) (*corev1.Service, error) { +func (r *CoderWorkspaceProxyReconciler) reconcileService(ctx context.Context, workspaceProxy *coderv1alpha1.CoderWorkspaceProxy) (*corev1.Service, error) { serviceName := workspaceProxyResourceName(workspaceProxy.Name) service := &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: workspaceProxy.Namespace}} @@ -273,9 +273,9 @@ func (r *WorkspaceProxyReconciler) reconcileService(ctx context.Context, workspa return service, nil } -func (r *WorkspaceProxyReconciler) reconcileStatus( +func (r *CoderWorkspaceProxyReconciler) reconcileStatus( ctx context.Context, - workspaceProxy *coderv1alpha1.WorkspaceProxy, + workspaceProxy *coderv1alpha1.CoderWorkspaceProxy, deployment *appsv1.Deployment, _ *corev1.Service, tokenRef *coderv1alpha1.SecretKeySelector, @@ -308,9 +308,9 @@ func (r *WorkspaceProxyReconciler) reconcileStatus( return nil } -func (r *WorkspaceProxyReconciler) ensureTokenSecret( +func (r *CoderWorkspaceProxyReconciler) ensureTokenSecret( ctx context.Context, - workspaceProxy *coderv1alpha1.WorkspaceProxy, + workspaceProxy *coderv1alpha1.CoderWorkspaceProxy, name string, key string, token string, @@ -337,7 +337,7 @@ func (r *WorkspaceProxyReconciler) ensureTokenSecret( return nil } -func (r *WorkspaceProxyReconciler) readSecretValue(ctx context.Context, namespace, name, key string) (string, error) { +func (r *CoderWorkspaceProxyReconciler) readSecretValue(ctx context.Context, namespace, name, key string) (string, error) { secret := &corev1.Secret{} if err := r.Get(ctx, types.NamespacedName{Name: name, Namespace: namespace}, secret); err != nil { return "", err @@ -355,7 +355,7 @@ func (r *WorkspaceProxyReconciler) readSecretValue(ctx context.Context, namespac } // SetupWithManager wires the reconciler into controller-runtime. -func (r *WorkspaceProxyReconciler) SetupWithManager(mgr ctrl.Manager) error { +func (r *CoderWorkspaceProxyReconciler) SetupWithManager(mgr ctrl.Manager) error { if mgr == nil { return fmt.Errorf("assertion failed: manager must not be nil") } @@ -367,11 +367,11 @@ func (r *WorkspaceProxyReconciler) SetupWithManager(mgr ctrl.Manager) error { } return ctrl.NewControllerManagedBy(mgr). - For(&coderv1alpha1.WorkspaceProxy{}). + For(&coderv1alpha1.CoderWorkspaceProxy{}). Owns(&appsv1.Deployment{}). Owns(&corev1.Service{}). Owns(&corev1.Secret{}). - Named("workspaceproxy"). + Named("coderworkspaceproxy"). Complete(r) } diff --git a/internal/controller/workspaceproxy_controller_test.go b/internal/controller/coderworkspaceproxy_controller_test.go similarity index 92% rename from internal/controller/workspaceproxy_controller_test.go rename to internal/controller/coderworkspaceproxy_controller_test.go index 1ec0e70d..861607b6 100644 --- a/internal/controller/workspaceproxy_controller_test.go +++ b/internal/controller/coderworkspaceproxy_controller_test.go @@ -108,7 +108,7 @@ func workspaceProxyInstanceLabelValue(name string) string { return fmt.Sprintf("%s-%s", name[:available], suffix) } -func TestWorkspaceProxyReconcile_UsingDirectTokenSecret(t *testing.T) { +func TestCoderWorkspaceProxyReconcile_UsingDirectTokenSecret(t *testing.T) { ctx := context.Background() secretName := "proxy-session-token" @@ -126,7 +126,7 @@ func TestWorkspaceProxyReconcile_UsingDirectTokenSecret(t *testing.T) { _ = k8sClient.Delete(ctx, secret) }) - workspaceProxy := &coderv1alpha1.WorkspaceProxy{ + workspaceProxy := &coderv1alpha1.CoderWorkspaceProxy{ ObjectMeta: metav1.ObjectMeta{Name: "proxy-direct", Namespace: "default"}, Spec: coderv1alpha1.WorkspaceProxySpec{ Image: "proxy-image:latest", @@ -144,7 +144,7 @@ func TestWorkspaceProxyReconcile_UsingDirectTokenSecret(t *testing.T) { _ = k8sClient.Delete(ctx, workspaceProxy) }) - reconciler := &controller.WorkspaceProxyReconciler{Client: k8sClient, Scheme: scheme} + reconciler := &controller.CoderWorkspaceProxyReconciler{Client: k8sClient, Scheme: scheme} result, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: types.NamespacedName{Name: workspaceProxy.Name, Namespace: workspaceProxy.Namespace}}) if err != nil { t.Fatalf("reconcile workspace proxy: %v", err) @@ -177,7 +177,7 @@ func TestWorkspaceProxyReconcile_UsingDirectTokenSecret(t *testing.T) { } } -func TestWorkspaceProxyReconcile_DoesNotCollideWithControlPlaneChildren(t *testing.T) { +func TestCoderWorkspaceProxyReconcile_DoesNotCollideWithControlPlaneChildren(t *testing.T) { ctx := context.Background() resourceName := "shared-name" @@ -208,7 +208,7 @@ func TestWorkspaceProxyReconcile_DoesNotCollideWithControlPlaneChildren(t *testi _ = k8sClient.Delete(ctx, controlPlane) }) - workspaceProxy := &coderv1alpha1.WorkspaceProxy{ + workspaceProxy := &coderv1alpha1.CoderWorkspaceProxy{ ObjectMeta: metav1.ObjectMeta{Name: resourceName, Namespace: "default"}, Spec: coderv1alpha1.WorkspaceProxySpec{ Image: "proxy-image:latest", @@ -231,7 +231,7 @@ func TestWorkspaceProxyReconcile_DoesNotCollideWithControlPlaneChildren(t *testi t.Fatalf("reconcile control plane: %v", err) } - workspaceProxyReconciler := &controller.WorkspaceProxyReconciler{Client: k8sClient, Scheme: scheme} + workspaceProxyReconciler := &controller.CoderWorkspaceProxyReconciler{Client: k8sClient, Scheme: scheme} if _, err := workspaceProxyReconciler.Reconcile(ctx, ctrl.Request{NamespacedName: types.NamespacedName{Name: workspaceProxy.Name, Namespace: workspaceProxy.Namespace}}); err != nil { t.Fatalf("reconcile workspace proxy: %v", err) } @@ -251,7 +251,7 @@ func TestWorkspaceProxyReconcile_DoesNotCollideWithControlPlaneChildren(t *testi } } -func TestWorkspaceProxyReconcile_TruncatesLongInstanceLabelValue(t *testing.T) { +func TestCoderWorkspaceProxyReconcile_TruncatesLongInstanceLabelValue(t *testing.T) { ctx := context.Background() resourceName := strings.Repeat("a", 70) secretName := "proxy-long-name-token" @@ -270,7 +270,7 @@ func TestWorkspaceProxyReconcile_TruncatesLongInstanceLabelValue(t *testing.T) { _ = k8sClient.Delete(ctx, secret) }) - workspaceProxy := &coderv1alpha1.WorkspaceProxy{ + workspaceProxy := &coderv1alpha1.CoderWorkspaceProxy{ ObjectMeta: metav1.ObjectMeta{Name: resourceName, Namespace: "default"}, Spec: coderv1alpha1.WorkspaceProxySpec{ Image: "proxy-image:latest", @@ -288,7 +288,7 @@ func TestWorkspaceProxyReconcile_TruncatesLongInstanceLabelValue(t *testing.T) { _ = k8sClient.Delete(ctx, workspaceProxy) }) - reconciler := &controller.WorkspaceProxyReconciler{Client: k8sClient, Scheme: scheme} + reconciler := &controller.CoderWorkspaceProxyReconciler{Client: k8sClient, Scheme: scheme} if _, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: types.NamespacedName{Name: workspaceProxy.Name, Namespace: workspaceProxy.Namespace}}); err != nil { t.Fatalf("reconcile workspace proxy: %v", err) } @@ -321,7 +321,7 @@ func TestWorkspaceProxyReconcile_TruncatesLongInstanceLabelValue(t *testing.T) { } } -func TestWorkspaceProxyReconcile_WithBootstrap_UsesExistingTokenWithoutCredentials(t *testing.T) { +func TestCoderWorkspaceProxyReconcile_WithBootstrap_UsesExistingTokenWithoutCredentials(t *testing.T) { ctx := context.Background() tokenSecretName := "proxy-bootstrap-existing-token" proxyTokenSecret := &corev1.Secret{ @@ -338,7 +338,7 @@ func TestWorkspaceProxyReconcile_WithBootstrap_UsesExistingTokenWithoutCredentia _ = k8sClient.Delete(ctx, proxyTokenSecret) }) - workspaceProxy := &coderv1alpha1.WorkspaceProxy{ + workspaceProxy := &coderv1alpha1.CoderWorkspaceProxy{ ObjectMeta: metav1.ObjectMeta{Name: "proxy-bootstrap-existing", Namespace: "default"}, Spec: coderv1alpha1.WorkspaceProxySpec{ Image: "proxy-image:latest", @@ -363,7 +363,7 @@ func TestWorkspaceProxyReconcile_WithBootstrap_UsesExistingTokenWithoutCredentia bootstrapClient := &fakeBootstrapClient{ response: coderbootstrap.RegisterWorkspaceProxyResponse{ProxyName: workspaceProxy.Name, ProxyToken: "generated-proxy-token"}, } - reconciler := &controller.WorkspaceProxyReconciler{Client: k8sClient, Scheme: scheme, BootstrapClient: bootstrapClient} + reconciler := &controller.CoderWorkspaceProxyReconciler{Client: k8sClient, Scheme: scheme, BootstrapClient: bootstrapClient} result, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: types.NamespacedName{Name: workspaceProxy.Name, Namespace: workspaceProxy.Namespace}}) if err != nil { @@ -376,7 +376,7 @@ func TestWorkspaceProxyReconcile_WithBootstrap_UsesExistingTokenWithoutCredentia t.Fatalf("expected bootstrap client to be skipped when token already exists, got %d calls", bootstrapClient.calls) } - reconciled := &coderv1alpha1.WorkspaceProxy{} + reconciled := &coderv1alpha1.CoderWorkspaceProxy{} if err := k8sClient.Get(ctx, types.NamespacedName{Name: workspaceProxy.Name, Namespace: workspaceProxy.Namespace}, reconciled); err != nil { t.Fatalf("get reconciled workspace proxy: %v", err) } @@ -388,7 +388,7 @@ func TestWorkspaceProxyReconcile_WithBootstrap_UsesExistingTokenWithoutCredentia } } -func TestWorkspaceProxyReconcile_WithBootstrap(t *testing.T) { +func TestCoderWorkspaceProxyReconcile_WithBootstrap(t *testing.T) { ctx := context.Background() credentialsSecret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{Name: "proxy-bootstrap-credentials", Namespace: "default"}, @@ -404,7 +404,7 @@ func TestWorkspaceProxyReconcile_WithBootstrap(t *testing.T) { _ = k8sClient.Delete(ctx, credentialsSecret) }) - workspaceProxy := &coderv1alpha1.WorkspaceProxy{ + workspaceProxy := &coderv1alpha1.CoderWorkspaceProxy{ ObjectMeta: metav1.ObjectMeta{Name: "proxy-bootstrap", Namespace: "default"}, Spec: coderv1alpha1.WorkspaceProxySpec{ Image: "proxy-image:latest", @@ -429,7 +429,7 @@ func TestWorkspaceProxyReconcile_WithBootstrap(t *testing.T) { bootstrapClient := &fakeBootstrapClient{ response: coderbootstrap.RegisterWorkspaceProxyResponse{ProxyName: workspaceProxy.Name, ProxyToken: "generated-proxy-token"}, } - reconciler := &controller.WorkspaceProxyReconciler{Client: k8sClient, Scheme: scheme, BootstrapClient: bootstrapClient} + reconciler := &controller.CoderWorkspaceProxyReconciler{Client: k8sClient, Scheme: scheme, BootstrapClient: bootstrapClient} result, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: types.NamespacedName{Name: workspaceProxy.Name, Namespace: workspaceProxy.Namespace}}) if err != nil { diff --git a/main_test.go b/main_test.go index d630a8b1..118869b3 100644 --- a/main_test.go +++ b/main_test.go @@ -40,8 +40,8 @@ func TestControllerSchemeRegistersCoderControlPlaneKinds(t *testing.T) { for _, gvk := range []schema.GroupVersionKind{ coderv1alpha1.GroupVersion.WithKind("CoderControlPlane"), coderv1alpha1.GroupVersion.WithKind("CoderControlPlaneList"), - coderv1alpha1.GroupVersion.WithKind("WorkspaceProxy"), - coderv1alpha1.GroupVersion.WithKind("WorkspaceProxyList"), + coderv1alpha1.GroupVersion.WithKind("CoderWorkspaceProxy"), + coderv1alpha1.GroupVersion.WithKind("CoderWorkspaceProxyList"), aggregationv1alpha1.SchemeGroupVersion.WithKind("CoderWorkspace"), aggregationv1alpha1.SchemeGroupVersion.WithKind("CoderWorkspaceList"), } { diff --git a/mkdocs.yml b/mkdocs.yml index b457ed5f..e8cdb5ba 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -45,7 +45,7 @@ nav: # BEGIN GENERATED API NAV - CoderControlPlane: reference/api/codercontrolplane.md - CoderProvisioner: reference/api/coderprovisioner.md - - WorkspaceProxy: reference/api/workspaceproxy.md + - CoderWorkspaceProxy: reference/api/coderworkspaceproxy.md - CoderTemplate: reference/api/codertemplate.md - CoderWorkspace: reference/api/coderworkspace.md # END GENERATED API NAV From 60023664bf7b63f725fea28d04ef4f6b3a579d9b Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Thu, 12 Feb 2026 11:15:02 +0000 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=A4=96=20fix:=20add=20CoderWorkspaceP?= =?UTF-8?q?roxy=20terms=20to=20cspell=20dictionary?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The docs-quality check failed on new CoderWorkspaceProxy resource names. Add the new singular/plural words to `.cspell.json` so docs CI passes. --- _Generated with [`mux`](https://github.com/coder/mux) • Model: `openai:gpt-5.3-codex` • Thinking: `xhigh` • Cost: `$0.62`_ --- .cspell.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.cspell.json b/.cspell.json index 98da0e2b..c9b07fc9 100644 --- a/.cspell.json +++ b/.cspell.json @@ -19,6 +19,8 @@ "codertemplates", "coderworkspace", "coderworkspaces", + "coderworkspaceproxy", + "coderworkspaceproxies", "coderprovisioner", "coderprovisioners", "controllerapp", From fe92f51c2a129183cc4a75e8dde571768f112ad1 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Thu, 12 Feb 2026 11:23:10 +0000 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=A4=96=20refactor:=20fold=20workspace?= =?UTF-8?q?=20proxy=20API=20types=20into=20coderworkspaceproxy=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the remaining shared workspace proxy constants/spec/status types from `workspaceproxy_types.go` into `coderworkspaceproxy_types.go` and delete the old file. --- _Generated with [`mux`](https://github.com/coder/mux) • Model: `openai:gpt-5.3-codex` • Thinking: `xhigh` • Cost: `$0.62`_ --- api/v1alpha1/coderworkspaceproxy_types.go | 69 +++++++++++++++++++++- api/v1alpha1/workspaceproxy_types.go | 70 ----------------------- 2 files changed, 68 insertions(+), 71 deletions(-) delete mode 100644 api/v1alpha1/workspaceproxy_types.go diff --git a/api/v1alpha1/coderworkspaceproxy_types.go b/api/v1alpha1/coderworkspaceproxy_types.go index 029713a5..f012bdf4 100644 --- a/api/v1alpha1/coderworkspaceproxy_types.go +++ b/api/v1alpha1/coderworkspaceproxy_types.go @@ -1,6 +1,73 @@ package v1alpha1 -import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + // WorkspaceProxyPhasePending indicates the proxy deployment is not ready. + WorkspaceProxyPhasePending = "Pending" + // WorkspaceProxyPhaseReady indicates at least one proxy pod is ready. + WorkspaceProxyPhaseReady = "Ready" +) + +// ProxyBootstrapSpec configures optional registration with the Coder API. +type ProxyBootstrapSpec struct { + // CoderURL is the URL for the primary Coder control plane API. + CoderURL string `json:"coderURL"` + // CredentialsSecretRef points to a Secret containing a Coder session token. + CredentialsSecretRef SecretKeySelector `json:"credentialsSecretRef"` + // ProxyName is the name used when registering the proxy in Coder. + ProxyName string `json:"proxyName,omitempty"` + // DisplayName is the human-readable name for the proxy region. + DisplayName string `json:"displayName,omitempty"` + // Icon is the optional icon URL or emoji path for the proxy region. + Icon string `json:"icon,omitempty"` + // GeneratedTokenSecretName stores the generated proxy token. + GeneratedTokenSecretName string `json:"generatedTokenSecretName,omitempty"` +} + +// WorkspaceProxySpec defines the desired state of a WorkspaceProxy. +type WorkspaceProxySpec struct { + // Image is the container image used for the workspace proxy pod. + Image string `json:"image,omitempty"` + // Replicas is the desired number of proxy pods. + Replicas *int32 `json:"replicas,omitempty"` + // Service controls the service created in front of the workspace proxy. + // +kubebuilder:default={} + Service ServiceSpec `json:"service,omitempty"` + // PrimaryAccessURL is the coderd URL the proxy should connect to. + PrimaryAccessURL string `json:"primaryAccessURL,omitempty"` + // ProxySessionTokenSecretRef points to a Secret key containing the proxy token. + ProxySessionTokenSecretRef *SecretKeySelector `json:"proxySessionTokenSecretRef,omitempty"` + // Bootstrap optionally registers the proxy and mints a proxy token. + Bootstrap *ProxyBootstrapSpec `json:"bootstrap,omitempty"` + // DerpOnly configures the workspace proxy to only serve DERP traffic. + DerpOnly bool `json:"derpOnly,omitempty"` + // ExtraArgs are appended to the default workspace proxy arguments. + ExtraArgs []string `json:"extraArgs,omitempty"` + // ExtraEnv are injected into the workspace proxy container. + ExtraEnv []corev1.EnvVar `json:"extraEnv,omitempty"` + // ImagePullSecrets are used by the pod to pull private images. + ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"` +} + +// WorkspaceProxyStatus defines the observed state of a WorkspaceProxy. +type WorkspaceProxyStatus struct { + // ObservedGeneration tracks the spec generation this status reflects. + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + // ReadyReplicas is the number of ready pods observed in the deployment. + ReadyReplicas int32 `json:"readyReplicas,omitempty"` + // Registered reports whether bootstrap registration completed successfully. + Registered bool `json:"registered,omitempty"` + // ProxyTokenSecretRef is the Secret used for the proxy session token. + ProxyTokenSecretRef *SecretKeySelector `json:"proxyTokenSecretRef,omitempty"` + // Phase is a high-level readiness indicator. + Phase string `json:"phase,omitempty"` + // Conditions are Kubernetes-standard conditions for this resource. + Conditions []metav1.Condition `json:"conditions,omitempty"` +} // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +kubebuilder:object:root=true diff --git a/api/v1alpha1/workspaceproxy_types.go b/api/v1alpha1/workspaceproxy_types.go deleted file mode 100644 index 7e7a4d53..00000000 --- a/api/v1alpha1/workspaceproxy_types.go +++ /dev/null @@ -1,70 +0,0 @@ -package v1alpha1 - -import ( - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -const ( - // WorkspaceProxyPhasePending indicates the proxy deployment is not ready. - WorkspaceProxyPhasePending = "Pending" - // WorkspaceProxyPhaseReady indicates at least one proxy pod is ready. - WorkspaceProxyPhaseReady = "Ready" -) - -// ProxyBootstrapSpec configures optional registration with the Coder API. -type ProxyBootstrapSpec struct { - // CoderURL is the URL for the primary Coder control plane API. - CoderURL string `json:"coderURL"` - // CredentialsSecretRef points to a Secret containing a Coder session token. - CredentialsSecretRef SecretKeySelector `json:"credentialsSecretRef"` - // ProxyName is the name used when registering the proxy in Coder. - ProxyName string `json:"proxyName,omitempty"` - // DisplayName is the human-readable name for the proxy region. - DisplayName string `json:"displayName,omitempty"` - // Icon is the optional icon URL or emoji path for the proxy region. - Icon string `json:"icon,omitempty"` - // GeneratedTokenSecretName stores the generated proxy token. - GeneratedTokenSecretName string `json:"generatedTokenSecretName,omitempty"` -} - -// WorkspaceProxySpec defines the desired state of a WorkspaceProxy. -type WorkspaceProxySpec struct { - // Image is the container image used for the workspace proxy pod. - Image string `json:"image,omitempty"` - // Replicas is the desired number of proxy pods. - Replicas *int32 `json:"replicas,omitempty"` - // Service controls the service created in front of the workspace proxy. - // +kubebuilder:default={} - Service ServiceSpec `json:"service,omitempty"` - // PrimaryAccessURL is the coderd URL the proxy should connect to. - PrimaryAccessURL string `json:"primaryAccessURL,omitempty"` - // ProxySessionTokenSecretRef points to a Secret key containing the proxy token. - ProxySessionTokenSecretRef *SecretKeySelector `json:"proxySessionTokenSecretRef,omitempty"` - // Bootstrap optionally registers the proxy and mints a proxy token. - Bootstrap *ProxyBootstrapSpec `json:"bootstrap,omitempty"` - // DerpOnly configures the workspace proxy to only serve DERP traffic. - DerpOnly bool `json:"derpOnly,omitempty"` - // ExtraArgs are appended to the default workspace proxy arguments. - ExtraArgs []string `json:"extraArgs,omitempty"` - // ExtraEnv are injected into the workspace proxy container. - ExtraEnv []corev1.EnvVar `json:"extraEnv,omitempty"` - // ImagePullSecrets are used by the pod to pull private images. - ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"` -} - -// WorkspaceProxyStatus defines the observed state of a WorkspaceProxy. -type WorkspaceProxyStatus struct { - // ObservedGeneration tracks the spec generation this status reflects. - ObservedGeneration int64 `json:"observedGeneration,omitempty"` - // ReadyReplicas is the number of ready pods observed in the deployment. - ReadyReplicas int32 `json:"readyReplicas,omitempty"` - // Registered reports whether bootstrap registration completed successfully. - Registered bool `json:"registered,omitempty"` - // ProxyTokenSecretRef is the Secret used for the proxy session token. - ProxyTokenSecretRef *SecretKeySelector `json:"proxyTokenSecretRef,omitempty"` - // Phase is a high-level readiness indicator. - Phase string `json:"phase,omitempty"` - // Conditions are Kubernetes-standard conditions for this resource. - Conditions []metav1.Condition `json:"conditions,omitempty"` -}