@@ -18,6 +18,8 @@ package v1
1818import (
1919 "context"
2020 "fmt"
21+ "sort"
22+ "strings"
2123
2224 "k8s.io/apimachinery/pkg/runtime"
2325 "k8s.io/apimachinery/pkg/types"
@@ -63,7 +65,12 @@ func (v *GatewayProxyCustomValidator) ValidateCreate(ctx context.Context, obj ru
6365 }
6466 gatewayProxyLog .Info ("Validation for GatewayProxy upon creation" , "name" , gp .GetName (), "namespace" , gp .GetNamespace ())
6567
66- return v .collectWarnings (ctx , gp ), nil
68+ warnings := v .collectWarnings (ctx , gp )
69+ if err := v .validateGatewayProxyConflict (ctx , gp ); err != nil {
70+ return nil , err
71+ }
72+
73+ return warnings , nil
6774}
6875
6976func (v * GatewayProxyCustomValidator ) ValidateUpdate (ctx context.Context , oldObj , newObj runtime.Object ) (admission.Warnings , error ) {
@@ -73,7 +80,12 @@ func (v *GatewayProxyCustomValidator) ValidateUpdate(ctx context.Context, oldObj
7380 }
7481 gatewayProxyLog .Info ("Validation for GatewayProxy upon update" , "name" , gp .GetName (), "namespace" , gp .GetNamespace ())
7582
76- return v .collectWarnings (ctx , gp ), nil
83+ warnings := v .collectWarnings (ctx , gp )
84+ if err := v .validateGatewayProxyConflict (ctx , gp ); err != nil {
85+ return nil , err
86+ }
87+
88+ return warnings , nil
7789}
7890
7991func (v * GatewayProxyCustomValidator ) ValidateDelete (context.Context , runtime.Object ) (admission.Warnings , error ) {
@@ -111,3 +123,126 @@ func (v *GatewayProxyCustomValidator) collectWarnings(ctx context.Context, gp *v
111123
112124 return warnings
113125}
126+
127+ func (v * GatewayProxyCustomValidator ) validateGatewayProxyConflict (ctx context.Context , gp * v1alpha1.GatewayProxy ) error {
128+ current := buildGatewayProxyConfig (gp )
129+ if ! current .readyForConflict () {
130+ return nil
131+ }
132+
133+ var list v1alpha1.GatewayProxyList
134+ if err := v .Client .List (ctx , & list ); err != nil {
135+ gatewayProxyLog .Error (err , "failed to list GatewayProxy objects for conflict detection" )
136+ return fmt .Errorf ("failed to list existing GatewayProxy resources: %w" , err )
137+ }
138+
139+ for _ , other := range list .Items {
140+ if other .GetNamespace () == gp .GetNamespace () && other .GetName () == gp .GetName () {
141+ // skip self
142+ continue
143+ }
144+ otherConfig := buildGatewayProxyConfig (& other )
145+ if ! otherConfig .readyForConflict () {
146+ continue
147+ }
148+ if ! current .sharesAdminKeyWith (otherConfig ) {
149+ continue
150+ }
151+ if current .serviceKey != "" && current .serviceKey == otherConfig .serviceKey {
152+ return fmt .Errorf ("gateway proxy configuration conflict: GatewayProxy %s/%s and %s/%s both target %s while sharing %s" ,
153+ gp .GetNamespace (), gp .GetName (),
154+ other .GetNamespace (), other .GetName (),
155+ current .serviceDescription ,
156+ current .adminKeyDetail (),
157+ )
158+ }
159+ if len (current .endpoints ) > 0 && len (otherConfig .endpoints ) > 0 {
160+ if overlap := current .endpointOverlap (otherConfig ); len (overlap ) > 0 {
161+ return fmt .Errorf ("gateway proxy configuration conflict: GatewayProxy %s/%s and %s/%s both target control plane endpoints [%s] while sharing %s" ,
162+ gp .GetNamespace (), gp .GetName (),
163+ other .GetNamespace (), other .GetName (),
164+ strings .Join (overlap , ", " ),
165+ current .adminKeyDetail (),
166+ )
167+ }
168+ }
169+ }
170+
171+ return nil
172+ }
173+
174+ type gatewayProxyConfig struct {
175+ inlineAdminKey string
176+ secretKey string
177+ serviceKey string
178+ serviceDescription string
179+ endpoints map [string ]struct {}
180+ }
181+
182+ func buildGatewayProxyConfig (gp * v1alpha1.GatewayProxy ) gatewayProxyConfig {
183+ var cfg gatewayProxyConfig
184+
185+ if gp == nil || gp .Spec .Provider == nil || gp .Spec .Provider .Type != v1alpha1 .ProviderTypeControlPlane || gp .Spec .Provider .ControlPlane == nil {
186+ return cfg
187+ }
188+
189+ cp := gp .Spec .Provider .ControlPlane
190+
191+ if cp .Auth .AdminKey != nil {
192+ if value := strings .TrimSpace (cp .Auth .AdminKey .Value ); value != "" {
193+ cfg .inlineAdminKey = value
194+ } else if cp .Auth .AdminKey .ValueFrom != nil && cp .Auth .AdminKey .ValueFrom .SecretKeyRef != nil {
195+ ref := cp .Auth .AdminKey .ValueFrom .SecretKeyRef
196+ cfg .secretKey = fmt .Sprintf ("%s/%s:%s" , gp .GetNamespace (), ref .Name , ref .Key )
197+ }
198+ }
199+
200+ if cp .Service != nil && cp .Service .Name != "" {
201+ cfg .serviceKey = fmt .Sprintf ("service:%s/%s:%d" , gp .GetNamespace (), cp .Service .Name , cp .Service .Port )
202+ cfg .serviceDescription = fmt .Sprintf ("Service %s/%s port %d" , gp .GetNamespace (), cp .Service .Name , cp .Service .Port )
203+ }
204+
205+ if len (cp .Endpoints ) > 0 {
206+ cfg .endpoints = make (map [string ]struct {}, len (cp .Endpoints ))
207+ for _ , endpoint := range cp .Endpoints {
208+ cfg .endpoints [endpoint ] = struct {}{}
209+ }
210+ }
211+
212+ return cfg
213+ }
214+
215+ func (c gatewayProxyConfig ) adminKeyDetail () string {
216+ if c .secretKey != "" {
217+ return fmt .Sprintf ("AdminKey secret %s" , c .secretKey )
218+ }
219+ return "the same inline AdminKey value"
220+ }
221+
222+ func (c gatewayProxyConfig ) sharesAdminKeyWith (other gatewayProxyConfig ) bool {
223+ if c .inlineAdminKey != "" && other .inlineAdminKey != "" {
224+ return c .inlineAdminKey == other .inlineAdminKey
225+ }
226+ if c .secretKey != "" && other .secretKey != "" {
227+ return c .secretKey == other .secretKey
228+ }
229+ return false
230+ }
231+
232+ func (c gatewayProxyConfig ) readyForConflict () bool {
233+ if c .inlineAdminKey == "" && c .secretKey == "" {
234+ return false
235+ }
236+ return c .serviceKey != "" || len (c .endpoints ) > 0
237+ }
238+
239+ func (c gatewayProxyConfig ) endpointOverlap (other gatewayProxyConfig ) []string {
240+ var overlap []string
241+ for endpoint := range c .endpoints {
242+ if _ , ok := other .endpoints [endpoint ]; ok {
243+ overlap = append (overlap , endpoint )
244+ }
245+ }
246+ sort .Strings (overlap )
247+ return overlap
248+ }
0 commit comments