This is an automated email from the ASF dual-hosted git repository.

ronething pushed a commit to branch fix/gatewayproxy_check
in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git

commit 3210a9d109e4af9f96399dcfda7e816f32d33af7
Author: Ashing Zheng <[email protected]>
AuthorDate: Mon Oct 13 17:02:44 2025 +0800

    feat: add conflict detection for gateway proxy
    
    Signed-off-by: Ashing Zheng <[email protected]>
---
 internal/controller/indexer/tlsroute.go          |   3 +-
 internal/provider/init/init.go                   |   3 +-
 internal/provider/register.go                    |   3 +-
 internal/webhook/v1/gatewayproxy_webhook.go      | 139 ++++++++++++++-
 internal/webhook/v1/gatewayproxy_webhook_test.go | 217 ++++++++++++++++++++++-
 test/e2e/webhook/gatewayproxy.go                 | 159 ++++++++++++++++-
 6 files changed, 510 insertions(+), 14 deletions(-)

diff --git a/internal/controller/indexer/tlsroute.go 
b/internal/controller/indexer/tlsroute.go
index 567131c4..acef5317 100644
--- a/internal/controller/indexer/tlsroute.go
+++ b/internal/controller/indexer/tlsroute.go
@@ -20,10 +20,11 @@ package indexer
 import (
        "context"
 
-       internaltypes 
"github.com/apache/apisix-ingress-controller/internal/types"
        ctrl "sigs.k8s.io/controller-runtime"
        "sigs.k8s.io/controller-runtime/pkg/client"
        gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
+
+       internaltypes 
"github.com/apache/apisix-ingress-controller/internal/types"
 )
 
 func setupTLSRouteIndexer(mgr ctrl.Manager) error {
diff --git a/internal/provider/init/init.go b/internal/provider/init/init.go
index b6ed9e99..be21c07d 100644
--- a/internal/provider/init/init.go
+++ b/internal/provider/init/init.go
@@ -18,11 +18,12 @@
 package init
 
 import (
+       "github.com/go-logr/logr"
+
        "github.com/apache/apisix-ingress-controller/internal/controller/status"
        "github.com/apache/apisix-ingress-controller/internal/manager/readiness"
        "github.com/apache/apisix-ingress-controller/internal/provider"
        "github.com/apache/apisix-ingress-controller/internal/provider/apisix"
-       "github.com/go-logr/logr"
 )
 
 func init() {
diff --git a/internal/provider/register.go b/internal/provider/register.go
index fddb1af5..a9feb032 100644
--- a/internal/provider/register.go
+++ b/internal/provider/register.go
@@ -21,9 +21,10 @@ import (
        "fmt"
        "net/http"
 
+       "github.com/go-logr/logr"
+
        "github.com/apache/apisix-ingress-controller/internal/controller/status"
        "github.com/apache/apisix-ingress-controller/internal/manager/readiness"
-       "github.com/go-logr/logr"
 )
 
 type RegisterHandler interface {
diff --git a/internal/webhook/v1/gatewayproxy_webhook.go 
b/internal/webhook/v1/gatewayproxy_webhook.go
index 75bccea3..b76a8cf7 100644
--- a/internal/webhook/v1/gatewayproxy_webhook.go
+++ b/internal/webhook/v1/gatewayproxy_webhook.go
@@ -18,6 +18,8 @@ package v1
 import (
        "context"
        "fmt"
+       "sort"
+       "strings"
 
        "k8s.io/apimachinery/pkg/runtime"
        "k8s.io/apimachinery/pkg/types"
@@ -63,7 +65,12 @@ func (v *GatewayProxyCustomValidator) ValidateCreate(ctx 
context.Context, obj ru
        }
        gatewayProxyLog.Info("Validation for GatewayProxy upon creation", 
"name", gp.GetName(), "namespace", gp.GetNamespace())
 
-       return v.collectWarnings(ctx, gp), nil
+       warnings := v.collectWarnings(ctx, gp)
+       if err := v.validateGatewayProxyConflict(ctx, gp); err != nil {
+               return nil, err
+       }
+
+       return warnings, nil
 }
 
 func (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
        }
        gatewayProxyLog.Info("Validation for GatewayProxy upon update", "name", 
gp.GetName(), "namespace", gp.GetNamespace())
 
-       return v.collectWarnings(ctx, gp), nil
+       warnings := v.collectWarnings(ctx, gp)
+       if err := v.validateGatewayProxyConflict(ctx, gp); err != nil {
+               return nil, err
+       }
+
+       return warnings, nil
 }
 
 func (v *GatewayProxyCustomValidator) ValidateDelete(context.Context, 
runtime.Object) (admission.Warnings, error) {
@@ -111,3 +123,126 @@ func (v *GatewayProxyCustomValidator) collectWarnings(ctx 
context.Context, gp *v
 
        return warnings
 }
+
+func (v *GatewayProxyCustomValidator) validateGatewayProxyConflict(ctx 
context.Context, gp *v1alpha1.GatewayProxy) error {
+       current := buildGatewayProxyConfig(gp)
+       if !current.readyForConflict() {
+               return nil
+       }
+
+       var list v1alpha1.GatewayProxyList
+       if err := v.Client.List(ctx, &list); err != nil {
+               gatewayProxyLog.Error(err, "failed to list GatewayProxy objects 
for conflict detection")
+               return fmt.Errorf("failed to list existing GatewayProxy 
resources: %w", err)
+       }
+
+       for _, other := range list.Items {
+               if other.GetNamespace() == gp.GetNamespace() && other.GetName() 
== gp.GetName() {
+                       // skip self
+                       continue
+               }
+               otherConfig := buildGatewayProxyConfig(&other)
+               if !otherConfig.readyForConflict() {
+                       continue
+               }
+               if !current.sharesAdminKeyWith(otherConfig) {
+                       continue
+               }
+               if current.serviceKey != "" && current.serviceKey == 
otherConfig.serviceKey {
+                       return fmt.Errorf("gateway proxy configuration 
conflict: GatewayProxy %s/%s and %s/%s both target %s while sharing %s",
+                               gp.GetNamespace(), gp.GetName(),
+                               other.GetNamespace(), other.GetName(),
+                               current.serviceDescription,
+                               current.adminKeyDetail(),
+                       )
+               }
+               if len(current.endpoints) > 0 && len(otherConfig.endpoints) > 0 
{
+                       if overlap := current.endpointOverlap(otherConfig); 
len(overlap) > 0 {
+                               return fmt.Errorf("gateway proxy configuration 
conflict: GatewayProxy %s/%s and %s/%s both target control plane endpoints [%s] 
while sharing %s",
+                                       gp.GetNamespace(), gp.GetName(),
+                                       other.GetNamespace(), other.GetName(),
+                                       strings.Join(overlap, ", "),
+                                       current.adminKeyDetail(),
+                               )
+                       }
+               }
+       }
+
+       return nil
+}
+
+type gatewayProxyConfig struct {
+       inlineAdminKey     string
+       secretKey          string
+       serviceKey         string
+       serviceDescription string
+       endpoints          map[string]struct{}
+}
+
+func buildGatewayProxyConfig(gp *v1alpha1.GatewayProxy) gatewayProxyConfig {
+       var cfg gatewayProxyConfig
+
+       if gp == nil || gp.Spec.Provider == nil || gp.Spec.Provider.Type != 
v1alpha1.ProviderTypeControlPlane || gp.Spec.Provider.ControlPlane == nil {
+               return cfg
+       }
+
+       cp := gp.Spec.Provider.ControlPlane
+
+       if cp.Auth.AdminKey != nil {
+               if value := strings.TrimSpace(cp.Auth.AdminKey.Value); value != 
"" {
+                       cfg.inlineAdminKey = value
+               } else if cp.Auth.AdminKey.ValueFrom != nil && 
cp.Auth.AdminKey.ValueFrom.SecretKeyRef != nil {
+                       ref := cp.Auth.AdminKey.ValueFrom.SecretKeyRef
+                       cfg.secretKey = fmt.Sprintf("%s/%s:%s", 
gp.GetNamespace(), ref.Name, ref.Key)
+               }
+       }
+
+       if cp.Service != nil && cp.Service.Name != "" {
+               cfg.serviceKey = fmt.Sprintf("service:%s/%s:%d", 
gp.GetNamespace(), cp.Service.Name, cp.Service.Port)
+               cfg.serviceDescription = fmt.Sprintf("Service %s/%s port %d", 
gp.GetNamespace(), cp.Service.Name, cp.Service.Port)
+       }
+
+       if len(cp.Endpoints) > 0 {
+               cfg.endpoints = make(map[string]struct{}, len(cp.Endpoints))
+               for _, endpoint := range cp.Endpoints {
+                       cfg.endpoints[endpoint] = struct{}{}
+               }
+       }
+
+       return cfg
+}
+
+func (c gatewayProxyConfig) adminKeyDetail() string {
+       if c.secretKey != "" {
+               return fmt.Sprintf("AdminKey secret %s", c.secretKey)
+       }
+       return "the same inline AdminKey value"
+}
+
+func (c gatewayProxyConfig) sharesAdminKeyWith(other gatewayProxyConfig) bool {
+       if c.inlineAdminKey != "" && other.inlineAdminKey != "" {
+               return c.inlineAdminKey == other.inlineAdminKey
+       }
+       if c.secretKey != "" && other.secretKey != "" {
+               return c.secretKey == other.secretKey
+       }
+       return false
+}
+
+func (c gatewayProxyConfig) readyForConflict() bool {
+       if c.inlineAdminKey == "" && c.secretKey == "" {
+               return false
+       }
+       return c.serviceKey != "" || len(c.endpoints) > 0
+}
+
+func (c gatewayProxyConfig) endpointOverlap(other gatewayProxyConfig) []string 
{
+       var overlap []string
+       for endpoint := range c.endpoints {
+               if _, ok := other.endpoints[endpoint]; ok {
+                       overlap = append(overlap, endpoint)
+               }
+       }
+       sort.Strings(overlap)
+       return overlap
+}
diff --git a/internal/webhook/v1/gatewayproxy_webhook_test.go 
b/internal/webhook/v1/gatewayproxy_webhook_test.go
index c43253c1..2768ac7a 100644
--- a/internal/webhook/v1/gatewayproxy_webhook_test.go
+++ b/internal/webhook/v1/gatewayproxy_webhook_test.go
@@ -29,6 +29,10 @@ import (
        v1alpha1 "github.com/apache/apisix-ingress-controller/api/v1alpha1"
 )
 
+const (
+       candidateName = "candidate"
+)
+
 func buildGatewayProxyValidator(t *testing.T, objects ...runtime.Object) 
*GatewayProxyCustomValidator {
        t.Helper()
 
@@ -54,7 +58,7 @@ func newGatewayProxy() *v1alpha1.GatewayProxy {
                        Provider: &v1alpha1.GatewayProxyProvider{
                                Type: v1alpha1.ProviderTypeControlPlane,
                                ControlPlane: &v1alpha1.ControlPlaneProvider{
-                                       Service: 
&v1alpha1.ProviderService{Name: "control-plane"},
+                                       Service: 
&v1alpha1.ProviderService{Name: "control-plane", Port: 9180},
                                        Auth: v1alpha1.ControlPlaneAuth{
                                                Type: v1alpha1.AuthTypeAdminKey,
                                                AdminKey: 
&v1alpha1.AdminKeyAuth{
@@ -72,6 +76,41 @@ func newGatewayProxy() *v1alpha1.GatewayProxy {
        }
 }
 
+func newGatewayProxyWithEndpoints(name string, endpoints []string) 
*v1alpha1.GatewayProxy {
+       gp := newGatewayProxy()
+       gp.Name = name
+       gp.Spec.Provider.ControlPlane.Service = nil
+       gp.Spec.Provider.ControlPlane.Endpoints = endpoints
+       return gp
+}
+
+func setInlineAdminKey(gp *v1alpha1.GatewayProxy, value string) {
+       if gp == nil || gp.Spec.Provider == nil || 
gp.Spec.Provider.ControlPlane == nil {
+               return
+       }
+       if gp.Spec.Provider.ControlPlane.Auth.AdminKey == nil {
+               gp.Spec.Provider.ControlPlane.Auth.AdminKey = 
&v1alpha1.AdminKeyAuth{}
+       }
+       gp.Spec.Provider.ControlPlane.Auth.AdminKey.Value = value
+       gp.Spec.Provider.ControlPlane.Auth.AdminKey.ValueFrom = nil
+}
+
+func setSecretAdminKey(gp *v1alpha1.GatewayProxy, name, key string) {
+       if gp == nil || gp.Spec.Provider == nil || 
gp.Spec.Provider.ControlPlane == nil {
+               return
+       }
+       if gp.Spec.Provider.ControlPlane.Auth.AdminKey == nil {
+               gp.Spec.Provider.ControlPlane.Auth.AdminKey = 
&v1alpha1.AdminKeyAuth{}
+       }
+       gp.Spec.Provider.ControlPlane.Auth.AdminKey.Value = ""
+       gp.Spec.Provider.ControlPlane.Auth.AdminKey.ValueFrom = 
&v1alpha1.AdminKeyValueFrom{
+               SecretKeyRef: &v1alpha1.SecretKeySelector{
+                       Name: name,
+                       Key:  key,
+               },
+       }
+}
+
 func TestGatewayProxyValidator_MissingService(t *testing.T) {
        gp := newGatewayProxy()
        gp.Spec.Provider.ControlPlane.Auth.AdminKey = nil
@@ -150,3 +189,179 @@ func TestGatewayProxyValidator_NoWarnings(t *testing.T) {
        require.NoError(t, err)
        require.Empty(t, warnings)
 }
+
+func TestGatewayProxyValidator_DetectsServiceConflict(t *testing.T) {
+       existing := newGatewayProxy()
+       existing.Name = "existing"
+
+       service := &corev1.Service{
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      "control-plane",
+                       Namespace: "default",
+               },
+       }
+       secret := &corev1.Secret{
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      "admin-key",
+                       Namespace: "default",
+               },
+               Data: map[string][]byte{
+                       "token": []byte("value"),
+               },
+       }
+
+       validator := buildGatewayProxyValidator(t, existing, service, secret)
+
+       candidate := newGatewayProxy()
+       candidate.Name = candidateName
+
+       warnings, err := validator.ValidateCreate(context.Background(), 
candidate)
+       require.Error(t, err)
+       require.Len(t, warnings, 0)
+       require.Contains(t, err.Error(), "gateway proxy configuration conflict")
+       require.Contains(t, err.Error(), "Service default/control-plane port 
9180")
+       require.Contains(t, err.Error(), "AdminKey secret 
default/admin-key:token")
+}
+
+func TestGatewayProxyValidator_DetectsEndpointConflict(t *testing.T) {
+       existing := newGatewayProxyWithEndpoints("existing", 
[]string{"https://127.0.0.1:9443";, "https://10.0.0.1:9443"})
+       secret := &corev1.Secret{
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      "admin-key",
+                       Namespace: "default",
+               },
+               Data: map[string][]byte{
+                       "token": []byte("value"),
+               },
+       }
+       validator := buildGatewayProxyValidator(t, existing, secret)
+
+       candidate := newGatewayProxyWithEndpoints(candidateName, 
[]string{"https://10.0.0.1:9443";, "https://127.0.0.1:9443"})
+
+       warnings, err := validator.ValidateCreate(context.Background(), 
candidate)
+       require.Error(t, err)
+       require.Len(t, warnings, 0)
+       require.Contains(t, err.Error(), "gateway proxy configuration conflict")
+       require.Contains(t, err.Error(), "endpoints [https://10.0.0.1:9443, 
https://127.0.0.1:9443]";)
+       require.Contains(t, err.Error(), "AdminKey secret 
default/admin-key:token")
+}
+
+func TestGatewayProxyValidator_AllowsDistinctGatewayGroups(t *testing.T) {
+       existing := newGatewayProxyWithEndpoints("existing", 
[]string{"https://127.0.0.1:9443"})
+       secret := &corev1.Secret{
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      "admin-key",
+                       Namespace: "default",
+               },
+               Data: map[string][]byte{
+                       "token": []byte("value"),
+               },
+       }
+       service := &corev1.Service{
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      "control-plane",
+                       Namespace: "default",
+               },
+       }
+       validator := buildGatewayProxyValidator(t, existing, secret, service)
+
+       candidate := newGatewayProxy()
+       candidate.Name = candidateName
+       candidate.Spec.Provider.ControlPlane.Service = 
&v1alpha1.ProviderService{
+               Name: "control-plane",
+               Port: 9180,
+       }
+
+       warnings, err := validator.ValidateCreate(context.Background(), 
candidate)
+       require.NoError(t, err)
+       require.Empty(t, warnings)
+}
+
+func TestGatewayProxyValidator_AllowsServiceConflictWithDifferentAdminSecret(t 
*testing.T) {
+       existing := newGatewayProxy()
+       existing.Name = "existing"
+
+       candidate := newGatewayProxy()
+       candidate.Name = candidateName
+       setSecretAdminKey(candidate, "admin-key-alt", "token")
+
+       service := &corev1.Service{
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      "control-plane",
+                       Namespace: "default",
+               },
+       }
+       existingSecret := &corev1.Secret{
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      "admin-key",
+                       Namespace: "default",
+               },
+               Data: map[string][]byte{
+                       "token": []byte("value"),
+               },
+       }
+       altSecret := &corev1.Secret{
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      "admin-key-alt",
+                       Namespace: "default",
+               },
+               Data: map[string][]byte{
+                       "token": []byte("value"),
+               },
+       }
+
+       validator := buildGatewayProxyValidator(t, existing, service, 
existingSecret, altSecret)
+
+       warnings, err := validator.ValidateCreate(context.Background(), 
candidate)
+       require.NoError(t, err)
+       require.Empty(t, warnings)
+}
+
+func TestGatewayProxyValidator_DetectsInlineAdminKeyConflict(t *testing.T) {
+       existing := newGatewayProxyWithEndpoints("existing", 
[]string{"https://127.0.0.1:9443";, "https://10.0.0.1:9443"})
+       setInlineAdminKey(existing, "inline-cred")
+
+       candidate := newGatewayProxyWithEndpoints(candidateName, 
[]string{"https://10.0.0.1:9443"})
+       setInlineAdminKey(candidate, "inline-cred")
+
+       validator := buildGatewayProxyValidator(t, existing)
+
+       warnings, err := validator.ValidateCreate(context.Background(), 
candidate)
+       require.Error(t, err)
+       require.Len(t, warnings, 0)
+       require.Contains(t, err.Error(), "gateway proxy configuration conflict")
+       require.Contains(t, err.Error(), "control plane endpoints 
[https://10.0.0.1:9443]";)
+       require.Contains(t, err.Error(), "inline AdminKey value")
+}
+
+func TestGatewayProxyValidator_AllowsEndpointOverlapWithDifferentAdminKey(t 
*testing.T) {
+       existing := newGatewayProxyWithEndpoints("existing", 
[]string{"https://127.0.0.1:9443";, "https://10.0.0.1:9443"})
+
+       candidate := newGatewayProxyWithEndpoints(candidateName, 
[]string{"https://10.0.0.1:9443";, "https://192.168.0.1:9443"})
+       setSecretAdminKey(candidate, "admin-key-alt", "token")
+
+       existingSecret := &corev1.Secret{
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      "admin-key",
+                       Namespace: "default",
+               },
+               Data: map[string][]byte{
+                       "token": []byte("value"),
+               },
+       }
+       altSecret := &corev1.Secret{
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      "admin-key-alt",
+                       Namespace: "default",
+               },
+               Data: map[string][]byte{
+                       "token": []byte("value"),
+               },
+       }
+
+       validator := buildGatewayProxyValidator(t, existing, existingSecret, 
altSecret)
+
+       warnings, err := validator.ValidateCreate(context.Background(), 
candidate)
+       require.NoError(t, err)
+       require.Empty(t, warnings)
+}
diff --git a/test/e2e/webhook/gatewayproxy.go b/test/e2e/webhook/gatewayproxy.go
index 6b1f6189..4f2d12e4 100644
--- a/test/e2e/webhook/gatewayproxy.go
+++ b/test/e2e/webhook/gatewayproxy.go
@@ -33,11 +33,7 @@ var _ = Describe("Test GatewayProxy Webhook", 
Label("webhook"), func() {
                EnableWebhook: true,
        })
 
-       It("should warn on missing service or secret references", func() {
-               missingService := "missing-control-plane"
-               missingSecret := "missing-admin-secret"
-               gpName := "webhook-gateway-proxy"
-               gpWithSecrets := `
+       gatewayProxyTemplate := `
 apiVersion: apisix.apache.org/v1alpha1
 kind: GatewayProxy
 metadata:
@@ -58,7 +54,12 @@ spec:
               key: token
 `
 
-               output, err := 
s.CreateResourceFromStringAndGetOutput(fmt.Sprintf(gpWithSecrets, gpName, 
missingService, missingSecret))
+       It("should warn on missing service or secret references", func() {
+               missingService := "missing-control-plane"
+               missingSecret := "missing-admin-secret"
+               gpName := "webhook-gateway-proxy"
+
+               output, err := 
s.CreateResourceFromStringAndGetOutput(fmt.Sprintf(gatewayProxyTemplate, 
gpName, missingService, missingSecret))
                Expect(err).ShouldNot(HaveOccurred())
                Expect(output).To(ContainSubstring(fmt.Sprintf("Warning: 
Referenced Service '%s/%s' not found", s.Namespace(), missingService)))
                Expect(output).To(ContainSubstring(fmt.Sprintf("Warning: 
Referenced Secret '%s/%s' not found", s.Namespace(), missingSecret)))
@@ -98,7 +99,7 @@ stringData:
                err = s.DeleteResource("GatewayProxy", gpName)
                Expect(err).ShouldNot(HaveOccurred())
 
-               output, err = 
s.CreateResourceFromStringAndGetOutput(fmt.Sprintf(gpWithSecrets, gpName, 
missingService, missingSecret))
+               output, err = 
s.CreateResourceFromStringAndGetOutput(fmt.Sprintf(gatewayProxyTemplate, 
gpName, missingService, missingSecret))
                Expect(err).ShouldNot(HaveOccurred())
                Expect(output).NotTo(ContainSubstring(fmt.Sprintf("Warning: 
Referenced Service '%s/%s' not found", s.Namespace(), missingService)))
                Expect(output).To(ContainSubstring(fmt.Sprintf("Warning: Secret 
key 'token' not found in Secret '%s/%s'", s.Namespace(), missingSecret)))
@@ -121,9 +122,151 @@ stringData:
                err = s.DeleteResource("GatewayProxy", gpName)
                Expect(err).ShouldNot(HaveOccurred())
 
-               output, err = 
s.CreateResourceFromStringAndGetOutput(fmt.Sprintf(gpWithSecrets, gpName, 
missingService, missingSecret))
+               output, err = 
s.CreateResourceFromStringAndGetOutput(fmt.Sprintf(gatewayProxyTemplate, 
gpName, missingService, missingSecret))
                Expect(err).ShouldNot(HaveOccurred())
                Expect(output).NotTo(ContainSubstring(fmt.Sprintf("Warning: 
Referenced Service '%s/%s' not found", s.Namespace(), missingService)))
                Expect(output).NotTo(ContainSubstring(fmt.Sprintf("Warning: 
Secret key 'token' not found in Secret '%s/%s'", s.Namespace(), missingSecret)))
        })
+
+       Context("GatewayProxy configuration conflicts", func() {
+               It("should reject GatewayProxy that reuses the same Service and 
AdminKey Secret as an existing one on create and update", func() {
+                       serviceTemplate := `
+apiVersion: v1
+kind: Service
+metadata:
+  name: %s
+spec:
+  selector:
+    app: dummy-control-plane
+  ports:
+  - name: admin
+    port: 9180
+    targetPort: 9180
+`
+                       secretTemplate := `
+apiVersion: v1
+kind: Secret
+metadata:
+  name: %s
+type: Opaque
+stringData:
+  %s: %s
+`
+                       serviceName := "gatewayproxy-shared-service"
+                       secretName := "gatewayproxy-shared-secret"
+                       initialProxy := "gatewayproxy-shared-primary"
+                       conflictingProxy := "gatewayproxy-shared-conflict"
+
+                       
Expect(s.CreateResourceFromString(fmt.Sprintf(serviceTemplate, 
serviceName))).ShouldNot(HaveOccurred(), "creating shared Service")
+                       
Expect(s.CreateResourceFromString(fmt.Sprintf(secretTemplate, secretName, 
"token", "value"))).ShouldNot(HaveOccurred(), "creating shared Secret")
+
+                       err := 
s.CreateResourceFromString(fmt.Sprintf(gatewayProxyTemplate, initialProxy, 
serviceName, secretName))
+                       Expect(err).ShouldNot(HaveOccurred(), "creating initial 
GatewayProxy")
+
+                       time.Sleep(2 * time.Second)
+
+                       err = 
s.CreateResourceFromString(fmt.Sprintf(gatewayProxyTemplate, conflictingProxy, 
serviceName, secretName))
+                       Expect(err).Should(HaveOccurred(), "expecting conflict 
for duplicated GatewayProxy")
+                       Expect(err.Error()).To(ContainSubstring("gateway proxy 
configuration conflict"))
+                       
Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("%s/%s", s.Namespace(), 
conflictingProxy)))
+                       
Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("%s/%s", s.Namespace(), 
initialProxy)))
+                       Expect(err.Error()).To(ContainSubstring("Service"))
+                       Expect(err.Error()).To(ContainSubstring("AdminKey 
secret"))
+
+                       Expect(s.DeleteResource("GatewayProxy", 
initialProxy)).ShouldNot(HaveOccurred())
+                       Expect(s.DeleteResource("Service", 
serviceName)).ShouldNot(HaveOccurred())
+                       Expect(s.DeleteResource("Secret", 
secretName)).ShouldNot(HaveOccurred())
+               })
+
+               It("should reject GatewayProxy that overlaps endpoints when 
sharing inline AdminKey value", func() {
+                       gatewayProxyTemplate := `
+apiVersion: apisix.apache.org/v1alpha1
+kind: GatewayProxy
+metadata:
+  name: %s
+spec:
+  provider:
+    type: ControlPlane
+    controlPlane:
+      endpoints:
+      - %s
+      - %s
+      auth:
+        type: AdminKey
+        adminKey:
+          value: "%s"
+`
+
+                       existingProxy := "gatewayproxy-inline-primary"
+                       conflictingProxy := "gatewayproxy-inline-conflict"
+                       endpointA := "https://127.0.0.1:9443";
+                       endpointB := "https://10.0.0.1:9443";
+                       endpointC := "https://192.168.0.1:9443";
+                       inlineKey := "inline-credential"
+
+                       err := 
s.CreateResourceFromString(fmt.Sprintf(gatewayProxyTemplate, existingProxy, 
endpointA, endpointB, inlineKey))
+                       Expect(err).ShouldNot(HaveOccurred(), "creating 
GatewayProxy with inline AdminKey")
+
+                       time.Sleep(2 * time.Second)
+
+                       err = 
s.CreateResourceFromString(fmt.Sprintf(gatewayProxyTemplate, conflictingProxy, 
endpointB, endpointC, inlineKey))
+                       Expect(err).Should(HaveOccurred(), "expecting conflict 
for overlapping endpoints with shared AdminKey")
+                       Expect(err.Error()).To(ContainSubstring("gateway proxy 
configuration conflict"))
+                       
Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("%s/%s", s.Namespace(), 
conflictingProxy)))
+                       
Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("%s/%s", s.Namespace(), 
existingProxy)))
+                       Expect(err.Error()).To(ContainSubstring("control plane 
endpoints"))
+                       Expect(err.Error()).To(ContainSubstring("inline 
AdminKey value"))
+               })
+
+               It("should reject GatewayProxy update that creates conflict 
with another GatewayProxy", func() {
+                       serviceTemplate := `
+apiVersion: v1
+kind: Service
+metadata:
+  name: %s
+spec:
+  selector:
+    app: dummy-control-plane
+  ports:
+  - name: admin
+    port: 9180
+    targetPort: 9180
+`
+                       secretTemplate := `
+apiVersion: v1
+kind: Secret
+metadata:
+  name: %s
+type: Opaque
+stringData:
+  %s: %s
+`
+                       sharedServiceName := 
"gatewayproxy-update-shared-service"
+                       sharedSecretName := "gatewayproxy-update-shared-secret"
+                       uniqueServiceName := 
"gatewayproxy-update-unique-service"
+                       proxyA := "gatewayproxy-update-a"
+                       proxyB := "gatewayproxy-update-b"
+
+                       
Expect(s.CreateResourceFromString(fmt.Sprintf(serviceTemplate, 
sharedServiceName))).ShouldNot(HaveOccurred(), "creating shared Service")
+                       
Expect(s.CreateResourceFromString(fmt.Sprintf(serviceTemplate, 
uniqueServiceName))).ShouldNot(HaveOccurred(), "creating unique Service")
+                       
Expect(s.CreateResourceFromString(fmt.Sprintf(secretTemplate, sharedSecretName, 
"token", "value"))).ShouldNot(HaveOccurred(), "creating shared Secret")
+
+                       err := 
s.CreateResourceFromString(fmt.Sprintf(gatewayProxyTemplate, proxyA, 
sharedServiceName, sharedSecretName))
+                       Expect(err).ShouldNot(HaveOccurred(), "creating 
GatewayProxy A with shared Service and Secret")
+
+                       time.Sleep(2 * time.Second)
+
+                       err = 
s.CreateResourceFromString(fmt.Sprintf(gatewayProxyTemplate, proxyB, 
uniqueServiceName, sharedSecretName))
+                       Expect(err).ShouldNot(HaveOccurred(), "creating 
GatewayProxy B with unique Service but same Secret")
+
+                       time.Sleep(2 * time.Second)
+
+                       By("updating GatewayProxy B to use the same Service as 
GatewayProxy A, causing conflict")
+                       err = 
s.CreateResourceFromString(fmt.Sprintf(gatewayProxyTemplate, proxyB, 
sharedServiceName, sharedSecretName))
+                       Expect(err).Should(HaveOccurred(), "expecting conflict 
when updating to same Service")
+                       Expect(err.Error()).To(ContainSubstring("gateway proxy 
configuration conflict"))
+                       
Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("%s/%s", s.Namespace(), 
proxyA)))
+                       
Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("%s/%s", s.Namespace(), 
proxyB)))
+               })
+       })
 })

Reply via email to