Skip to content

fix: identical endpoint name conflicts#1521

Open
akurinnoy wants to merge 14 commits into
devfile:mainfrom
akurinnoy:identical-endpoint-name
Open

fix: identical endpoint name conflicts#1521
akurinnoy wants to merge 14 commits into
devfile:mainfrom
akurinnoy:identical-endpoint-name

Conversation

@akurinnoy
Copy link
Copy Markdown
Collaborator

@akurinnoy akurinnoy commented Oct 8, 2025

What does this PR do?

This PR implements conflict detection and prevention at both the controller and webhook levels.

What issues does this PR fix or reference?

fixes eclipse-che/che#23231

Is it tested? How?

Same-namespace conflict detection

  1. Create test namespace
oc create namespace test-namespace
  1. Create first namespace and wait for it to be running.
cat << EOF | oc apply -f -
apiVersion: workspace.devfile.io/v1alpha2
kind: DevWorkspace
metadata:
  name: endpoint-test-1
  namespace: test-namespace
spec:
  started: true
  routingClass: basic
  template:
    components:
      - name: postgresql
        container:
          image: quay.io/fedora/postgresql-12:latest
          endpoints:
            - name: postgresql
              targetPort: 5432
              exposure: internal
              protocol: tcp
              attributes:
                discoverable: true
          env:
            - name: POSTGRESQL_USER
              value: user
            - name: POSTGRESQL_PASSWORD
              value: pass
            - name: POSTGRESQL_ROOT_PASSWORD
              value: root
            - name: POSTGRESQL_DATABASE
              value: db
EOF
oc wait --for=condition=Ready dw/endpoint-test-1 -n test-namespace --timeout=300s
  1. Try to create second workspace with same discoverable endpoint name. This operation should fail.
cat << EOF | oc apply -f -
apiVersion: workspace.devfile.io/v1alpha2
kind: DevWorkspace
metadata:
  name: endpoint-test-2
  namespace: test-namespace
spec:
  started: true
  routingClass: basic
  template:
    components:
      - name: postgresql
        container:
          image: quay.io/fedora/postgresql-12:latest
          endpoints:
            - name: postgresql
              targetPort: 5432
              exposure: internal
              protocol: tcp
              attributes:
                discoverable: true
          env:
            - name: POSTGRESQL_USER
              value: user
            - name: POSTGRESQL_PASSWORD
              value: pass
            - name: POSTGRESQL_ROOT_PASSWORD
              value: root
            - name: POSTGRESQL_DATABASE
              value: db
EOF

Expected result:

# "admission webhook "validate-devfile.devworkspace-controller.svc" denied the request: 
# discoverable endpoint 'postgresql' is already in use by workspace 'endpoint-test-1'"

Cross-name isolation

  1. Create second namespace
oc create namespace test-namespace-2
  1. Create workspace in second namespace with same endpoint name
cat << EOF | oc apply -f -
apiVersion: workspace.devfile.io/v1alpha2
kind: DevWorkspace
metadata:
  name: endpoint-test-1
  namespace: test-namespace-2
spec:
  started: true
  routingClass: basic
  template:
    components:
      - name: postgresql
        container:
          image: quay.io/fedora/postgresql-12:latest
          endpoints:
            - name: postgresql
              targetPort: 5432
              exposure: internal
              protocol: tcp
              attributes:
                discoverable: true
          env:
            - name: POSTGRESQL_USER
              value: user
            - name: POSTGRESQL_PASSWORD
              value: pass
            - name: POSTGRESQL_ROOT_PASSWORD
              value: root
            - name: POSTGRESQL_DATABASE
              value: db
EOF
  1. Verify both workspaces are running successfully
oc wait --for=condition=Ready dw/endpoint-test-1 -n test-namespace --timeout=300s
oc wait --for=condition=Ready dw/endpoint-test-1 -n test-namespace-2 --timeout=300s
  1. Verify both workspaces have services with the same name
oc get svc postgresql -n test-namespace
oc get svc postgresql -n test-namespace-2

PR Checklist

  • E2E tests pass (when PR is ready, comment /test v8-devworkspace-operator-e2e, v8-che-happy-path to trigger)
    • v8-devworkspace-operator-e2e: DevWorkspace e2e test
    • v8-che-happy-path: Happy path for verification integration with Che

Summary by CodeRabbit

Release Notes

  • New Features

    • Added validation to detect and prevent duplicate discoverable endpoints across workspaces in the same namespace.
  • Bug Fixes

    • Improved error handling and reporting for service conflicts with clear messaging about which endpoint and workspace are involved.
  • Tests

    • Added comprehensive test coverage for endpoint conflict detection and validation scenarios.

Review Change Stack

Signed-off-by: Oleksii Kurinnyi <okurinny@redhat.com>
@akurinnoy akurinnoy self-assigned this Oct 8, 2025
Signed-off-by: Oleksii Kurinnyi <okurinny@redhat.com>
Comment thread controllers/controller/devworkspacerouting/solvers/basic_solver.go Outdated
@akurinnoy akurinnoy marked this pull request as draft October 8, 2025 14:26
Signed-off-by: Oleksii Kurinnyi <okurinny@redhat.com>
Signed-off-by: Oleksii Kurinnyi <okurinny@redhat.com>
Comment thread controllers/controller/devworkspacerouting/solvers/common.go Outdated
Signed-off-by: Oleksii Kurinnyi <okurinny@redhat.com>
Signed-off-by: Oleksii Kurinnyi <okurinny@redhat.com>
Signed-off-by: Oleksii Kurinnyi <okurinny@redhat.com>
Signed-off-by: Oleksii Kurinnyi <okurinny@redhat.com>
Signed-off-by: Oleksii Kurinnyi <okurinny@redhat.com>
Signed-off-by: Oleksii Kurinnyi <okurinny@redhat.com>
@openshift-ci
Copy link
Copy Markdown

openshift-ci Bot commented Oct 9, 2025

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: akurinnoy, tolusha
Once this PR has been reviewed and has the lgtm label, please assign dkwon17 for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@akurinnoy akurinnoy marked this pull request as ready for review October 9, 2025 10:16
Signed-off-by: Oleksii Kurinnyi <okurinny@redhat.com>
@openshift-ci openshift-ci Bot removed the lgtm label Oct 9, 2025
@openshift-ci
Copy link
Copy Markdown

openshift-ci Bot commented Oct 9, 2025

New changes are detected. LGTM label has been removed.

Comment thread webhook/workspace/handler/validate_test.go Outdated
Comment thread webhook/workspace/handler/validate_test.go Outdated
Comment thread webhook/workspace/handler/validate_test.go Outdated
Comment thread controllers/controller/devworkspacerouting/solvers/errors.go
@rohanKanojia
Copy link
Copy Markdown
Member

I tested the PR with abovementioned steps and it seems to work as expected ✔️

Signed-off-by: Oleksii Kurinnyi <okurinny@redhat.com>
Signed-off-by: Oleksii Kurinnyi <okurinny@redhat.com>
@akurinnoy
Copy link
Copy Markdown
Collaborator Author

/retest

// GetDiscoverableServicesForEndpoints converts the endpoint list into a set of services, each corresponding to a single discoverable
// endpoint from the list. Endpoints with the NoneEndpointExposure are ignored.
func GetDiscoverableServicesForEndpoints(endpoints map[string]controllerv1alpha1.EndpointList, meta DevWorkspaceMetadata) []corev1.Service {
func GetDiscoverableServicesForEndpoints(endpoints map[string]controllerv1alpha1.EndpointList, meta DevWorkspaceMetadata, cl client.Client, log logr.Logger) ([]corev1.Service, error) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@akurinnoy is there a reason for implementing duplicate endpiont detection in both the webhook and the GetDiscoverableServicesForEndpoints function?

Copy link
Copy Markdown
Collaborator Author

@akurinnoy akurinnoy Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While the webhook prevents the DevWorkspaces (which will cause endpoint conflicts) from being created, the controller checks other cases:

  • two workspaces created simultaneously - both of them may pass the webhook validation
  • services can be created from other sources
  • old workspaces, created before this validation

Is this the answer to your question?

@rohanKanojia
Copy link
Copy Markdown
Member

@akurinnoy : I ran your claude skill /ok-pr-review and it revealed this finding. Do you think it's a valid use case?

The webhook validation has a logic error that causes it to reject valid workspace creations. Currently, if the current workspace has a discoverable endpoint named X, and another workspace has any endpoint (discoverable OR NOT) named X, we reject it.

Steps to reproduce

  • Create workspace A with a non-discoverable endpoint named "postgresql"
oc create -f - <<EOF
apiVersion: workspace.devfile.io/v1alpha2
kind: DevWorkspace
metadata:
  name: workspace-a
  namespace: test-namespace
spec:
  started: true
  routingClass: basic
  template:
    components:
      - name: postgresql
        container:
          image: quay.io/fedora/postgresql-12:latest
          endpoints:
            - name: postgresql
              targetPort: 5432
              exposure: internal
              protocol: tcp
EOF
  • Create workspace B with a discoverable endpoint named "postgresql" (gets rejected)
oc create -f - <<EOF
apiVersion: workspace.devfile.io/v1alpha2
kind: DevWorkspace
metadata:
  name: workspace-b
  namespace: test-namespace
spec:
  started: true
  routingClass: basic
  template:
    components:
      - name: postgresql
        container:
          image: quay.io/fedora/postgresql-12:latest
          endpoints:
            - name: postgresql
              targetPort: 5432
              exposure: internal
              protocol: tcp
              attributes:
                discoverable: true
EOF

Do you think Workspace B should be allowed because Workspace A's endpoint is NOT discoverable and doesn't create a service named postgresql?

@tolusha
Copy link
Copy Markdown
Contributor

tolusha commented May 8, 2026

Hi! I'm che-ai-assistant — I help with your pull requests.

I check for new commands every 5m0s (if I am not busy :) ).

Available commands:

  • /che-ai-assistant generate-che-doc — Generate a documentation PR based on this PR's changes
  • /che-ai-assistant ok-pr-review — Run a comprehensive PR review (summary, code review, deep review, impact analysis)
  • /che-ai-assistant help — Show this help message

Check discoverability of other workspace's endpoint before
reporting a conflict. Previously, a non-discoverable endpoint
with the same name would incorrectly trigger a rejection.

Assisted-by: Claude Code (claude-opus-4-6)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Oleksii Kurinnyi <okurinny@redhat.com>
@akurinnoy akurinnoy requested a review from btjd as a code owner May 14, 2026 15:08
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 14, 2026

📝 Walkthrough

Walkthrough

This PR implements conflict detection for DevWorkspace discoverable endpoints to prevent multiple workspaces from claiming the same service name. It refactors solvers to perform in-cluster ownership lookups, adds a new ServiceConflictError type, integrates conflict handling into the routing controller and admission webhook, and includes comprehensive tests.

Changes

Discoverable Endpoint Conflict Detection

Layer / File(s) Summary
Error types and solver interface contracts
controllers/controller/devworkspacerouting/solvers/errors.go, controllers/controller/devworkspacerouting/solvers/solver.go
ServiceConflictError type introduced to report endpoint name conflicts with EndpointName and WorkspaceName fields. RoutingSolverGetter.GetSolver interface extended to accept a logr.Logger parameter for dependency injection.
Solver dependency injection: client and logger
controllers/controller/devworkspacerouting/solvers/basic_solver.go, controllers/controller/devworkspacerouting/solvers/cluster_solver.go
BasicSolver and ClusterSolver refactored from dependency-less structs to store a Kubernetes client.Client and logr.Logger. New constructor functions NewBasicSolver and NewClusterSolver introduced. SolverGetter.GetSolver updated to instantiate solvers via constructors, passing injected dependencies.
Discoverable endpoint conflict detection in solvers
controllers/controller/devworkspacerouting/solvers/common.go
DevWorkspaceMetadata extended with DevWorkspaceName field. GetDiscoverableServicesForEndpoints signature updated to accept client.Client and logr.Logger, return ([]corev1.Service, error), and perform in-cluster ownership lookups: computes deterministic service names, queries existing services by name/namespace, detects conflicts by comparing DevWorkspaceIDLabel, and returns ServiceConflictError when service is owned by a different workspace. Both BasicSolver and ClusterSolver invoke this with their dependencies.
Controller reconciliation: conflict handling and error reporting
controllers/controller/devworkspacerouting/devworkspacerouting_controller.go
Routing controller passes reqLogger to SolverGetter.GetSolver. Adds DevWorkspaceName from instance.Name to workspace metadata. Adds explicit handling for *solvers.ServiceConflictError during spec object provisioning: logs conflict details and marks DevWorkspaceRouting status as RoutingFailed.
Webhook admission validation for endpoint conflicts
webhook/workspace/handler/validate.go
ValidateDevfile extended to invoke new validateEndpoints helper. validateEndpoints scans incoming DevWorkspace's component endpoints marked discoverable: true, lists existing DevWorkspaces in the same namespace, detects endpoint name conflicts across workspaces (excluding same UID and deletion-timestamped workspaces), and returns solvers.ServiceConflictError on first conflict; aggregates error message into admission denial.
Tests, fixtures, and supporting configuration
controllers/controller/devworkspacerouting/solvers/common_test.go, webhook/workspace/handler/validate_test.go, webhook/workspace/handler/testdata/test-devworkspace.yaml, pkg/webhook/cluster_roles.go, controllers/workspace/suite_test.go
Table-driven test for GetDiscoverableServicesForEndpoints validates conflict detection, service name computation, and namespace isolation. Webhook test validates conflict scenarios with multiple workspace fixtures and fake Kubernetes client. Test DevWorkspace YAML fixture defines discoverable endpoint. RBAC updated to allow webhook get, list, watch on devworkspaces. Metrics server disabled in test suite to prevent port conflicts.

Sequence Diagram(s)

sequenceDiagram
  participant Webhook as Admission Webhook
  participant Controller as Routing Controller
  participant Solver as DevWorkspace Solver
  participant Client as Kubernetes API
  
  Webhook->>Webhook: validateEndpoints: collect discoverable endpoint names
  Webhook->>Client: list DevWorkspaces in namespace
  Client-->>Webhook: return workspace list
  Webhook->>Webhook: compare endpoint names across workspaces
  alt Conflict detected
    Webhook-->>Webhook: return ServiceConflictError
  else No conflict
    Webhook-->>Webhook: allow admission
  end
  
  Controller->>Solver: GetSolver with logger
  Solver->>Solver: NewBasicSolver/NewClusterSolver with client+logger
  Controller->>Solver: GetSpecObjects
  Solver->>Solver: GetDiscoverableServicesForEndpoints
  Solver->>Client: Get existing Service by name
  Client-->>Solver: Service or not-found
  alt Service exists and belongs to different workspace
    Solver-->>Solver: return ServiceConflictError
  else Service ok or not found
    Solver-->>Solver: create/update Service with ownership labels
  end
  alt Error returned
    Controller->>Controller: markRoutingFailed with conflict message
  else Success
    Controller->>Controller: continue reconciliation
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

The PR spans multiple layers (solvers, controller, webhook) with moderate logic density. The core conflict detection logic is straightforward (metadata lookup and label comparison), but the changes are heterogeneous across different subsystems. Tests are comprehensive and provide good coverage of edge cases.

Suggested labels

ok-to-test, lgtm

Suggested reviewers

  • ibuziuk

Poem

🐰 Two workspaces, one endpoint name?
No more infinite loops in this game!
Conflict detection, upstream and in,
Labels and lookups—let harmony win.
Service ownership, finally clear,
Peace in the cluster, hooray, hooray, hooray! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: identical endpoint name conflicts' clearly and concisely describes the main change—addressing conflicts when workspaces share endpoint names—which is the primary objective of the PR.
Linked Issues check ✅ Passed The PR implements conflict detection and prevention for identically named discoverable endpoints at both controller and webhook levels, directly addressing the #23231 issue where identical endpoint names caused controller loops and stuck workspace states.
Out of Scope Changes check ✅ Passed All changes are scoped to conflict detection and prevention: solver dependency injection, service conflict error handling, endpoint validation logic, RBAC permissions, metrics server configuration, and comprehensive test coverage. No unrelated changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@controllers/controller/devworkspacerouting/devworkspacerouting_controller.go`:
- Around line 128-133: The code sets workspaceMeta.DevWorkspaceName =
instance.Name but must use the parent DevWorkspace's name from
instance.OwnerReferences; update the DevWorkspaceMetadata population so
DevWorkspaceName is assigned from the owner reference whose Kind (or
APIVersion+Kind) indicates the parent DevWorkspace (e.g., iterate
instance.OwnerReferences, find ownerRef.Kind == "DevWorkspace" and use
ownerRef.Name), and fall back to instance.Name only if no such ownerRef exists;
change the assignment near workspaceMeta (DevWorkspaceMetadata,
workspaceMeta.DevWorkspaceName) to use that ownerRef-derived name.

In `@controllers/controller/devworkspacerouting/solvers/common_test.go`:
- Around line 16-27: Reorder the import block in common_test.go into three
groups with blank lines between them: (1) standard library (e.g., "testing"),
(2) third-party and Kubernetes packages (e.g.,
"github.com/stretchr/testify/assert", "k8s.io/api/core/v1" as corev1,
"k8s.io/apimachinery/pkg/apis/meta/v1" as metav1,
"k8s.io/apimachinery/pkg/runtime",
"sigs.k8s.io/controller-runtime/pkg/client/fake",
"sigs.k8s.io/controller-runtime/pkg/log/zap"), and (3) project-local imports
(e.g., "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1" as
controllerv1alpha1 and
"github.com/devfile/devworkspace-operator/pkg/constants"); insert single blank
lines between these groups and then run 'make fmt' to apply formatting.

In `@webhook/workspace/handler/validate_test.go`:
- Around line 105-124: The test currently asserts a ServiceConflictError when
validateEndpoints (on WebhookHandler) is called with a workspace and a
deletingWorkspace (created via setupWorkspace) that has DeletionTimestamp set;
update the test so it reflects webhook behavior that ignores
deletion-timestamped workspaces: call handler.validateEndpoints(context.TODO(),
workspace) and assert no error (i.e., expect nil) instead of asserting
Error/ErrorAs for *solvers.ServiceConflictError, and remove or replace the
subsequent checks for conflictErr.EndpointName/WorkspaceName accordingly so the
test verifies that deletion-timestamped workspaces do not cause conflicts.
- Around line 16-31: Reorder the import block in the validate_test.go file into
three blank-line-separated groups: (1) standard library imports (context, os,
path/filepath, testing), (2) third-party and Kubernetes imports
(github.com/stretchr/testify/assert, sigs.k8s.io/... , k8s.io/... etc.), and (3)
project-local imports (github.com/devfile/... and your controller packages),
making sure each package stays in its respective group; after rearranging, run
make fmt to apply formatting.

In `@webhook/workspace/handler/validate.go`:
- Around line 81-84: validateEndpoints currently returns raw errors from
h.Client.List which are being appended to devfileErrors and treated as user
validation failures in ValidateDevfile; change validateEndpoints signature to
return (*solvers.ServiceConflictError, error) so real validation conflicts come
back as the typed value and infrastructure/API errors flow via the error return,
update validateEndpoints to return (nil, err) for h.Client.List failures and
(conflictErr, nil) for validation failures, then in ValidateDevfile (and the
other call site around lines 109-112) check the error return first and convert
infrastructure errors into admission.Errored(http.StatusBadRequest, err) (or
propagate the error) instead of appending to devfileErrors, only append
conflictErr.Error() to devfileErrors when the typed conflict value is returned.
- Around line 114-130: The validation loop currently only skips the same
workspace by UID but still inspects workspaces that are being deleted; modify
the loop that iterates workspaceList.Items (and the check around otherWorkspace)
to also skip any otherWorkspace with a non-nil DeletionTimestamp (i.e., treat
terminating workspaces as excluded from discoverable endpoint conflict checks),
so that discoverableEndpoints lookup and potential return of
solvers.ServiceConflictError only considers non-terminating workspaces.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 981c002a-3f92-488e-8077-9057055e9194

📥 Commits

Reviewing files that changed from the base of the PR and between e0ed8d2 and 7a5ff10.

📒 Files selected for processing (12)
  • controllers/controller/devworkspacerouting/devworkspacerouting_controller.go
  • controllers/controller/devworkspacerouting/solvers/basic_solver.go
  • controllers/controller/devworkspacerouting/solvers/cluster_solver.go
  • controllers/controller/devworkspacerouting/solvers/common.go
  • controllers/controller/devworkspacerouting/solvers/common_test.go
  • controllers/controller/devworkspacerouting/solvers/errors.go
  • controllers/controller/devworkspacerouting/solvers/solver.go
  • controllers/workspace/suite_test.go
  • pkg/webhook/cluster_roles.go
  • webhook/workspace/handler/testdata/test-devworkspace.yaml
  • webhook/workspace/handler/validate.go
  • webhook/workspace/handler/validate_test.go

Comment on lines 128 to 133
workspaceMeta := solvers.DevWorkspaceMetadata{
DevWorkspaceId: instance.Spec.DevWorkspaceId,
Namespace: instance.Namespace,
PodSelector: instance.Spec.PodSelector,
DevWorkspaceId: instance.Spec.DevWorkspaceId,
DevWorkspaceName: instance.Name,
Namespace: instance.Namespace,
PodSelector: instance.Spec.PodSelector,
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Confirm how DevWorkspaceRouting objects are named relative to the parent DevWorkspace.
# Look for routing creation sites in the workspace controller / provision packages.
rg -nP --type=go -C5 '\bDevWorkspaceRouting\s*\{' -g '!**/vendor/**' -g '!**/zz_generated*'
rg -nP --type=go -C3 '\bObjectMeta:\s*metav1\.ObjectMeta\s*\{' -g '*routing*'

Repository: devfile/devworkspace-operator

Length of output: 26087


🏁 Script executed:

#!/bin/bash
# Find the common.DevWorkspaceRoutingName function definition
rg -nP --type=go "func.*DevWorkspaceRoutingName" -g '!**/vendor/**'

# Show the context around where workspaceMeta is created in the controller
sed -n '120,140p' controllers/controller/devworkspacerouting/devworkspacerouting_controller.go

# Check if there's a reference to parent DevWorkspace in the routing controller
rg -nP --type=go "DevWorkspace\s*\{" controllers/controller/devworkspacerouting/devworkspacerouting_controller.go -C 3

Repository: devfile/devworkspace-operator

Length of output: 879


🏁 Script executed:

#!/bin/bash
# Get the full implementation of DevWorkspaceRoutingName
cat -n pkg/common/naming.go | head -40

# Check if DevWorkspaceRouting object has parent reference or ownerRef pointing to DevWorkspace
rg -nP --type=go "OwnerReferences|ownerRef" controllers/controller/devworkspacerouting/devworkspacerouting_controller.go

# Search for webhook code that validates workspace names
fd webhook -t f --type go | head -5
rg -nP --type=go "otherWorkspace|DevWorkspaceName" --type=go -A 3 -B 3 | head -50

Repository: devfile/devworkspace-operator

Length of output: 6551


🏁 Script executed:

#!/bin/bash
# Find webhook validation code that uses DevWorkspaceName
rg -nP --type=go "DevWorkspaceName|validateEndpoints" webhook/ --type go -A 5 -B 2 | head -100

# Check how DevWorkspaceRouting relates to parent DevWorkspace (ownerReferences)
rg -nP --type=go "OwnerReferences" controllers/workspace/ -A 3 -B 1

# Look at the full reconcile method context around line 128-133
sed -n '100,150p' controllers/controller/devworkspacerouting/devworkspacerouting_controller.go

Repository: devfile/devworkspace-operator

Length of output: 13643


🏁 Script executed:

#!/bin/bash
# Find where ServiceConflictError is created and how it uses DevWorkspaceName
rg -nP --type=go "ServiceConflictError" pkg/ --type go -B 5 -A 5

# Check how solvers use DevWorkspaceMetadata.DevWorkspaceName
rg -nP --type=go "DevWorkspaceName" pkg/ --type go -C 3

# Find what information is available in the routing controller about the parent workspace
rg -nP --type=go "getDevWorkspace\|FindOwner" controllers/controller/devworkspacerouting/ --type go -C 3

Repository: devfile/devworkspace-operator

Length of output: 6411


🏁 Script executed:

#!/bin/bash
# Find ServiceConflictError in solvers package
rg -nP --type=go "type ServiceConflictError\|ServiceConflictError\s*struct" pkg/solvers/ -A 10

# Check solvers package for DevWorkspaceMetadata usage
rg -nP --type=go "DevWorkspaceMetadata" pkg/solvers/ -B 2 -A 8 | head -100

# Look for conflict reporting with workspace names
rg -nP --type=go "OtherWorkspaceName\|ConflictingWorkspaceName\|conflict.*workspace" pkg/ --type go -C 3

Repository: devfile/devworkspace-operator

Length of output: 179


🏁 Script executed:

#!/bin/bash
# Find where solvers package is located
find . -name "*.go" -type f | xargs grep -l "type ServiceConflictError" | head -5

# Find solvers imports
rg -nP --type=go "solvers\." controllers/controller/devworkspacerouting/ --type go | head -10

# Search for ServiceConflictError definition
rg -nP --type=go "ServiceConflictError" --type go -B 3 -A 10 | head -100

Repository: devfile/devworkspace-operator

Length of output: 11248


🏁 Script executed:

#!/bin/bash
# Check where DevWorkspaceName is used in solvers
rg -nP --type=go "DevWorkspaceName\|OtherWorkspaceName" controllers/controller/devworkspacerouting/solvers/ --type go -B 3 -A 3

# Check if solvers checks for conflicts by comparing workspace metadata names
rg -nP --type=go "metadata\.DevWorkspaceName\|workspaceMeta\.DevWorkspaceName" controllers/controller/devworkspacerouting/solvers/ --type go -B 5 -A 5

# Look at the full errors.go to understand ServiceConflictError
cat -n controllers/controller/devworkspacerouting/solvers/errors.go

Repository: devfile/devworkspace-operator

Length of output: 2871


🏁 Script executed:

#!/bin/bash
# Find where ServiceConflictError is instantiated in solvers
rg -nP --type=go "ServiceConflictError\s*\{" controllers/controller/devworkspacerouting/solvers/ --type go -B 5 -A 3

# Check the common_test.go file more fully to understand the concern
sed -n '70,100p' controllers/controller/devworkspacerouting/solvers/common_test.go

Repository: devfile/devworkspace-operator

Length of output: 4589


🏁 Script executed:

#!/bin/bash
# Find all usages of DevWorkspaceName in the solvers code
rg -nP --type=go "DevWorkspaceName\|meta\.DevWorkspaceName" controllers/controller/devworkspacerouting/solvers/ --type go -C 3

# Check if DevWorkspaceName is compared with any service labels or workspace names
rg -nP --type=go "\.DevWorkspaceName\s*==" controllers/controller/devworkspacerouting/ --type go -C 5

Repository: devfile/devworkspace-operator

Length of output: 55


🏁 Script executed:

#!/bin/bash
# Let's trace how DevWorkspaceName is actually used by checking all references to it
rg -nP --type=go "DevWorkspaceName" controllers/controller/devworkspacerouting/solvers/ --type go

# Check what parameters GetSpecObjects and GetDiscoverableServicesForEndpoints use
rg -nP --type=go "GetSpecObjects\|GetDiscoverableServicesForEndpoints" controllers/controller/devworkspacerouting/solvers/ --type go -B 2 -A 8 | head -60

# Check the full common.go file around the conflict check
sed -n '50,75p' controllers/controller/devworkspacerouting/solvers/common.go

Repository: devfile/devworkspace-operator

Length of output: 2144


🏁 Script executed:

#!/bin/bash
# Check if DevWorkspaceRouting has ownerReferences pointing to parent DevWorkspace
sed -n '75,200p' controllers/controller/devworkspacerouting/devworkspacerouting_controller.go | head -80

# Check if we can get parent workspace name from ownerReferences
rg -nP --type=go "GetOwnerReferences\|OwnerReferences" controllers/controller/devworkspacerouting/devworkspacerouting_controller.go --type go -B 2 -A 5

# Check how workspace controller creates the routing with ownerReferences
sed -n '100,120p' pkg/provision/workspace/routing.go

Repository: devfile/devworkspace-operator

Length of output: 4073


🏁 Script executed:

#!/bin/bash
# Verify: Check if DevWorkspaceRouting object has ownerReferences that we can use to look up parent workspace
sed -n '105,130p' pkg/provision/workspace/routing.go

# Check if there's any code that looks up the parent DevWorkspace from the routing object
rg -nP --type=go "ownerRef.*DevWorkspace\|GetOwner" controllers/controller/devworkspacerouting/ --type go -C 3

# Verify test data - what are the expected workspace names in tests
sed -n '40,60p' controllers/controller/devworkspacerouting/solvers/common_test.go

Repository: devfile/devworkspace-operator

Length of output: 1446


🏁 Script executed:

#!/bin/bash
# Check if there's a way to get parent workspace name from ownerReferences in the routing controller
rg -nP --type=go "for.*ownerRef\|OwnerReferences\|GetOwnerReferences" controllers/controller/devworkspacerouting/devworkspacerouting_controller.go --type go -B 2 -A 8

# Verify: Can we access parent workspace from routing object?
sed -n '1,80p' controllers/controller/devworkspacerouting/devworkspacerouting_controller.go | tail -30

Repository: devfile/devworkspace-operator

Length of output: 1583


🏁 Script executed:

#!/bin/bash
# Final verification: check if the parent workspace name is accessible via ownerReferences
# and confirm the exact issue with how names diverge

# Show where SetControllerReference is used
rg -nP --type=go "SetControllerReference" pkg/provision/workspace/routing.go --type go -B 3 -A 3

# Check what happens after ownerReferences are set
sed -n '120,135p' pkg/provision/workspace/routing.go

# Verify the actual behavior: Get parent workspace name from ownerReference
rg -nP --type=go "ownerReferences\|OwnerReferences" apis/controller/v1alpha1/devworkspacerouting_types.go --type go -C 5

Repository: devfile/devworkspace-operator

Length of output: 604


🏁 Script executed:

#!/bin/bash
# Final confirmation: Check if we can get parent workspace name from ownerReferences in the routing reconciler
# and verify the actual issue scenario

# Get the full reconcile method to see all available information
sed -n '72,180p' controllers/controller/devworkspacerouting/devworkspacerouting_controller.go | grep -A 5 "ownerRef\|OwnerReferences" || echo "No ownerReferences used in reconciler"

# Check test to confirm expected behavior
rg -nP --type=go "DevWorkspaceName.*routing-\|routing-.*DevWorkspaceName" controllers/controller/devworkspacerouting/ --type go

# Verify the actual naming mismatch by checking what parent workspace name should be
sed -n '40,70p' controllers/workspace/devworkspace_controller_test.go

Repository: devfile/devworkspace-operator

Length of output: 1038


🏁 Script executed:

#!/bin/bash
# Let me verify the actual problem: check if parent workspace name is accessible via ownerReferences
# and whether this causes a real issue

# Get the type definition for DevWorkspaceRouting to see ownerReferences field
sed -n '1,100p' apis/controller/v1alpha1/devworkspacerouting_types.go

# Check if there's documentation on how DevWorkspaceName should be set
rg -nP --type=go "DevWorkspaceMetadata" apis/controller/v1alpha1/ --type go -B 5 -A 10

Repository: devfile/devworkspace-operator

Length of output: 3967


Use the parent DevWorkspace.Name instead of instance.Name for DevWorkspaceName.

DevWorkspaceRouting objects are created with names like "routing-{workspaceId}" (based on the workspace ID), but the parent DevWorkspace can have any arbitrary name. The code currently sets workspaceMeta.DevWorkspaceName = instance.Name, which passes the routing object's name to solvers for service labeling. This causes a mismatch: services get labeled with the routing name (e.g., "routing-abc123"), but the webhook's conflict reporting uses the actual DevWorkspace.Name (e.g., "my-workspace"). When users see conflict errors, the workspace name attributed to the service owner will not match the actual workspace name, making error messages confusing.

Since DevWorkspaceRouting has an ownerReferences to its parent DevWorkspace, the parent workspace name should be retrieved from there and used for DevWorkspaceName instead of instance.Name.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controllers/controller/devworkspacerouting/devworkspacerouting_controller.go`
around lines 128 - 133, The code sets workspaceMeta.DevWorkspaceName =
instance.Name but must use the parent DevWorkspace's name from
instance.OwnerReferences; update the DevWorkspaceMetadata population so
DevWorkspaceName is assigned from the owner reference whose Kind (or
APIVersion+Kind) indicates the parent DevWorkspace (e.g., iterate
instance.OwnerReferences, find ownerRef.Kind == "DevWorkspace" and use
ownerRef.Name), and fall back to instance.Name only if no such ownerRef exists;
change the assignment near workspaceMeta (DevWorkspaceMetadata,
workspaceMeta.DevWorkspaceName) to use that ownerRef-derived name.

Comment on lines +16 to +27
import (
"testing"

controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
"github.com/devfile/devworkspace-operator/pkg/constants"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Organize imports into three groups per coding guidelines.

The imports should be organized into three groups separated by blank lines: (1) standard library, (2) third-party + Kubernetes, (3) project-local. As per coding guidelines, run 'make fmt' to enforce automatically.

♻️ Proposed fix
 import (
 	"testing"
 
+	"github.com/stretchr/testify/assert"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
+	"sigs.k8s.io/controller-runtime/pkg/log/zap"
+
 	controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
 	"github.com/devfile/devworkspace-operator/pkg/constants"
-	"github.com/stretchr/testify/assert"
-	corev1 "k8s.io/api/core/v1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/runtime"
-	"sigs.k8s.io/controller-runtime/pkg/client/fake"
-	"sigs.k8s.io/controller-runtime/pkg/log/zap"
 )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import (
"testing"
controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
"github.com/devfile/devworkspace-operator/pkg/constants"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
)
import (
"testing"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
"github.com/devfile/devworkspace-operator/pkg/constants"
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controllers/controller/devworkspacerouting/solvers/common_test.go` around
lines 16 - 27, Reorder the import block in common_test.go into three groups with
blank lines between them: (1) standard library (e.g., "testing"), (2)
third-party and Kubernetes packages (e.g., "github.com/stretchr/testify/assert",
"k8s.io/api/core/v1" as corev1, "k8s.io/apimachinery/pkg/apis/meta/v1" as
metav1, "k8s.io/apimachinery/pkg/runtime",
"sigs.k8s.io/controller-runtime/pkg/client/fake",
"sigs.k8s.io/controller-runtime/pkg/log/zap"), and (3) project-local imports
(e.g., "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1" as
controllerv1alpha1 and
"github.com/devfile/devworkspace-operator/pkg/constants"); insert single blank
lines between these groups and then run 'make fmt' to apply formatting.

Comment on lines +16 to +31
import (
"context"
"os"
"path/filepath"
"testing"

dwv2 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
"github.com/devfile/devworkspace-operator/controllers/controller/devworkspacerouting/solvers"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/yaml"
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Organize imports into three groups per coding guidelines.

The imports should be organized into three groups separated by blank lines: (1) standard library, (2) third-party + Kubernetes, (3) project-local. As per coding guidelines, run 'make fmt' to enforce automatically.

♻️ Proposed fix
 import (
 	"context"
 	"os"
 	"path/filepath"
 	"testing"
 
-	dwv2 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
-	"github.com/devfile/devworkspace-operator/controllers/controller/devworkspacerouting/solvers"
 	"github.com/stretchr/testify/assert"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/types"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 	"sigs.k8s.io/yaml"
+
+	dwv2 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
+	"github.com/devfile/devworkspace-operator/controllers/controller/devworkspacerouting/solvers"
 )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webhook/workspace/handler/validate_test.go` around lines 16 - 31, Reorder the
import block in the validate_test.go file into three blank-line-separated
groups: (1) standard library imports (context, os, path/filepath, testing), (2)
third-party and Kubernetes imports (github.com/stretchr/testify/assert,
sigs.k8s.io/... , k8s.io/... etc.), and (3) project-local imports
(github.com/devfile/... and your controller packages), making sure each package
stays in its respective group; after rearranging, run make fmt to apply
formatting.

Comment on lines +105 to +124
t.Run("Conflict detected even when workspace is being deleted", func(t *testing.T) {
workspace := setupWorkspace(t, "workspace-1", "uid-1", "test-namespace")

// Workspace being deleted with same endpoint name
deletingWorkspace := setupWorkspace(t, "workspace-deleting", "uid-deleting", "test-namespace")
now := metav1.Now()
deletingWorkspace.DeletionTimestamp = &now
// Add finalizer - required by fake client when setting deletionTimestamp
deletingWorkspace.Finalizers = []string{"test-finalizer"}

fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(deletingWorkspace).Build()
handler := &WebhookHandler{Client: fakeClient}
err := handler.validateEndpoints(context.TODO(), workspace)
assert.Error(t, err, "Should detect conflict even with workspace being deleted")

var conflictErr *solvers.ServiceConflictError
assert.ErrorAs(t, err, &conflictErr, "Error should be a ServiceConflictError")
assert.Equal(t, "test-endpoint", conflictErr.EndpointName, "Conflict should be on 'test-endpoint'")
assert.Equal(t, "workspace-deleting", conflictErr.WorkspaceName, "Conflict should reference 'workspace-deleting'")
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Test expectation contradicts the expected webhook behavior.

According to the review stack context, the webhook validation should exclude deletion-timestamped workspaces from conflict detection (layer 5: "excluding same UID and deletion-timestamped workspaces"). However, this test expects a ServiceConflictError when validating against a workspace with a DeletionTimestamp.

The test should expect no error because workspaces being deleted should not block new workspace creation.

🔧 Proposed fix
-	t.Run("Conflict detected even when workspace is being deleted", func(t *testing.T) {
+	t.Run("No conflict when other workspace is being deleted", func(t *testing.T) {
 		workspace := setupWorkspace(t, "workspace-1", "uid-1", "test-namespace")
 
 		// Workspace being deleted with same endpoint name
 		deletingWorkspace := setupWorkspace(t, "workspace-deleting", "uid-deleting", "test-namespace")
 		now := metav1.Now()
 		deletingWorkspace.DeletionTimestamp = &now
 		// Add finalizer - required by fake client when setting deletionTimestamp
 		deletingWorkspace.Finalizers = []string{"test-finalizer"}
 
 		fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(deletingWorkspace).Build()
 		handler := &WebhookHandler{Client: fakeClient}
 		err := handler.validateEndpoints(context.TODO(), workspace)
-		assert.Error(t, err, "Should detect conflict even with workspace being deleted")
-
-		var conflictErr *solvers.ServiceConflictError
-		assert.ErrorAs(t, err, &conflictErr, "Error should be a ServiceConflictError")
-		assert.Equal(t, "test-endpoint", conflictErr.EndpointName, "Conflict should be on 'test-endpoint'")
-		assert.Equal(t, "workspace-deleting", conflictErr.WorkspaceName, "Conflict should reference 'workspace-deleting'")
+		assert.NoError(t, err, "Should not conflict with workspace being deleted")
 	})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webhook/workspace/handler/validate_test.go` around lines 105 - 124, The test
currently asserts a ServiceConflictError when validateEndpoints (on
WebhookHandler) is called with a workspace and a deletingWorkspace (created via
setupWorkspace) that has DeletionTimestamp set; update the test so it reflects
webhook behavior that ignores deletion-timestamped workspaces: call
handler.validateEndpoints(context.TODO(), workspace) and assert no error (i.e.,
expect nil) instead of asserting Error/ErrorAs for
*solvers.ServiceConflictError, and remove or replace the subsequent checks for
conflictErr.EndpointName/WorkspaceName accordingly so the test verifies that
deletion-timestamped workspaces do not cause conflicts.

Comment on lines +81 to +84
endpointErrors := h.validateEndpoints(ctx, wksp)
if endpointErrors != nil {
devfileErrors = append(devfileErrors, endpointErrors.Error())
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't report API/list failures as devfile validation errors.

When h.Client.List fails (API server hiccup, RBAC misconfiguration, context cancellation, etc.), validateEndpoints returns that raw error. In ValidateDevfile it is then appended to devfileErrors and surfaced to the user via admission.Denied(...) as if the user's devfile were invalid. Per the webhook handling guidelines, infrastructure errors should be returned via admission.Errored(...) so the API server can retry/surface them appropriately, and the user is not shown a misleading validation message.

As per coding guidelines: "For validating webhooks, return 'admission.Denied()' when rejecting requests, 'admission.Allowed()' when accepting, and 'admission.Errored(http.StatusBadRequest, err)' for decode errors"

🛠️ Suggested approach

One option is to distinguish list errors from validation errors at the call site:

-	endpointErrors := h.validateEndpoints(ctx, wksp)
-	if endpointErrors != nil {
-		devfileErrors = append(devfileErrors, endpointErrors.Error())
-	}
+	conflict, err := h.validateEndpoints(ctx, wksp)
+	if err != nil {
+		return admission.Errored(http.StatusInternalServerError, err)
+	}
+	if conflict != nil {
+		devfileErrors = append(devfileErrors, conflict.Error())
+	}

And have validateEndpoints return (*solvers.ServiceConflictError, error) so a List failure flows through err while a real conflict flows through the typed value.

Also applies to: 109-112

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webhook/workspace/handler/validate.go` around lines 81 - 84,
validateEndpoints currently returns raw errors from h.Client.List which are
being appended to devfileErrors and treated as user validation failures in
ValidateDevfile; change validateEndpoints signature to return
(*solvers.ServiceConflictError, error) so real validation conflicts come back as
the typed value and infrastructure/API errors flow via the error return, update
validateEndpoints to return (nil, err) for h.Client.List failures and
(conflictErr, nil) for validation failures, then in ValidateDevfile (and the
other call site around lines 109-112) check the error return first and convert
infrastructure errors into admission.Errored(http.StatusBadRequest, err) (or
propagate the error) instead of appending to devfileErrors, only append
conflictErr.Error() to devfileErrors when the typed conflict value is returned.

Comment on lines +114 to +130
for _, otherWorkspace := range workspaceList.Items {
if otherWorkspace.UID == workspace.UID {
continue
}
for _, component := range otherWorkspace.Spec.Template.Components {
if component.Container != nil {
for _, endpoint := range component.Container.Endpoints {
if discoverableEndpoints[endpoint.Name] && endpoint.Attributes.GetBoolean(string(v1alpha1.DiscoverableAttribute), nil) {
return &solvers.ServiceConflictError{
EndpointName: endpoint.Name,
WorkspaceName: otherWorkspace.Name,
}
}
}
}
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Skip workspaces that are being deleted.

Only UID equality is checked when filtering out the requesting workspace. A DevWorkspace that is mid-deletion (i.e. DeletionTimestamp != nil) will still be in the list returned by h.Client.List and will cause this webhook to reject a fresh workspace that reuses the same discoverable endpoint name — even though the old service is on its way out. This both contradicts the AI summary ("excluding same UID and deletion-timestamped workspaces") and the PR's stated intent in the commit history ("adds a check for terminating workspaces"), and it produces a bad UX (users must wait for full deletion before they can re-create).

🐛 Proposed fix
 	for _, otherWorkspace := range workspaceList.Items {
-		if otherWorkspace.UID == workspace.UID {
+		if otherWorkspace.UID == workspace.UID {
+			continue
+		}
+		if otherWorkspace.GetDeletionTimestamp() != nil {
 			continue
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webhook/workspace/handler/validate.go` around lines 114 - 130, The validation
loop currently only skips the same workspace by UID but still inspects
workspaces that are being deleted; modify the loop that iterates
workspaceList.Items (and the check around otherWorkspace) to also skip any
otherWorkspace with a non-nil DeletionTimestamp (i.e., treat terminating
workspaces as excluded from discoverable endpoint conflict checks), so that
discoverableEndpoints lookup and potential return of
solvers.ServiceConflictError only considers non-terminating workspaces.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dev Workspace Controller Loop with Identical Endpoint Names on DevWorkspace

4 participants