fix: identical endpoint name conflicts#1521
Conversation
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>
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>
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: akurinnoy, tolusha The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
Signed-off-by: Oleksii Kurinnyi <okurinny@redhat.com>
|
New changes are detected. LGTM label has been removed. |
|
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>
|
/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) { |
There was a problem hiding this comment.
@akurinnoy is there a reason for implementing duplicate endpiont detection in both the webhook and the GetDiscoverableServicesForEndpoints function?
There was a problem hiding this comment.
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?
|
@akurinnoy : I ran your claude skill 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
Do you think Workspace B should be allowed because Workspace A's endpoint is NOT discoverable and doesn't create a service named |
|
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:
|
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>
📝 WalkthroughWalkthroughThis PR adds ownership-aware discoverable service handling: it introduces ServiceConflictError, refactors solvers to accept a Kubernetes client and logger and to perform in-cluster checks for existing Services, integrates conflict handling into the routing controller and admission webhook, and provides tests, RBAC, and test-suite config. ChangesDiscoverable Endpoint Conflict Detection
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (12)
controllers/controller/devworkspacerouting/devworkspacerouting_controller.gocontrollers/controller/devworkspacerouting/solvers/basic_solver.gocontrollers/controller/devworkspacerouting/solvers/cluster_solver.gocontrollers/controller/devworkspacerouting/solvers/common.gocontrollers/controller/devworkspacerouting/solvers/common_test.gocontrollers/controller/devworkspacerouting/solvers/errors.gocontrollers/controller/devworkspacerouting/solvers/solver.gocontrollers/workspace/suite_test.gopkg/webhook/cluster_roles.gowebhook/workspace/handler/testdata/test-devworkspace.yamlwebhook/workspace/handler/validate.gowebhook/workspace/handler/validate_test.go
Address CodeRabbitAI review feedback: - Return (*ServiceConflictError, error) from validateEndpoints so infrastructure errors use admission.Errored instead of admission.Denied - Fix import ordering in common_test.go and validate_test.go (3 groups) - Add .claude/settings.local.json to .gitignore 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>
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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 `@webhook/workspace/handler/validate_test.go`:
- Line 1: Update the copyright header in
webhook/workspace/handler/validate_test.go by changing the year range string "//
Copyright (c) 2019-2025 Red Hat, Inc." to the current-year range "// Copyright
(c) 2019-2026 Red Hat, Inc."; locate and replace that exact header line at the
top of the file so it matches the required format used across Go sources.
- Line 58: The test currently ignores the error from dwv2.AddToScheme(scheme);
change this to explicitly assert success by calling assert.NoError(t,
dwv2.AddToScheme(scheme)) so the scheme initialization failure surfaces in the
test; locate the call to dwv2.AddToScheme in validate_test.go (the variable
scheme) and replace the `_ =` ignoring pattern with the assert.NoError check
(using the existing testify/assert import).
In `@webhook/workspace/handler/validate.go`:
- Around line 24-29: Reorder the import block in validate.go so imports follow
Go grouping: first any standard-library packages (none shown here), then a blank
line, then third-party and Kubernetes imports together (e.g.,
"github.com/devfile/api/v2/..." packages and
"sigs.k8s.io/controller-runtime/..." client and admission), then a blank line,
and finally project-local imports (e.g.,
"github.com/devfile/devworkspace-operator/apis/controller/v1alpha1" and
"github.com/devfile/devworkspace-operator/controllers/controller/devworkspacerouting/solvers");
ensure there are blank lines separating these three groups and keep the existing
import identifiers (dwv2, devfilevalidation, v1alpha1, solvers, client,
admission) intact.
🪄 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: 13233f4a-6817-4e7f-a346-91250ddebbe0
📒 Files selected for processing (4)
.gitignorecontrollers/controller/devworkspacerouting/solvers/common_test.gowebhook/workspace/handler/validate.gowebhook/workspace/handler/validate_test.go
✅ Files skipped from review due to trivial changes (1)
- .gitignore
🚧 Files skipped from review as they are similar to previous changes (1)
- controllers/controller/devworkspacerouting/solvers/common_test.go
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
oc wait --for=condition=Ready dw/endpoint-test-1 -n test-namespace --timeout=300sExpected result:
Cross-name isolation
PR Checklist
/test v8-devworkspace-operator-e2e, v8-che-happy-pathto trigger)v8-devworkspace-operator-e2e: DevWorkspace e2e testv8-che-happy-path: Happy path for verification integration with CheSummary by CodeRabbit
New Features
Bug Fixes
Tests
Chores