Skip to content

Commit d899dfb

Browse files
akurinnoyclaude
andcommitted
fixup! fixup! fixup! fixup! fix: identical endpoint name conflicts
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>
1 parent 7a5ff10 commit d899dfb

4 files changed

Lines changed: 42 additions & 36 deletions

File tree

‎.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ deploy/current
66
deploy/deployment/olm
77
generated
88
.vscode
9+
.claude/settings.local.json
910
__debug_bin
1011

1112
# Ignore cached kustomize for makefile

‎controllers/controller/devworkspacerouting/solvers/common_test.go‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@ package solvers
1616
import (
1717
"testing"
1818

19-
controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
20-
"github.com/devfile/devworkspace-operator/pkg/constants"
2119
"github.com/stretchr/testify/assert"
2220
corev1 "k8s.io/api/core/v1"
2321
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2422
"k8s.io/apimachinery/pkg/runtime"
2523
"sigs.k8s.io/controller-runtime/pkg/client/fake"
2624
"sigs.k8s.io/controller-runtime/pkg/log/zap"
25+
26+
controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
27+
"github.com/devfile/devworkspace-operator/pkg/constants"
2728
)
2829

2930
func TestGetDiscoverableServicesForEndpoints(t *testing.T) {

‎webhook/workspace/handler/validate.go‎

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,12 @@ func (h *WebhookHandler) ValidateDevfile(ctx context.Context, req admission.Requ
7878
}
7979
}
8080

81-
endpointErrors := h.validateEndpoints(ctx, wksp)
82-
if endpointErrors != nil {
83-
devfileErrors = append(devfileErrors, endpointErrors.Error())
81+
conflict, err := h.validateEndpoints(ctx, wksp)
82+
if err != nil {
83+
return admission.Errored(http.StatusInternalServerError, err)
84+
}
85+
if conflict != nil {
86+
devfileErrors = append(devfileErrors, conflict.Error())
8487
}
8588

8689
if len(devfileErrors) > 0 {
@@ -90,7 +93,7 @@ func (h *WebhookHandler) ValidateDevfile(ctx context.Context, req admission.Requ
9093
return admission.Allowed("No Devfile errors were found")
9194
}
9295

93-
func (h *WebhookHandler) validateEndpoints(ctx context.Context, workspace *dwv2.DevWorkspace) error {
96+
func (h *WebhookHandler) validateEndpoints(ctx context.Context, workspace *dwv2.DevWorkspace) (*solvers.ServiceConflictError, error) {
9497
discoverableEndpoints := map[string]bool{}
9598
for _, component := range workspace.Spec.Template.Components {
9699
if component.Container != nil {
@@ -103,12 +106,12 @@ func (h *WebhookHandler) validateEndpoints(ctx context.Context, workspace *dwv2.
103106
}
104107

105108
if len(discoverableEndpoints) == 0 {
106-
return nil
109+
return nil, nil
107110
}
108111

109112
workspaceList := &dwv2.DevWorkspaceList{}
110113
if err := h.Client.List(ctx, workspaceList, client.InNamespace(workspace.Namespace)); err != nil {
111-
return err
114+
return nil, err
112115
}
113116

114117
for _, otherWorkspace := range workspaceList.Items {
@@ -122,12 +125,12 @@ func (h *WebhookHandler) validateEndpoints(ctx context.Context, workspace *dwv2.
122125
return &solvers.ServiceConflictError{
123126
EndpointName: endpoint.Name,
124127
WorkspaceName: otherWorkspace.Name,
125-
}
128+
}, nil
126129
}
127130
}
128131
}
129132
}
130133
}
131134

132-
return nil
135+
return nil, nil
133136
}

‎webhook/workspace/handler/validate_test.go‎

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@ import (
1919
"path/filepath"
2020
"testing"
2121

22-
dwv2 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
23-
"github.com/devfile/devworkspace-operator/controllers/controller/devworkspacerouting/solvers"
2422
"github.com/stretchr/testify/assert"
2523
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2624
"k8s.io/apimachinery/pkg/runtime"
2725
"k8s.io/apimachinery/pkg/types"
2826
"sigs.k8s.io/controller-runtime/pkg/client"
2927
"sigs.k8s.io/controller-runtime/pkg/client/fake"
3028
"sigs.k8s.io/yaml"
29+
30+
dwv2 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
3131
)
3232

3333
func loadObjectFromFile(objName string, obj client.Object, filename string) error {
@@ -67,13 +67,11 @@ func TestValidateEndpoints(t *testing.T) {
6767
// Test for conflict in same namespace
6868
fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(otherWorkspaceSameNS).Build()
6969
handler := &WebhookHandler{Client: fakeClient}
70-
err := handler.validateEndpoints(context.TODO(), workspace)
71-
assert.Error(t, err, "Expected a conflict error for workspaces in the same namespace")
72-
73-
var conflictErr *solvers.ServiceConflictError
74-
assert.ErrorAs(t, err, &conflictErr, "Error should be a ServiceConflictError")
75-
assert.Equal(t, "test-endpoint", conflictErr.EndpointName, "Conflict should be on 'test-endpoint'")
76-
assert.Equal(t, "workspace-2", conflictErr.WorkspaceName, "Conflict should reference 'workspace-2'")
70+
conflict, err := handler.validateEndpoints(context.TODO(), workspace)
71+
assert.NoError(t, err, "Did not expect an infrastructure error")
72+
assert.NotNil(t, conflict, "Expected a conflict for workspaces in the same namespace")
73+
assert.Equal(t, "test-endpoint", conflict.EndpointName, "Conflict should be on 'test-endpoint'")
74+
assert.Equal(t, "workspace-2", conflict.WorkspaceName, "Conflict should reference 'workspace-2'")
7775
})
7876

7977
t.Run("No conflict in different namespace", func(t *testing.T) {
@@ -86,8 +84,9 @@ func TestValidateEndpoints(t *testing.T) {
8684
// Test no conflict in different namespace (workspace only queries its own namespace)
8785
fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(otherWorkspaceDiffNS).Build()
8886
handler := &WebhookHandler{Client: fakeClient}
89-
err := handler.validateEndpoints(context.TODO(), workspace)
90-
assert.NoError(t, err, "Did not expect an error for workspaces in different namespaces")
87+
conflict, err := handler.validateEndpoints(context.TODO(), workspace)
88+
assert.NoError(t, err)
89+
assert.Nil(t, conflict, "Did not expect a conflict for workspaces in different namespaces")
9190
})
9291

9392
t.Run("No conflict when endpoint name is different", func(t *testing.T) {
@@ -98,8 +97,9 @@ func TestValidateEndpoints(t *testing.T) {
9897

9998
fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(otherWorkspace).Build()
10099
handler := &WebhookHandler{Client: fakeClient}
101-
err := handler.validateEndpoints(context.TODO(), workspace)
102-
assert.NoError(t, err, "Did not expect an error for different endpoint names")
100+
conflict, err := handler.validateEndpoints(context.TODO(), workspace)
101+
assert.NoError(t, err)
102+
assert.Nil(t, conflict, "Did not expect a conflict for different endpoint names")
103103
})
104104

105105
t.Run("Conflict detected even when workspace is being deleted", func(t *testing.T) {
@@ -114,13 +114,11 @@ func TestValidateEndpoints(t *testing.T) {
114114

115115
fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(deletingWorkspace).Build()
116116
handler := &WebhookHandler{Client: fakeClient}
117-
err := handler.validateEndpoints(context.TODO(), workspace)
118-
assert.Error(t, err, "Should detect conflict even with workspace being deleted")
119-
120-
var conflictErr *solvers.ServiceConflictError
121-
assert.ErrorAs(t, err, &conflictErr, "Error should be a ServiceConflictError")
122-
assert.Equal(t, "test-endpoint", conflictErr.EndpointName, "Conflict should be on 'test-endpoint'")
123-
assert.Equal(t, "workspace-deleting", conflictErr.WorkspaceName, "Conflict should reference 'workspace-deleting'")
117+
conflict, err := handler.validateEndpoints(context.TODO(), workspace)
118+
assert.NoError(t, err, "Did not expect an infrastructure error")
119+
assert.NotNil(t, conflict, "Should detect conflict even with workspace being deleted")
120+
assert.Equal(t, "test-endpoint", conflict.EndpointName, "Conflict should be on 'test-endpoint'")
121+
assert.Equal(t, "workspace-deleting", conflict.WorkspaceName, "Conflict should reference 'workspace-deleting'")
124122
})
125123

126124
t.Run("No conflict when workspace has no discoverable endpoints", func(t *testing.T) {
@@ -132,8 +130,9 @@ func TestValidateEndpoints(t *testing.T) {
132130

133131
fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(otherWorkspace).Build()
134132
handler := &WebhookHandler{Client: fakeClient}
135-
err := handler.validateEndpoints(context.TODO(), workspace)
136-
assert.NoError(t, err, "Did not expect an error when workspace has no discoverable endpoints")
133+
conflict, err := handler.validateEndpoints(context.TODO(), workspace)
134+
assert.NoError(t, err)
135+
assert.Nil(t, conflict, "Did not expect a conflict when workspace has no discoverable endpoints")
137136
})
138137

139138
t.Run("No conflict when other workspace endpoint is not discoverable", func(t *testing.T) {
@@ -146,8 +145,9 @@ func TestValidateEndpoints(t *testing.T) {
146145

147146
fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(otherWorkspace).Build()
148147
handler := &WebhookHandler{Client: fakeClient}
149-
err := handler.validateEndpoints(context.TODO(), workspace)
150-
assert.NoError(t, err, "Should not conflict when other workspace's endpoint is not discoverable")
148+
conflict, err := handler.validateEndpoints(context.TODO(), workspace)
149+
assert.NoError(t, err)
150+
assert.Nil(t, conflict, "Should not conflict when other workspace's endpoint is not discoverable")
151151
})
152152

153153
t.Run("Multiple workspaces in different namespaces can have same endpoint", func(t *testing.T) {
@@ -166,7 +166,8 @@ func TestValidateEndpoints(t *testing.T) {
166166
handler := &WebhookHandler{Client: fakeClient}
167167

168168
// Validating workspace1 should succeed (different namespaces)
169-
err := handler.validateEndpoints(context.TODO(), workspace1)
170-
assert.NoError(t, err, "Should allow same endpoint name in different namespaces")
169+
conflict, err := handler.validateEndpoints(context.TODO(), workspace1)
170+
assert.NoError(t, err)
171+
assert.Nil(t, conflict, "Should allow same endpoint name in different namespaces")
171172
})
172173
}

0 commit comments

Comments
 (0)