This is an automated email from the ASF dual-hosted git repository.
zhangjintao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git
The following commit(s) were added to refs/heads/master by this push:
new a1ef639 feat: add ApisixPluginConfig controller loop and e2e test
case (#815)
a1ef639 is described below
commit a1ef63963c5ee9f4b225e5e663ec060afd4da2f8
Author: nevercase <[email protected]>
AuthorDate: Wed Dec 29 22:20:27 2021 +0800
feat: add ApisixPluginConfig controller loop and e2e test case (#815)
Co-authored-by: Jintao Zhang <[email protected]>
---
pkg/apisix/apisix.go | 2 +
pkg/apisix/cache/memdb.go | 17 +
pkg/apisix/cache/memdb_test.go | 20 +-
pkg/apisix/cache/schema.go | 6 +
pkg/apisix/cluster.go | 20 +
pkg/apisix/cluster_test.go | 9 +
pkg/apisix/nonexistentclient.go | 4 +
pkg/ingress/apisix_pluginconfig.go | 329 +++++++++++++
pkg/ingress/apisix_route.go | 53 +-
pkg/ingress/compare.go | 71 ++-
pkg/ingress/controller.go | 15 +-
pkg/ingress/ingress.go | 17 +-
pkg/ingress/manifest.go | 91 +++-
pkg/ingress/manifest_test.go | 83 ++++
pkg/ingress/status.go | 17 +
pkg/kube/apisix/apis/config/v2beta3/types.go | 15 +-
.../apis/config/v2beta3/zz_generated.deepcopy.go | 2 +-
pkg/kube/apisix_plugin_config.go | 132 +++++
pkg/kube/apisix_route.go | 6 +-
pkg/kube/translation/apisix_pluginconfig.go | 64 +++
pkg/kube/translation/apisix_pluginconfig_test.go | 114 +++++
pkg/kube/translation/apisix_route.go | 76 ++-
pkg/kube/translation/apisix_route_test.go | 14 +-
pkg/kube/translation/context.go | 21 +-
pkg/kube/translation/context_test.go | 22 +-
pkg/kube/translation/ingress.go | 65 ++-
pkg/kube/translation/ingress_test.go | 44 +-
pkg/kube/translation/translator.go | 22 +-
pkg/types/apisix/v1/types.go | 31 +-
samples/deploy/crd/v1/ApisixPluginConfig.yaml | 1 +
samples/deploy/crd/v1/ApisixRoute.yaml | 3 +
samples/deploy/rbac/apisix_view_clusterrole.yaml | 4 +-
test/e2e/ingress/resourcepushing.go | 45 +-
test/e2e/plugins/plugin_config.go | 546 +++++++++++++++++++++
test/e2e/scaffold/ingress.go | 4 +-
test/e2e/scaffold/k8s.go | 33 ++
36 files changed, 1887 insertions(+), 131 deletions(-)
diff --git a/pkg/apisix/apisix.go b/pkg/apisix/apisix.go
index 838b286..d8cfb2d 100644
--- a/pkg/apisix/apisix.go
+++ b/pkg/apisix/apisix.go
@@ -59,6 +59,8 @@ type Cluster interface {
HealthCheck(context.Context) error
// Plugin returns a Plugin interface that can operate Plugin resources.
Plugin() Plugin
+ // PluginConfig returns a PluginConfig interface that can operate
PluginConfig resources.
+ PluginConfig() PluginConfig
// Schema returns a Schema interface that can fetch schema of APISIX
objects.
Schema() Schema
}
diff --git a/pkg/apisix/cache/memdb.go b/pkg/apisix/cache/memdb.go
index 4557d13..bf21818 100644
--- a/pkg/apisix/cache/memdb.go
+++ b/pkg/apisix/cache/memdb.go
@@ -310,6 +310,9 @@ func (c *dbCache) DeleteSchema(schema *v1.Schema) error {
}
func (c *dbCache) DeletePluginConfig(pc *v1.PluginConfig) error {
+ if err := c.checkPluginConfigReference(pc); err != nil {
+ return err
+ }
return c.delete("plugin_config", pc)
}
@@ -347,3 +350,17 @@ func (c *dbCache) checkUpstreamReference(u *v1.Upstream)
error {
}
return nil
}
+
+func (c *dbCache) checkPluginConfigReference(u *v1.PluginConfig) error {
+ // PluginConfig is referenced by Route.
+ txn := c.db.Txn(false)
+ defer txn.Abort()
+ obj, err := txn.First("route", "plugin_config_id", u.ID)
+ if err != nil && err != memdb.ErrNotFound {
+ return err
+ }
+ if obj != nil {
+ return ErrStillInUse
+ }
+ return nil
+}
diff --git a/pkg/apisix/cache/memdb_test.go b/pkg/apisix/cache/memdb_test.go
index 25b731f..b6ee313 100644
--- a/pkg/apisix/cache/memdb_test.go
+++ b/pkg/apisix/cache/memdb_test.go
@@ -18,6 +18,7 @@ package cache
import (
"testing"
+ "github.com/hashicorp/go-memdb"
"github.com/stretchr/testify/assert"
v1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
@@ -183,7 +184,8 @@ func TestMemDBCacheReference(t *testing.T) {
Name: "route",
ID: "1",
},
- UpstreamId: "1",
+ UpstreamId: "1",
+ PluginConfigId: "1",
}
u := &v1.Upstream{
Metadata: v1.Metadata{
@@ -201,6 +203,18 @@ func TestMemDBCacheReference(t *testing.T) {
ID: "1",
UpstreamId: "2",
}
+ pc := &v1.PluginConfig{
+ Metadata: v1.Metadata{
+ ID: "1",
+ Name: "pluginConfig",
+ },
+ }
+ pc2 := &v1.PluginConfig{
+ Metadata: v1.Metadata{
+ ID: "2",
+ Name: "pluginConfig",
+ },
+ }
db, err := NewMemDBCache()
assert.Nil(t, err, "NewMemDBCache")
@@ -208,13 +222,17 @@ func TestMemDBCacheReference(t *testing.T) {
assert.Nil(t, db.InsertUpstream(u))
assert.Nil(t, db.InsertStreamRoute(sr))
assert.Nil(t, db.InsertUpstream(u2))
+ assert.Nil(t, db.InsertPluginConfig(pc))
assert.Error(t, ErrStillInUse, db.DeleteUpstream(u))
assert.Error(t, ErrStillInUse, db.DeleteUpstream(u2))
+ assert.Error(t, ErrStillInUse, db.DeletePluginConfig(pc))
+ assert.Equal(t, memdb.ErrNotFound, db.DeletePluginConfig(pc2))
assert.Nil(t, db.DeleteRoute(r))
assert.Nil(t, db.DeleteUpstream(u))
assert.Nil(t, db.DeleteStreamRoute(sr))
assert.Nil(t, db.DeleteUpstream(u2))
+ assert.Nil(t, db.DeletePluginConfig(pc))
}
func TestMemDBCacheStreamRoute(t *testing.T) {
diff --git a/pkg/apisix/cache/schema.go b/pkg/apisix/cache/schema.go
index 66c3588..e4ef348 100644
--- a/pkg/apisix/cache/schema.go
+++ b/pkg/apisix/cache/schema.go
@@ -42,6 +42,12 @@ var (
Indexer:
&memdb.StringFieldIndex{Field: "UpstreamId"},
AllowMissing: true,
},
+ "plugin_config_id": {
+ Name:
"plugin_config_id",
+ Unique: false,
+ Indexer:
&memdb.StringFieldIndex{Field: "PluginConfigId"},
+ AllowMissing: true,
+ },
},
},
"upstream": {
diff --git a/pkg/apisix/cluster.go b/pkg/apisix/cluster.go
index 0106dec..4a5dbd7 100644
--- a/pkg/apisix/cluster.go
+++ b/pkg/apisix/cluster.go
@@ -233,6 +233,11 @@ func (c *cluster) syncCacheOnce(ctx context.Context)
(bool, error) {
log.Errorf("failed to list consumers in APISIX: %s", err)
return false, err
}
+ pluginConfigs, err := c.pluginConfig.List(ctx)
+ if err != nil {
+ log.Errorf("failed to list plugin_configs in APISIX: %s", err)
+ return false, err
+ }
for _, r := range routes {
if err := c.cache.InsertRoute(r); err != nil {
@@ -293,6 +298,16 @@ func (c *cluster) syncCacheOnce(ctx context.Context)
(bool, error) {
)
}
}
+ for _, u := range pluginConfigs {
+ if err := c.cache.InsertPluginConfig(u); err != nil {
+ log.Errorw("failed to insert pluginConfig to cache",
+ zap.String("pluginConfig", u.ID),
+ zap.String("cluster", c.name),
+ zap.String("error", err.Error()),
+ )
+ return false, err
+ }
+ }
return true, nil
}
@@ -433,6 +448,11 @@ func (c *cluster) Plugin() Plugin {
return c.plugin
}
+// PluginConfig implements Cluster.PluginConfig method.
+func (c *cluster) PluginConfig() PluginConfig {
+ return c.pluginConfig
+}
+
// Schema implements Cluster.Schema method.
func (c *cluster) Schema() Schema {
return c.schema
diff --git a/pkg/apisix/cluster_test.go b/pkg/apisix/cluster_test.go
index cd6f323..51d970f 100644
--- a/pkg/apisix/cluster_test.go
+++ b/pkg/apisix/cluster_test.go
@@ -92,4 +92,13 @@ func TestNonExistentCluster(t *testing.T) {
assert.Equal(t, ErrClusterNotExist, err)
err =
apisix.Cluster("non-existent-cluster").SSL().Delete(context.Background(),
&v1.Ssl{})
assert.Equal(t, ErrClusterNotExist, err)
+
+ _, err =
apisix.Cluster("non-existent-cluster").PluginConfig().List(context.Background())
+ assert.Equal(t, ErrClusterNotExist, err)
+ _, err =
apisix.Cluster("non-existent-cluster").PluginConfig().Create(context.Background(),
&v1.PluginConfig{})
+ assert.Equal(t, ErrClusterNotExist, err)
+ _, err =
apisix.Cluster("non-existent-cluster").PluginConfig().Update(context.Background(),
&v1.PluginConfig{})
+ assert.Equal(t, ErrClusterNotExist, err)
+ err =
apisix.Cluster("non-existent-cluster").PluginConfig().Delete(context.Background(),
&v1.PluginConfig{})
+ assert.Equal(t, ErrClusterNotExist, err)
}
diff --git a/pkg/apisix/nonexistentclient.go b/pkg/apisix/nonexistentclient.go
index 445ff62..c844480 100644
--- a/pkg/apisix/nonexistentclient.go
+++ b/pkg/apisix/nonexistentclient.go
@@ -268,6 +268,10 @@ func (nc *nonExistentCluster) Plugin() Plugin {
return nc.plugin
}
+func (nc *nonExistentCluster) PluginConfig() PluginConfig {
+ return nc.pluginConfig
+}
+
func (nc *nonExistentCluster) Schema() Schema {
return nc.schema
}
diff --git a/pkg/ingress/apisix_pluginconfig.go
b/pkg/ingress/apisix_pluginconfig.go
new file mode 100644
index 0000000..6b79299
--- /dev/null
+++ b/pkg/ingress/apisix_pluginconfig.go
@@ -0,0 +1,329 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package ingress
+
+import (
+ "context"
+ "time"
+
+ "go.uber.org/zap"
+ v1 "k8s.io/api/core/v1"
+ k8serrors "k8s.io/apimachinery/pkg/api/errors"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/tools/cache"
+ "k8s.io/client-go/util/workqueue"
+
+ "github.com/apache/apisix-ingress-controller/pkg/kube"
+ "github.com/apache/apisix-ingress-controller/pkg/kube/translation"
+ "github.com/apache/apisix-ingress-controller/pkg/log"
+ "github.com/apache/apisix-ingress-controller/pkg/types"
+)
+
+type apisixPluginConfigController struct {
+ controller *Controller
+ workqueue workqueue.RateLimitingInterface
+ workers int
+}
+
+func (c *Controller) newApisixPluginConfigController()
*apisixPluginConfigController {
+ ctl := &apisixPluginConfigController{
+ controller: c,
+ workqueue:
workqueue.NewNamedRateLimitingQueue(workqueue.NewItemFastSlowRateLimiter(1*time.Second,
60*time.Second, 5), "ApisixPluginConfig"),
+ workers: 1,
+ }
+ c.apisixPluginConfigInformer.AddEventHandler(
+ cache.ResourceEventHandlerFuncs{
+ AddFunc: ctl.onAdd,
+ UpdateFunc: ctl.onUpdate,
+ DeleteFunc: ctl.onDelete,
+ },
+ )
+ return ctl
+}
+
+func (c *apisixPluginConfigController) run(ctx context.Context) {
+ log.Info("ApisixPluginConfig controller started")
+ defer log.Info("ApisixPluginConfig controller exited")
+ defer c.workqueue.ShutDown()
+
+ ok := cache.WaitForCacheSync(ctx.Done(),
c.controller.apisixPluginConfigInformer.HasSynced)
+ if !ok {
+ log.Error("cache sync failed")
+ return
+ }
+
+ for i := 0; i < c.workers; i++ {
+ go c.runWorker(ctx)
+ }
+ <-ctx.Done()
+}
+
+func (c *apisixPluginConfigController) runWorker(ctx context.Context) {
+ for {
+ obj, quit := c.workqueue.Get()
+ if quit {
+ return
+ }
+ err := c.sync(ctx, obj.(*types.Event))
+ c.workqueue.Done(obj)
+ c.handleSyncErr(obj, err)
+ }
+}
+
+func (c *apisixPluginConfigController) sync(ctx context.Context, ev
*types.Event) error {
+ obj := ev.Object.(kube.ApisixPluginConfigEvent)
+ namespace, name, err := cache.SplitMetaNamespaceKey(obj.Key)
+ if err != nil {
+ log.Errorf("invalid resource key: %s", obj.Key)
+ return err
+ }
+ var (
+ apc kube.ApisixPluginConfig
+ tctx *translation.TranslateContext
+ )
+ switch obj.GroupVersion {
+ case kube.ApisixPluginConfigV2beta3:
+ apc, err =
c.controller.apisixPluginConfigLister.V2beta3(namespace, name)
+ }
+ if err != nil {
+ if !k8serrors.IsNotFound(err) {
+ log.Errorw("failed to get ApisixPluginConfig",
+ zap.String("version", obj.GroupVersion),
+ zap.String("key", obj.Key),
+ zap.Error(err),
+ )
+ return err
+ }
+
+ if ev.Type != types.EventDelete {
+ log.Warnw("ApisixPluginConfig was deleted before it can
be delivered",
+ zap.String("key", obj.Key),
+ zap.String("version", obj.GroupVersion),
+ )
+ return nil
+ }
+ }
+ if ev.Type == types.EventDelete {
+ if apc != nil {
+ // We still find the resource while we are processing
the DELETE event,
+ // that means object with same namespace and name was
created, discarding
+ // this stale DELETE event.
+ log.Warnw("discard the stale ApisixPluginConfig delete
event since the resource still exists",
+ zap.String("key", obj.Key),
+ )
+ return nil
+ }
+ apc = ev.Tombstone.(kube.ApisixPluginConfig)
+ }
+
+ switch obj.GroupVersion {
+ case kube.ApisixPluginConfigV2beta3:
+ if ev.Type != types.EventDelete {
+ tctx, err =
c.controller.translator.TranslatePluginConfigV2beta3(apc.V2beta3())
+ } else {
+ tctx, err =
c.controller.translator.TranslatePluginConfigV2beta3NotStrictly(apc.V2beta3())
+ }
+ if err != nil {
+ log.Errorw("failed to translate ApisixPluginConfig
v2beta3",
+ zap.Error(err),
+ zap.Any("object", apc),
+ )
+ return err
+ }
+ }
+
+ log.Debugw("translated ApisixPluginConfig",
+ zap.Any("pluginConfigs", tctx.PluginConfigs),
+ )
+
+ m := &manifest{
+ pluginConfigs: tctx.PluginConfigs,
+ }
+
+ var (
+ added *manifest
+ updated *manifest
+ deleted *manifest
+ )
+
+ if ev.Type == types.EventDelete {
+ deleted = m
+ } else if ev.Type == types.EventAdd {
+ added = m
+ } else {
+ var oldCtx *translation.TranslateContext
+ switch obj.GroupVersion {
+ case kube.ApisixPluginConfigV2beta3:
+ oldCtx, err =
c.controller.translator.TranslatePluginConfigV2beta3(obj.OldObject.V2beta3())
+ }
+ if err != nil {
+ log.Errorw("failed to translate old ApisixPluginConfig",
+ zap.String("version", obj.GroupVersion),
+ zap.String("event", "update"),
+ zap.Error(err),
+ zap.Any("ApisixPluginConfig", apc),
+ )
+ return err
+ }
+
+ om := &manifest{
+ pluginConfigs: oldCtx.PluginConfigs,
+ }
+ added, updated, deleted = m.diff(om)
+ }
+
+ return c.controller.syncManifests(ctx, added, updated, deleted)
+}
+
+func (c *apisixPluginConfigController) handleSyncErr(obj interface{},
errOrigin error) {
+ ev := obj.(*types.Event)
+ event := ev.Object.(kube.ApisixPluginConfigEvent)
+ namespace, name, errLocal := cache.SplitMetaNamespaceKey(event.Key)
+ if errLocal != nil {
+ log.Errorf("invalid resource key: %s", event.Key)
+ c.controller.MetricsCollector.IncrSyncOperation("PluginConfig",
"failure")
+ return
+ }
+ var apc kube.ApisixPluginConfig
+ switch event.GroupVersion {
+ case kube.ApisixPluginConfigV2beta3:
+ apc, errLocal =
c.controller.apisixPluginConfigLister.V2beta3(namespace, name)
+ }
+ if errOrigin == nil {
+ if ev.Type != types.EventDelete {
+ if errLocal == nil {
+ switch apc.GroupVersion() {
+ case kube.ApisixPluginConfigV2beta3:
+
c.controller.recorderEvent(apc.V2beta3(), v1.EventTypeNormal, _resourceSynced,
nil)
+
c.controller.recordStatus(apc.V2beta3(), _resourceSynced, nil,
metav1.ConditionTrue, apc.V2beta3().GetGeneration())
+ }
+ } else {
+ log.Errorw("failed list ApisixPluginConfig",
+ zap.Error(errLocal),
+ zap.String("name", name),
+ zap.String("namespace", namespace),
+ )
+ }
+ }
+ c.workqueue.Forget(obj)
+ c.controller.MetricsCollector.IncrSyncOperation("PluginConfig",
"success")
+ return
+ }
+ log.Warnw("sync ApisixPluginConfig failed, will retry",
+ zap.Any("object", obj),
+ zap.Error(errOrigin),
+ )
+ if errLocal == nil {
+ switch apc.GroupVersion() {
+ case kube.ApisixPluginConfigV2beta3:
+ c.controller.recorderEvent(apc.V2beta3(),
v1.EventTypeWarning, _resourceSyncAborted, errOrigin)
+ c.controller.recordStatus(apc.V2beta3(),
_resourceSyncAborted, errOrigin, metav1.ConditionFalse,
apc.V2beta3().GetGeneration())
+ }
+ } else {
+ log.Errorw("failed list ApisixPluginConfig",
+ zap.Error(errLocal),
+ zap.String("name", name),
+ zap.String("namespace", namespace),
+ )
+ }
+ c.workqueue.AddRateLimited(obj)
+ c.controller.MetricsCollector.IncrSyncOperation("PluginConfig",
"failure")
+}
+
+func (c *apisixPluginConfigController) onAdd(obj interface{}) {
+ key, err := cache.MetaNamespaceKeyFunc(obj)
+ if err != nil {
+ log.Errorf("found ApisixPluginConfig resource with bad meta
namespace key: %s", err)
+ return
+ }
+ if !c.controller.namespaceWatching(key) {
+ return
+ }
+ log.Debugw("ApisixPluginConfig add event arrived",
+ zap.Any("object", obj))
+
+ apc := kube.MustNewApisixPluginConfig(obj)
+ c.workqueue.Add(&types.Event{
+ Type: types.EventAdd,
+ Object: kube.ApisixPluginConfigEvent{
+ Key: key,
+ GroupVersion: apc.GroupVersion(),
+ },
+ })
+
+ c.controller.MetricsCollector.IncrEvents("PluginConfig", "add")
+}
+
+func (c *apisixPluginConfigController) onUpdate(oldObj, newObj interface{}) {
+ prev := kube.MustNewApisixPluginConfig(oldObj)
+ curr := kube.MustNewApisixPluginConfig(newObj)
+ if prev.ResourceVersion() >= curr.ResourceVersion() {
+ return
+ }
+ key, err := cache.MetaNamespaceKeyFunc(newObj)
+ if err != nil {
+ log.Errorf("found ApisixPluginConfig resource with bad meta
namespace key: %s", err)
+ return
+ }
+ if !c.controller.namespaceWatching(key) {
+ return
+ }
+ log.Debugw("ApisixPluginConfig update event arrived",
+ zap.Any("new object", curr),
+ zap.Any("old object", prev),
+ )
+ c.workqueue.Add(&types.Event{
+ Type: types.EventUpdate,
+ Object: kube.ApisixPluginConfigEvent{
+ Key: key,
+ GroupVersion: curr.GroupVersion(),
+ OldObject: prev,
+ },
+ })
+
+ c.controller.MetricsCollector.IncrEvents("PluginConfig", "update")
+}
+
+func (c *apisixPluginConfigController) onDelete(obj interface{}) {
+ apc, err := kube.NewApisixPluginConfig(obj)
+ if err != nil {
+ tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
+ if !ok {
+ return
+ }
+ apc = kube.MustNewApisixPluginConfig(tombstone)
+ }
+ key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
+ if err != nil {
+ log.Errorf("found ApisixPluginConfig resource with bad meta
namesapce key: %s", err)
+ return
+ }
+ if !c.controller.namespaceWatching(key) {
+ return
+ }
+ log.Debugw("ApisixPluginConfig delete event arrived",
+ zap.Any("final state", apc),
+ )
+ c.workqueue.Add(&types.Event{
+ Type: types.EventDelete,
+ Object: kube.ApisixPluginConfigEvent{
+ Key: key,
+ GroupVersion: apc.GroupVersion(),
+ },
+ Tombstone: apc,
+ })
+
+ c.controller.MetricsCollector.IncrEvents("PluginConfig", "delete")
+}
diff --git a/pkg/ingress/apisix_route.go b/pkg/ingress/apisix_route.go
index b46c99d..8d601a4 100644
--- a/pkg/ingress/apisix_route.go
+++ b/pkg/ingress/apisix_route.go
@@ -26,9 +26,11 @@ import (
"k8s.io/client-go/util/workqueue"
"github.com/apache/apisix-ingress-controller/pkg/kube"
+
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2beta3"
"github.com/apache/apisix-ingress-controller/pkg/kube/translation"
"github.com/apache/apisix-ingress-controller/pkg/log"
"github.com/apache/apisix-ingress-controller/pkg/types"
+ apisixv1
"github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
)
type apisixRouteController struct {
@@ -90,8 +92,9 @@ func (c *apisixRouteController) sync(ctx context.Context, ev
*types.Event) error
return err
}
var (
- ar kube.ApisixRoute
- tctx *translation.TranslateContext
+ ar kube.ApisixRoute
+ replaced *v2beta3.ApisixRoute
+ tctx *translation.TranslateContext
)
switch obj.GroupVersion {
case kube.ApisixRouteV2beta1:
@@ -161,7 +164,9 @@ func (c *apisixRouteController) sync(ctx context.Context,
ev *types.Event) error
}
case kube.ApisixRouteV2beta3:
if ev.Type != types.EventDelete {
- tctx, err =
c.controller.translator.TranslateRouteV2beta3(ar.V2beta3())
+ if replaced, err =
c.replacePluginNameWithIdIfNotEmptyV2beta3(ctx, ar.V2beta3()); err == nil {
+ tctx, err =
c.controller.translator.TranslateRouteV2beta3(replaced)
+ }
} else {
tctx, err =
c.controller.translator.TranslateRouteV2beta3NotStrictly(ar.V2beta3())
}
@@ -178,12 +183,14 @@ func (c *apisixRouteController) sync(ctx context.Context,
ev *types.Event) error
zap.Any("routes", tctx.Routes),
zap.Any("upstreams", tctx.Upstreams),
zap.Any("apisix_route", ar),
+ zap.Any("pluginConfigs", tctx.PluginConfigs),
)
m := &manifest{
- routes: tctx.Routes,
- upstreams: tctx.Upstreams,
- streamRoutes: tctx.StreamRoutes,
+ routes: tctx.Routes,
+ upstreams: tctx.Upstreams,
+ streamRoutes: tctx.StreamRoutes,
+ pluginConfigs: tctx.PluginConfigs,
}
var (
@@ -204,7 +211,9 @@ func (c *apisixRouteController) sync(ctx context.Context,
ev *types.Event) error
case kube.ApisixRouteV2beta2:
oldCtx, err =
c.controller.translator.TranslateRouteV2beta2(obj.OldObject.V2beta2())
case kube.ApisixRouteV2beta3:
- oldCtx, err =
c.controller.translator.TranslateRouteV2beta3(obj.OldObject.V2beta3())
+ if replaced, err =
c.replacePluginNameWithIdIfNotEmptyV2beta3(ctx, obj.OldObject.V2beta3()); err
== nil {
+ oldCtx, err =
c.controller.translator.TranslateRouteV2beta3(replaced)
+ }
}
if err != nil {
log.Errorw("failed to translate old ApisixRoute",
@@ -217,9 +226,10 @@ func (c *apisixRouteController) sync(ctx context.Context,
ev *types.Event) error
}
om := &manifest{
- routes: oldCtx.Routes,
- upstreams: oldCtx.Upstreams,
- streamRoutes: oldCtx.StreamRoutes,
+ routes: oldCtx.Routes,
+ upstreams: oldCtx.Upstreams,
+ streamRoutes: oldCtx.StreamRoutes,
+ pluginConfigs: oldCtx.PluginConfigs,
}
added, updated, deleted = m.diff(om)
}
@@ -227,6 +237,29 @@ func (c *apisixRouteController) sync(ctx context.Context,
ev *types.Event) error
return c.controller.syncManifests(ctx, added, updated, deleted)
}
+func (c *apisixRouteController) replacePluginNameWithIdIfNotEmptyV2beta3(ctx
context.Context, in *v2beta3.ApisixRoute) (*v2beta3.ApisixRoute, error) {
+ clusterName := c.controller.cfg.APISIX.DefaultClusterName
+ news := make([]v2beta3.ApisixRouteHTTP, 0)
+ for _, v := range in.Spec.HTTP {
+ pluginConfigId := ""
+ if v.PluginConfigName != "" {
+ pc, err :=
c.controller.apisix.Cluster(clusterName).PluginConfig().Get(ctx,
apisixv1.ComposePluginConfigName(in.Namespace, v.PluginConfigName))
+ if err != nil {
+
log.Errorw("replacePluginNameWithIdIfNotEmptyV2beta3 error: plugin_config not
found",
+ zap.String("name",
apisixv1.ComposePluginConfigName(in.Namespace, v.PluginConfigName)),
+ zap.Any("obj", in),
+ zap.Error(err))
+ } else {
+ pluginConfigId = pc.ID
+ }
+ }
+ v.PluginConfigName = pluginConfigId
+ news = append(news, v)
+ }
+ in.Spec.HTTP = news
+ return in, nil
+}
+
func (c *apisixRouteController) handleSyncErr(obj interface{}, errOrigin
error) {
ev := obj.(*types.Event)
event := ev.Object.(kube.ApisixRouteEvent)
diff --git a/pkg/ingress/compare.go b/pkg/ingress/compare.go
index 227d284..fa870b2 100644
--- a/pkg/ingress/compare.go
+++ b/pkg/ingress/compare.go
@@ -31,18 +31,20 @@ import (
// cc
https://github.com/apache/apisix-ingress-controller/pull/742#discussion_r757197791
func (c *Controller) CompareResources(ctx context.Context) error {
var (
- wg sync.WaitGroup
- routeMapK8S = new(sync.Map)
- streamRouteMapK8S = new(sync.Map)
- upstreamMapK8S = new(sync.Map)
- sslMapK8S = new(sync.Map)
- consumerMapK8S = new(sync.Map)
-
- routeMapA6 = make(map[string]string)
- streamRouteMapA6 = make(map[string]string)
- upstreamMapA6 = make(map[string]string)
- sslMapA6 = make(map[string]string)
- consumerMapA6 = make(map[string]string)
+ wg sync.WaitGroup
+ routeMapK8S = new(sync.Map)
+ streamRouteMapK8S = new(sync.Map)
+ upstreamMapK8S = new(sync.Map)
+ sslMapK8S = new(sync.Map)
+ consumerMapK8S = new(sync.Map)
+ pluginConfigMapK8S = new(sync.Map)
+
+ routeMapA6 = make(map[string]string)
+ streamRouteMapA6 = make(map[string]string)
+ upstreamMapA6 = make(map[string]string)
+ sslMapA6 = make(map[string]string)
+ consumerMapA6 = make(map[string]string)
+ pluginConfigMapA6 = make(map[string]string)
)
// watchingNamespace == nil means to monitor all namespaces
if !validation.HasValueInSyncMap(c.watchingNamespace) {
@@ -94,11 +96,15 @@ func (c *Controller) CompareResources(ctx context.Context)
error {
for _, ssl := range tc.SSL {
sslMapK8S.Store(ssl.ID,
ssl.ID)
}
+ // pluginConfigs
+ for _, pluginConfig := range
tc.PluginConfigs {
+
pluginConfigMapK8S.Store(pluginConfig.ID, pluginConfig.ID)
+ }
}
}
}
- // todo ApisixUpstream
- // ApisixUpstream should be synced with ApisixRoute
resource
+ // todo ApisixUpstream and ApisixPluginConfig
+ // ApisixUpstream and ApisixPluginConfig should be
synced with ApisixRoute resource
// ApisixSSL
retSSL, err :=
c.kubeClient.APISIXClient.ApisixV2beta3().ApisixTlses(ns).List(ctx, opts)
@@ -153,18 +159,23 @@ func (c *Controller) CompareResources(ctx
context.Context) error {
if err := c.listConsumerCache(ctx, consumerMapA6); err != nil {
return err
}
+ if err := c.listPluginConfigCache(ctx, pluginConfigMapA6); err != nil {
+ return err
+ }
// 3.compare
- routeReult := findRedundant(routeMapA6, routeMapK8S)
- streamRouteReult := findRedundant(streamRouteMapA6, streamRouteMapK8S)
- upstreamReult := findRedundant(upstreamMapA6, upstreamMapK8S)
- sslReult := findRedundant(sslMapA6, sslMapK8S)
- consuemrReult := findRedundant(consumerMapA6, consumerMapK8S)
+ routeResult := findRedundant(routeMapA6, routeMapK8S)
+ streamRouteResult := findRedundant(streamRouteMapA6, streamRouteMapK8S)
+ upstreamResult := findRedundant(upstreamMapA6, upstreamMapK8S)
+ sslResult := findRedundant(sslMapA6, sslMapK8S)
+ consumerResult := findRedundant(consumerMapA6, consumerMapK8S)
+ pluginConfigResult := findRedundant(pluginConfigMapA6,
pluginConfigMapK8S)
// 4.warn
- warnRedundantResources(routeReult, "route")
- warnRedundantResources(streamRouteReult, "streamRoute")
- warnRedundantResources(upstreamReult, "upstream")
- warnRedundantResources(sslReult, "ssl")
- warnRedundantResources(consuemrReult, "consumer")
+ warnRedundantResources(routeResult, "route")
+ warnRedundantResources(streamRouteResult, "streamRoute")
+ warnRedundantResources(upstreamResult, "upstream")
+ warnRedundantResources(sslResult, "ssl")
+ warnRedundantResources(consumerResult, "consumer")
+ warnRedundantResources(pluginConfigResult, "pluginConfig")
return nil
}
@@ -247,3 +258,15 @@ func (c *Controller) listConsumerCache(ctx
context.Context, consumerMapA6 map[st
}
return nil
}
+
+func (c *Controller) listPluginConfigCache(ctx context.Context,
pluginConfigMapA6 map[string]string) error {
+ pluginConfigInA6, err :=
c.apisix.Cluster(c.cfg.APISIX.DefaultClusterName).PluginConfig().List(ctx)
+ if err != nil {
+ return err
+ } else {
+ for _, ra := range pluginConfigInA6 {
+ pluginConfigMapA6[ra.ID] = ra.ID
+ }
+ }
+ return nil
+}
diff --git a/pkg/ingress/controller.go b/pkg/ingress/controller.go
index b7686ee..b519b75 100644
--- a/pkg/ingress/controller.go
+++ b/pkg/ingress/controller.go
@@ -115,6 +115,8 @@ type Controller struct {
apisixClusterConfigInformer cache.SharedIndexInformer
apisixConsumerInformer cache.SharedIndexInformer
apisixConsumerLister listersv2beta3.ApisixConsumerLister
+ apisixPluginConfigInformer cache.SharedIndexInformer
+ apisixPluginConfigLister kube.ApisixPluginConfigLister
gatewayInformer cache.SharedIndexInformer
gatewayLister gatewaylistersv1alpha2.GatewayLister
@@ -132,6 +134,7 @@ type Controller struct {
apisixTlsController *apisixTlsController
apisixClusterConfigController *apisixClusterConfigController
apisixConsumerController *apisixConsumerController
+ apisixPluginConfigController *apisixPluginConfigController
}
// NewController creates an ingress apisix controller object.
@@ -224,6 +227,9 @@ func (c *Controller) initWhenStartLeading() {
c.apisixTlsLister =
apisixFactory.Apisix().V2beta3().ApisixTlses().Lister()
c.apisixClusterConfigLister =
apisixFactory.Apisix().V2beta3().ApisixClusterConfigs().Lister()
c.apisixConsumerLister =
apisixFactory.Apisix().V2beta3().ApisixConsumers().Lister()
+ c.apisixPluginConfigLister = kube.NewApisixPluginConfigLister(
+ apisixFactory.Apisix().V2beta3().ApisixPluginConfigs().Lister(),
+ )
c.translator = translation.NewTranslator(&translation.TranslatorOptions{
PodCache: c.podCache,
@@ -265,6 +271,7 @@ func (c *Controller) initWhenStartLeading() {
c.secretInformer = kubeFactory.Core().V1().Secrets().Informer()
c.apisixTlsInformer =
apisixFactory.Apisix().V2beta3().ApisixTlses().Informer()
c.apisixConsumerInformer =
apisixFactory.Apisix().V2beta3().ApisixConsumers().Informer()
+ c.apisixPluginConfigInformer =
apisixFactory.Apisix().V2beta3().ApisixPluginConfigs().Informer()
if c.cfg.Kubernetes.WatchEndpointSlices {
c.endpointSliceController = c.newEndpointSliceController()
@@ -280,6 +287,7 @@ func (c *Controller) initWhenStartLeading() {
c.apisixTlsController = c.newApisixTlsController()
c.secretController = c.newSecretController()
c.apisixConsumerController = c.newApisixConsumerController()
+ c.apisixPluginConfigController = c.newApisixPluginConfigController()
c.gatewayController = c.newGatewayController()
}
@@ -467,7 +475,6 @@ func (c *Controller) run(ctx context.Context) {
c.ingressInformer.Run(ctx.Done())
})
c.goAttach(func() {
-
c.apisixRouteInformer.Run(ctx.Done())
})
c.goAttach(func() {
@@ -486,6 +493,9 @@ func (c *Controller) run(ctx context.Context) {
c.apisixConsumerInformer.Run(ctx.Done())
})
c.goAttach(func() {
+ c.apisixPluginConfigInformer.Run(ctx.Done())
+ })
+ c.goAttach(func() {
c.namespaceController.run(ctx)
})
c.goAttach(func() {
@@ -530,6 +540,9 @@ func (c *Controller) run(ctx context.Context) {
c.goAttach(func() {
c.apisixConsumerController.run(ctx)
})
+ c.goAttach(func() {
+ c.apisixPluginConfigController.run(ctx)
+ })
c.MetricsCollector.ResetLeader(true)
diff --git a/pkg/ingress/ingress.go b/pkg/ingress/ingress.go
index b4d69a8..b5ffbb6 100644
--- a/pkg/ingress/ingress.go
+++ b/pkg/ingress/ingress.go
@@ -135,17 +135,19 @@ func (c *ingressController) sync(ctx context.Context, ev
*types.Event) error {
return err
}
- log.Debugw("translated ingress resource to a couple of routes and
upstreams",
+ log.Debugw("translated ingress resource to a couple of routes,
upstreams and pluginConfigs",
zap.Any("ingress", ing),
zap.Any("routes", tctx.Routes),
zap.Any("upstreams", tctx.Upstreams),
zap.Any("ssl", tctx.SSL),
+ zap.Any("pluginConfigs", tctx.PluginConfigs),
)
m := &manifest{
- ssl: tctx.SSL,
- routes: tctx.Routes,
- upstreams: tctx.Upstreams,
+ ssl: tctx.SSL,
+ routes: tctx.Routes,
+ upstreams: tctx.Upstreams,
+ pluginConfigs: tctx.PluginConfigs,
}
var (
@@ -169,9 +171,10 @@ func (c *ingressController) sync(ctx context.Context, ev
*types.Event) error {
return err
}
om := &manifest{
- routes: oldCtx.Routes,
- upstreams: oldCtx.Upstreams,
- ssl: oldCtx.SSL,
+ routes: oldCtx.Routes,
+ upstreams: oldCtx.Upstreams,
+ ssl: oldCtx.SSL,
+ pluginConfigs: oldCtx.PluginConfigs,
}
added, updated, deleted = m.diff(om)
}
diff --git a/pkg/ingress/manifest.go b/pkg/ingress/manifest.go
index 2d82a30..bf4edfa 100644
--- a/pkg/ingress/manifest.go
+++ b/pkg/ingress/manifest.go
@@ -140,11 +140,37 @@ func diffStreamRoutes(olds, news []*apisixv1.StreamRoute)
(added, updated, delet
return
}
+func diffPluginConfigs(olds, news []*apisixv1.PluginConfig) (added, updated,
deleted []*apisixv1.PluginConfig) {
+ oldMap := make(map[string]*apisixv1.PluginConfig, len(olds))
+ newMap := make(map[string]*apisixv1.PluginConfig, len(news))
+ for _, sr := range olds {
+ oldMap[sr.ID] = sr
+ }
+ for _, sr := range news {
+ newMap[sr.ID] = sr
+ }
+
+ for _, sr := range news {
+ if ou, ok := oldMap[sr.ID]; !ok {
+ added = append(added, sr)
+ } else if !reflect.DeepEqual(ou, sr) {
+ updated = append(updated, sr)
+ }
+ }
+ for _, sr := range olds {
+ if _, ok := newMap[sr.ID]; !ok {
+ deleted = append(deleted, sr)
+ }
+ }
+ return
+}
+
type manifest struct {
- routes []*apisixv1.Route
- upstreams []*apisixv1.Upstream
- streamRoutes []*apisixv1.StreamRoute
- ssl []*apisixv1.Ssl
+ routes []*apisixv1.Route
+ upstreams []*apisixv1.Upstream
+ streamRoutes []*apisixv1.StreamRoute
+ ssl []*apisixv1.Ssl
+ pluginConfigs []*apisixv1.PluginConfig
}
func (m *manifest) diff(om *manifest) (added, updated, deleted *manifest) {
@@ -152,29 +178,33 @@ func (m *manifest) diff(om *manifest) (added, updated,
deleted *manifest) {
ar, ur, dr := diffRoutes(om.routes, m.routes)
au, uu, du := diffUpstreams(om.upstreams, m.upstreams)
asr, usr, dsr := diffStreamRoutes(om.streamRoutes, m.streamRoutes)
+ apc, upc, dpc := diffPluginConfigs(om.pluginConfigs, m.pluginConfigs)
- if ar != nil || au != nil || asr != nil || sa != nil {
+ if ar != nil || au != nil || asr != nil || sa != nil || apc != nil {
added = &manifest{
- routes: ar,
- upstreams: au,
- streamRoutes: asr,
- ssl: sa,
+ routes: ar,
+ upstreams: au,
+ streamRoutes: asr,
+ ssl: sa,
+ pluginConfigs: apc,
}
}
- if ur != nil || uu != nil || usr != nil || su != nil {
+ if ur != nil || uu != nil || usr != nil || su != nil || upc != nil {
updated = &manifest{
- routes: ur,
- upstreams: uu,
- streamRoutes: usr,
- ssl: su,
+ routes: ur,
+ upstreams: uu,
+ streamRoutes: usr,
+ ssl: su,
+ pluginConfigs: upc,
}
}
- if dr != nil || du != nil || dsr != nil || sd != nil {
+ if dr != nil || du != nil || dsr != nil || sd != nil || dpc != nil {
deleted = &manifest{
- routes: dr,
- upstreams: du,
- streamRoutes: dsr,
- ssl: sd,
+ routes: dr,
+ upstreams: du,
+ streamRoutes: dsr,
+ ssl: sd,
+ pluginConfigs: dpc,
}
}
return
@@ -213,6 +243,19 @@ func (c *Controller) syncManifests(ctx context.Context,
added, updated, deleted
}
}
}
+ for _, pc := range deleted.pluginConfigs {
+ if err :=
c.apisix.Cluster(clusterName).PluginConfig().Delete(ctx, pc); err != nil {
+ // pluginConfig might be referenced by other
routes.
+ if err != cache.ErrStillInUse {
+ merr = multierror.Append(merr, err)
+ } else {
+ log.Infow("plugin_config was referenced
by other routes",
+ zap.String("plugin_config_id",
pc.ID),
+
zap.String("plugin_config_name", pc.Name),
+ )
+ }
+ }
+ }
}
if added != nil {
// Should create upstreams firstly due to the dependencies.
@@ -226,6 +269,11 @@ func (c *Controller) syncManifests(ctx context.Context,
added, updated, deleted
merr = multierror.Append(merr, err)
}
}
+ for _, pc := range added.pluginConfigs {
+ if _, err :=
c.apisix.Cluster(clusterName).PluginConfig().Create(ctx, pc); err != nil {
+ merr = multierror.Append(merr, err)
+ }
+ }
for _, r := range added.routes {
if _, err :=
c.apisix.Cluster(clusterName).Route().Create(ctx, r); err != nil {
merr = multierror.Append(merr, err)
@@ -248,6 +296,11 @@ func (c *Controller) syncManifests(ctx context.Context,
added, updated, deleted
merr = multierror.Append(merr, err)
}
}
+ for _, pc := range updated.pluginConfigs {
+ if _, err :=
c.apisix.Cluster(clusterName).PluginConfig().Update(ctx, pc); err != nil {
+ merr = multierror.Append(merr, err)
+ }
+ }
for _, r := range updated.routes {
if _, err :=
c.apisix.Cluster(clusterName).Route().Update(ctx, r); err != nil {
merr = multierror.Append(merr, err)
diff --git a/pkg/ingress/manifest_test.go b/pkg/ingress/manifest_test.go
index 847e179..9bad334 100644
--- a/pkg/ingress/manifest_test.go
+++ b/pkg/ingress/manifest_test.go
@@ -180,6 +180,69 @@ func TestDiffUpstreams(t *testing.T) {
assert.Equal(t, "2", deleted[0].ID)
}
+func TestDiffPluginConfigs(t *testing.T) {
+ news := []*apisixv1.PluginConfig{
+ {
+ Metadata: apisixv1.Metadata{
+ ID: "1",
+ },
+ },
+ {
+ Metadata: apisixv1.Metadata{
+ ID: "3",
+ },
+ Plugins: map[string]interface{}{
+ "key-1": 123456,
+ },
+ },
+ }
+ added, updated, deleted := diffPluginConfigs(nil, news)
+ assert.Nil(t, updated)
+ assert.Nil(t, deleted)
+ assert.Len(t, added, 2)
+ assert.Equal(t, "1", added[0].ID)
+ assert.Equal(t, "3", added[1].ID)
+ assert.Equal(t, news[1].Plugins, added[1].Plugins)
+
+ olds := []*apisixv1.PluginConfig{
+ {
+ Metadata: apisixv1.Metadata{
+ ID: "2",
+ },
+ },
+ {
+ Metadata: apisixv1.Metadata{
+ ID: "3",
+ },
+ Plugins: map[string]interface{}{
+ "key-1": 123456789,
+ "key-2": map[string][]string{
+ "whitelist": {
+ "127.0.0.0/24",
+ "113.74.26.106",
+ },
+ },
+ },
+ },
+ }
+ added, updated, deleted = diffPluginConfigs(olds, nil)
+ assert.Nil(t, updated)
+ assert.Nil(t, added)
+ assert.Len(t, deleted, 2)
+ assert.Equal(t, "2", deleted[0].ID)
+ assert.Equal(t, "3", deleted[1].ID)
+ assert.Equal(t, olds[1].Plugins, deleted[1].Plugins)
+
+ added, updated, deleted = diffPluginConfigs(olds, news)
+ assert.Len(t, added, 1)
+ assert.Equal(t, "1", added[0].ID)
+ assert.Len(t, updated, 1)
+ assert.Equal(t, "3", updated[0].ID)
+ assert.Len(t, updated[0].Plugins, 1)
+ assert.Len(t, deleted, 1)
+ assert.Equal(t, "2", deleted[0].ID)
+}
+
func TestManifestDiff(t *testing.T) {
retries := 2
m := &manifest{
@@ -204,6 +267,22 @@ func TestManifestDiff(t *testing.T) {
Retries: &retries,
},
},
+ pluginConfigs: []*apisixv1.PluginConfig{
+ {
+ Metadata: apisixv1.Metadata{
+ ID: "5",
+ },
+ Plugins: map[string]interface{}{
+ "key-1": 123456789,
+ "key-2": map[string][]string{
+ "whitelist": {
+ "127.0.0.0/24",
+ "113.74.26.106",
+ },
+ },
+ },
+ },
+ },
}
om := &manifest{
routes: []*apisixv1.Route{
@@ -226,13 +305,17 @@ func TestManifestDiff(t *testing.T) {
assert.Equal(t, "1", added.routes[0].ID)
assert.Len(t, added.upstreams, 1)
assert.Equal(t, "4", added.upstreams[0].ID)
+ assert.Len(t, added.pluginConfigs, 1)
+ assert.Equal(t, "5", added.pluginConfigs[0].ID)
assert.Len(t, updated.routes, 1)
assert.Equal(t, "3", updated.routes[0].ID)
assert.Equal(t, []string{"GET"}, updated.routes[0].Methods)
assert.Nil(t, updated.upstreams)
+ assert.Nil(t, updated.pluginConfigs)
assert.Len(t, deleted.routes, 1)
assert.Equal(t, "2", deleted.routes[0].ID)
assert.Nil(t, updated.upstreams)
+ assert.Nil(t, updated.pluginConfigs)
}
diff --git a/pkg/ingress/status.go b/pkg/ingress/status.go
index 09f3125..cda58a5 100644
--- a/pkg/ingress/status.go
+++ b/pkg/ingress/status.go
@@ -173,6 +173,23 @@ func (c *Controller) recordStatus(at interface{}, reason
string, err error, stat
)
}
}
+ case *configv2beta3.ApisixPluginConfig:
+ // set to status
+ if v.Status.Conditions == nil {
+ conditions := make([]metav1.Condition, 0)
+ v.Status.Conditions = conditions
+ }
+ if c.verifyGeneration(&v.Status.Conditions, condition) {
+ meta.SetStatusCondition(&v.Status.Conditions, condition)
+ if _, errRecord :=
client.ApisixV2beta3().ApisixPluginConfigs(v.Namespace).
+ UpdateStatus(context.TODO(), v,
metav1.UpdateOptions{}); errRecord != nil {
+ log.Errorw("failed to record status change for
ApisixPluginConfig",
+ zap.Error(errRecord),
+ zap.String("name", v.Name),
+ zap.String("namespace", v.Namespace),
+ )
+ }
+ }
case *networkingv1.Ingress:
// set to status
lbips, err := c.ingressLBStatusIPs()
diff --git a/pkg/kube/apisix/apis/config/v2beta3/types.go
b/pkg/kube/apisix/apis/config/v2beta3/types.go
index bdf948f..58c4c14 100644
--- a/pkg/kube/apisix/apis/config/v2beta3/types.go
+++ b/pkg/kube/apisix/apis/config/v2beta3/types.go
@@ -67,10 +67,11 @@ type ApisixRouteHTTP struct {
// Backends represents potential backends to proxy after the route
// rule matched. When number of backends are more than one,
traffic-split
// plugin in APISIX will be used to split traffic based on the backend
weight.
- Backends []ApisixRouteHTTPBackend `json:"backends,omitempty"
yaml:"backends,omitempty"`
- Websocket bool `json:"websocket"
yaml:"websocket"`
- Plugins []ApisixRouteHTTPPlugin `json:"plugins,omitempty"
yaml:"plugins,omitempty"`
- Authentication ApisixRouteAuthentication
`json:"authentication,omitempty" yaml:"authentication,omitempty"`
+ Backends []ApisixRouteHTTPBackend `json:"backends,omitempty"
yaml:"backends,omitempty"`
+ Websocket bool `json:"websocket"
yaml:"websocket"`
+ PluginConfigName string
`json:"plugin_config_name,omitempty" yaml:"plugin_config_name,omitempty"`
+ Plugins []ApisixRouteHTTPPlugin `json:"plugins,omitempty"
yaml:"plugins,omitempty"`
+ Authentication ApisixRouteAuthentication
`json:"authentication,omitempty" yaml:"authentication,omitempty"`
}
// ApisixRouteHTTPBackend represents a HTTP backend (a Kuberentes Service).
@@ -629,11 +630,9 @@ type ApisixPluginConfig struct {
// ApisixPluginConfigSpec defines the desired state of ApisixPluginConfigSpec.
type ApisixPluginConfigSpec struct {
- // Plugins contains a list of ApisixRouteHTTPPluginConfig
+ // Plugins contains a list of ApisixRouteHTTPPlugin
// +required
- // +kubebuilder:validation:Required
- // +kubebuilder:validation:MinItems=1
- Plugins []ApisixRouteHTTPPluginConfig `json:"plugins" yaml:"plugins"`
+ Plugins []ApisixRouteHTTPPlugin `json:"plugins" yaml:"plugins"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
diff --git a/pkg/kube/apisix/apis/config/v2beta3/zz_generated.deepcopy.go
b/pkg/kube/apisix/apis/config/v2beta3/zz_generated.deepcopy.go
index 3498257..27ba4f5 100644
--- a/pkg/kube/apisix/apis/config/v2beta3/zz_generated.deepcopy.go
+++ b/pkg/kube/apisix/apis/config/v2beta3/zz_generated.deepcopy.go
@@ -522,7 +522,7 @@ func (in *ApisixPluginConfigSpec) DeepCopyInto(out
*ApisixPluginConfigSpec) {
*out = *in
if in.Plugins != nil {
in, out := &in.Plugins, &out.Plugins
- *out = make([]ApisixRouteHTTPPluginConfig, len(*in))
+ *out = make([]ApisixRouteHTTPPlugin, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
diff --git a/pkg/kube/apisix_plugin_config.go b/pkg/kube/apisix_plugin_config.go
new file mode 100644
index 0000000..a3147d2
--- /dev/null
+++ b/pkg/kube/apisix_plugin_config.go
@@ -0,0 +1,132 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package kube
+
+import (
+ "errors"
+
+ configv2beta3
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2beta3"
+ listersv2beta3
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/client/listers/config/v2beta3"
+)
+
+const (
+ // ApisixPluginConfigV2beta3 represents the ApisixPluginConfig in
apisix.apache.org/v2beta3 group version
+ ApisixPluginConfigV2beta3 = "apisix.apache.org/v2beta3"
+)
+
+// ApisixPluginConfigLister is an encapsulation for the lister of
ApisixPluginConfig,
+// it aims at to be compatible with different ApisixPluginConfig versions.
+type ApisixPluginConfigLister interface {
+ // V2beta3 gets the ApisixPluginConfig in apisix.apache.org/v2beta3.
+ V2beta3(string, string) (ApisixPluginConfig, error)
+}
+
+// ApisixPluginConfigInformer is an encapsulation for the informer of
ApisixPluginConfig,
+// it aims at to be compatible with different ApisixPluginConfig versions.
+type ApisixPluginConfigInformer interface {
+ Run(chan struct{})
+}
+
+// ApisixPluginConfig is an encapsulation for ApisixPluginConfig resource with
different
+// versions, for now, they are apisix.apache.org/v1 and
apisix.apache.org/v2alpha1
+type ApisixPluginConfig interface {
+ // GroupVersion returns the api group version of the
+ // real ApisixPluginConfig.
+ GroupVersion() string
+ // V2beta3 returns the ApisixPluginConfig in apisix.apache.org/v2beta3,
the real
+ // ApisixPluginConfig must be in this group version, otherwise will
panic.
+ V2beta3() *configv2beta3.ApisixPluginConfig
+ // ResourceVersion returns the the resource version field inside
+ // the real ApisixPluginConfig.
+ ResourceVersion() string
+}
+
+// ApisixPluginConfigEvent contains the ApisixPluginConfig key (namespace/name)
+// and the group version message.
+type ApisixPluginConfigEvent struct {
+ Key string
+ OldObject ApisixPluginConfig
+ GroupVersion string
+}
+
+type apisixPluginConfig struct {
+ groupVersion string
+ v2beta3 *configv2beta3.ApisixPluginConfig
+}
+
+func (apc *apisixPluginConfig) V2beta3() *configv2beta3.ApisixPluginConfig {
+ if apc.groupVersion != ApisixPluginConfigV2beta3 {
+ panic("not a apisix.apache.org/v2beta3 pluginConfig")
+ }
+ return apc.v2beta3
+}
+
+func (apc *apisixPluginConfig) GroupVersion() string {
+ return apc.groupVersion
+}
+
+func (apc *apisixPluginConfig) ResourceVersion() string {
+ return apc.V2beta3().ResourceVersion
+}
+
+type apisixPluginConfigLister struct {
+ v2beta3Lister listersv2beta3.ApisixPluginConfigLister
+}
+
+func (l *apisixPluginConfigLister) V2beta3(namespace, name string)
(ApisixPluginConfig, error) {
+ apc, err := l.v2beta3Lister.ApisixPluginConfigs(namespace).Get(name)
+ if err != nil {
+ return nil, err
+ }
+ return &apisixPluginConfig{
+ groupVersion: ApisixPluginConfigV2beta3,
+ v2beta3: apc,
+ }, nil
+}
+
+// MustNewApisixPluginConfig creates a kube.ApisixPluginConfig object
according to the
+// type of obj.
+func MustNewApisixPluginConfig(obj interface{}) ApisixPluginConfig {
+ switch apc := obj.(type) {
+ case *configv2beta3.ApisixPluginConfig:
+ return &apisixPluginConfig{
+ groupVersion: ApisixPluginConfigV2beta3,
+ v2beta3: apc,
+ }
+ default:
+ panic("invalid ApisixPluginConfig type")
+ }
+}
+
+// NewApisixPluginConfig creates a kube.ApisixPluginConfig object according to
the
+// type of obj. It returns nil and the error reason when the
+// type assertion fails.
+func NewApisixPluginConfig(obj interface{}) (ApisixPluginConfig, error) {
+ switch apc := obj.(type) {
+ case *configv2beta3.ApisixPluginConfig:
+ return &apisixPluginConfig{
+ groupVersion: ApisixPluginConfigV2beta3,
+ v2beta3: apc,
+ }, nil
+ default:
+ return nil, errors.New("invalid ApisixPluginConfig type")
+ }
+}
+
+func NewApisixPluginConfigLister(v2beta3
listersv2beta3.ApisixPluginConfigLister) ApisixPluginConfigLister {
+ return &apisixPluginConfigLister{
+ v2beta3Lister: v2beta3,
+ }
+}
diff --git a/pkg/kube/apisix_route.go b/pkg/kube/apisix_route.go
index 7ecf025..54e4a78 100644
--- a/pkg/kube/apisix_route.go
+++ b/pkg/kube/apisix_route.go
@@ -88,20 +88,20 @@ type apisixRoute struct {
func (ar *apisixRoute) V2beta1() *configv2beta1.ApisixRoute {
if ar.groupVersion != ApisixRouteV2beta1 {
- panic("not a apisix.apache.org/v2beta1 ingress")
+ panic("not a apisix.apache.org/v2beta1 route")
}
return ar.v2beta1
}
func (ar *apisixRoute) V2beta2() *configv2beta2.ApisixRoute {
if ar.groupVersion != ApisixRouteV2beta2 {
- panic("not a apisix.apache.org/v2beta3 ingress")
+ panic("not a apisix.apache.org/v2beta3 route")
}
return ar.v2beta2
}
func (ar *apisixRoute) V2beta3() *configv2beta3.ApisixRoute {
if ar.groupVersion != ApisixRouteV2beta3 {
- panic("not a apisix.apache.org/v2beta3 ingress")
+ panic("not a apisix.apache.org/v2beta3 route")
}
return ar.v2beta3
}
diff --git a/pkg/kube/translation/apisix_pluginconfig.go
b/pkg/kube/translation/apisix_pluginconfig.go
new file mode 100644
index 0000000..44a6216
--- /dev/null
+++ b/pkg/kube/translation/apisix_pluginconfig.go
@@ -0,0 +1,64 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package translation
+
+import (
+ "go.uber.org/zap"
+
+ "github.com/apache/apisix-ingress-controller/pkg/id"
+ configv2beta3
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2beta3"
+ "github.com/apache/apisix-ingress-controller/pkg/log"
+ apisixv1
"github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
+)
+
+func (t *translator) TranslatePluginConfigV2beta3(config
*configv2beta3.ApisixPluginConfig) (*TranslateContext, error) {
+ ctx := defaultEmptyTranslateContext()
+ pluginMap := make(apisixv1.Plugins)
+ if len(config.Spec.Plugins) > 0 {
+ for _, plugin := range config.Spec.Plugins {
+ if !plugin.Enable {
+ continue
+ }
+ if plugin.Config != nil {
+ // Here, it will override same key.
+ if t, ok := pluginMap[plugin.Name]; ok {
+ log.Infow("TranslatePluginConfigV2beta3
override same plugin key",
+ zap.String("key", plugin.Name),
+ zap.Any("old", t),
+ zap.Any("new", plugin.Config),
+ )
+ }
+ pluginMap[plugin.Name] = plugin.Config
+ } else {
+ pluginMap[plugin.Name] =
make(map[string]interface{})
+ }
+ }
+ }
+ pc := apisixv1.NewDefaultPluginConfig()
+ pc.Name = apisixv1.ComposePluginConfigName(config.Namespace,
config.Name)
+ pc.ID = id.GenID(pc.Name)
+ pc.Plugins = pluginMap
+ ctx.addPluginConfig(pc)
+ return ctx, nil
+}
+
+func (t *translator) TranslatePluginConfigV2beta3NotStrictly(config
*configv2beta3.ApisixPluginConfig) (*TranslateContext, error) {
+ ctx := defaultEmptyTranslateContext()
+ pc := apisixv1.NewDefaultPluginConfig()
+ pc.Name = apisixv1.ComposePluginConfigName(config.Namespace,
config.Name)
+ pc.ID = id.GenID(pc.Name)
+ ctx.addPluginConfig(pc)
+ return ctx, nil
+}
diff --git a/pkg/kube/translation/apisix_pluginconfig_test.go
b/pkg/kube/translation/apisix_pluginconfig_test.go
new file mode 100644
index 0000000..4d20570
--- /dev/null
+++ b/pkg/kube/translation/apisix_pluginconfig_test.go
@@ -0,0 +1,114 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package translation
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+ configv2beta3
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2beta3"
+)
+
+func TestTranslatePluginConfigV2beta3(t *testing.T) {
+ apc := &configv2beta3.ApisixPluginConfig{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "apc",
+ Namespace: "test-ns",
+ },
+ Spec: configv2beta3.ApisixPluginConfigSpec{
+ Plugins: []configv2beta3.ApisixRouteHTTPPlugin{
+ {
+ Name: "case1",
+ Enable: true,
+ Config: map[string]interface{}{
+ "key-1": 1,
+ "key-2": 2,
+ },
+ },
+ {
+ Name: "case2",
+ Enable: false,
+ Config: map[string]interface{}{
+ "key-3": 3,
+ "key-4": 4,
+ "key-5": 5,
+ },
+ },
+ {
+ Name: "case3",
+ Enable: true,
+ Config: map[string]interface{}{
+ "key-6": 6,
+ "key-7": 7,
+ "key-8": 8,
+ "key-9": 9,
+ },
+ },
+ },
+ },
+ }
+ trans := &translator{}
+ ctx, err := trans.TranslatePluginConfigV2beta3(apc)
+ assert.NoError(t, err)
+ assert.Len(t, ctx.PluginConfigs, 1)
+ assert.Len(t, ctx.PluginConfigs[0].Plugins, 2)
+}
+
+func TestTranslatePluginConfigV2beta3NotStrictly(t *testing.T) {
+ apc := &configv2beta3.ApisixPluginConfig{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "apc",
+ Namespace: "test-ns",
+ },
+ Spec: configv2beta3.ApisixPluginConfigSpec{
+ Plugins: []configv2beta3.ApisixRouteHTTPPlugin{
+ {
+ Name: "case1",
+ Enable: true,
+ Config: map[string]interface{}{
+ "key-1": 1,
+ "key-2": 2,
+ },
+ },
+ {
+ Name: "case2",
+ Enable: false,
+ Config: map[string]interface{}{
+ "key-3": 3,
+ "key-4": 4,
+ "key-5": 5,
+ },
+ },
+ {
+ Name: "case3",
+ Enable: true,
+ Config: map[string]interface{}{
+ "key-6": 6,
+ "key-7": 7,
+ "key-8": 8,
+ "key-9": 9,
+ },
+ },
+ },
+ },
+ }
+ trans := &translator{}
+ ctx, err := trans.TranslatePluginConfigV2beta3NotStrictly(apc)
+ assert.NoError(t, err)
+ assert.Len(t, ctx.PluginConfigs, 1)
+ assert.Len(t, ctx.PluginConfigs[0].Plugins, 0)
+}
diff --git a/pkg/kube/translation/apisix_route.go
b/pkg/kube/translation/apisix_route.go
index edaa8ae..8e92485 100644
--- a/pkg/kube/translation/apisix_route.go
+++ b/pkg/kube/translation/apisix_route.go
@@ -135,9 +135,7 @@ import (
//}
func (t *translator) TranslateRouteV2beta1(ar *configv2beta1.ApisixRoute)
(*TranslateContext, error) {
- ctx := &TranslateContext{
- upstreamMap: make(map[string]struct{}),
- }
+ ctx := defaultEmptyTranslateContext()
if err := t.translateHTTPRouteV2beta1(ctx, ar); err != nil {
return nil, err
@@ -149,9 +147,7 @@ func (t *translator) TranslateRouteV2beta1(ar
*configv2beta1.ApisixRoute) (*Tran
}
func (t *translator) TranslateRouteV2beta1NotStrictly(ar
*configv2beta1.ApisixRoute) (*TranslateContext, error) {
- ctx := &TranslateContext{
- upstreamMap: make(map[string]struct{}),
- }
+ ctx := defaultEmptyTranslateContext()
if err := t.translateHTTPRouteV2beta1NotStrictly(ctx, ar); err != nil {
return nil, err
@@ -277,9 +273,7 @@ func (t *translator) translateHTTPRouteV2beta1(ctx
*TranslateContext, ar *config
}
func (t *translator) TranslateRouteV2beta2(ar *configv2beta2.ApisixRoute)
(*TranslateContext, error) {
- ctx := &TranslateContext{
- upstreamMap: make(map[string]struct{}),
- }
+ ctx := defaultEmptyTranslateContext()
if err := t.translateHTTPRouteV2beta2(ctx, ar); err != nil {
return nil, err
@@ -291,9 +285,7 @@ func (t *translator) TranslateRouteV2beta2(ar
*configv2beta2.ApisixRoute) (*Tran
}
func (t *translator) TranslateRouteV2beta2NotStrictly(ar
*configv2beta2.ApisixRoute) (*TranslateContext, error) {
- ctx := &TranslateContext{
- upstreamMap: make(map[string]struct{}),
- }
+ ctx := defaultEmptyTranslateContext()
if err := t.translateHTTPRouteV2beta2NotStrictly(ctx, ar); err != nil {
return nil, err
@@ -305,9 +297,7 @@ func (t *translator) TranslateRouteV2beta2NotStrictly(ar
*configv2beta2.ApisixRo
}
func (t *translator) TranslateRouteV2beta3(ar *configv2beta3.ApisixRoute)
(*TranslateContext, error) {
- ctx := &TranslateContext{
- upstreamMap: make(map[string]struct{}),
- }
+ ctx := defaultEmptyTranslateContext()
if err := t.translateHTTPRouteV2beta3(ctx, ar); err != nil {
return nil, err
@@ -319,9 +309,7 @@ func (t *translator) TranslateRouteV2beta3(ar
*configv2beta3.ApisixRoute) (*Tran
}
func (t *translator) TranslateRouteV2beta3NotStrictly(ar
*configv2beta3.ApisixRoute) (*TranslateContext, error) {
- ctx := &TranslateContext{
- upstreamMap: make(map[string]struct{}),
- }
+ ctx := defaultEmptyTranslateContext()
if err := t.translateHTTPRouteV2beta3NotStrictly(ctx, ar); err != nil {
return nil, err
@@ -542,6 +530,18 @@ func (t *translator) translateHTTPRouteV2beta3(ctx
*TranslateContext, ar *config
route.EnableWebsocket = part.Websocket
route.Plugins = pluginMap
route.Timeout = timeout
+ pluginConfig := apisixv1.NewDefaultPluginConfig()
+ if len(pluginMap) > 0 {
+ pluginConfigName :=
apisixv1.ComposePluginConfigName(ar.Namespace, backend.ServiceName)
+ route.PluginConfigId = id.GenID(pluginConfigName)
+ pluginConfig.ID = route.PluginConfigId
+ pluginConfig.Name = pluginConfigName
+ pluginConfig.Plugins = pluginMap
+ } else {
+ if part.PluginConfigName != "" {
+ route.PluginConfigId = part.PluginConfigName
+ }
+ }
if len(backends) > 0 {
weight := _defaultWeight
@@ -566,6 +566,9 @@ func (t *translator) translateHTTPRouteV2beta3(ctx
*TranslateContext, ar *config
}
ctx.addUpstream(ups)
}
+ if len(pluginMap) > 0 {
+ ctx.addPluginConfig(pluginConfig)
+ }
}
return nil
}
@@ -944,10 +947,44 @@ func (t *translator)
translateHTTPRouteV2beta3NotStrictly(ctx *TranslateContext,
// Use the first backend as the default backend in Route,
// others will be configured in traffic-split plugin.
backend := backends[0]
+
+ pluginMap := make(apisixv1.Plugins)
+ // add route plugins
+ for _, plugin := range part.Plugins {
+ if !plugin.Enable {
+ continue
+ }
+ if plugin.Config != nil {
+ pluginMap[plugin.Name] = plugin.Config
+ } else {
+ pluginMap[plugin.Name] =
make(map[string]interface{})
+ }
+ }
+
+ // add KeyAuth and basicAuth plugin
+ if part.Authentication.Enable {
+ switch part.Authentication.Type {
+ case "keyAuth":
+ pluginMap["key-auth"] =
part.Authentication.KeyAuth
+ case "basicAuth":
+ pluginMap["basic-auth"] =
make(map[string]interface{})
+ default:
+ pluginMap["basic-auth"] =
make(map[string]interface{})
+ }
+ }
+
upstreamName := apisixv1.ComposeUpstreamName(ar.Namespace,
backend.ServiceName, backend.Subset, backend.ServicePort.IntVal)
route := apisixv1.NewDefaultRoute()
route.Name = apisixv1.ComposeRouteName(ar.Namespace, ar.Name,
part.Name)
route.ID = id.GenID(route.Name)
+ pluginConfig := apisixv1.NewDefaultPluginConfig()
+ if len(pluginMap) > 0 {
+ pluginConfigName :=
apisixv1.ComposePluginConfigName(ar.Namespace, backend.ServiceName)
+ route.PluginConfigId = id.GenID(pluginConfigName)
+ pluginConfig.ID = route.PluginConfigId
+ pluginConfig.Name = pluginConfigName
+ }
+
ctx.addRoute(route)
if !ctx.checkUpstreamExist(upstreamName) {
ups, err :=
t.translateUpstreamNotStrictly(ar.Namespace, backend.ServiceName,
backend.Subset, backend.ServicePort.IntVal)
@@ -956,6 +993,9 @@ func (t *translator)
translateHTTPRouteV2beta3NotStrictly(ctx *TranslateContext,
}
ctx.addUpstream(ups)
}
+ if len(pluginMap) > 0 {
+ ctx.addPluginConfig(pluginConfig)
+ }
}
return nil
}
diff --git a/pkg/kube/translation/apisix_route_test.go
b/pkg/kube/translation/apisix_route_test.go
index 59cb23c..ed15ea8 100644
--- a/pkg/kube/translation/apisix_route_test.go
+++ b/pkg/kube/translation/apisix_route_test.go
@@ -355,6 +355,16 @@ func TestTranslateApisixRouteV2alpha1NotStrictly(t
*testing.T) {
},
},
},
+ Plugins:
[]configv2beta3.ApisixRouteHTTPPlugin{
+ {
+ Name: "plugin-1",
+ Enable: true,
+ Config:
map[string]interface{}{
+ "key-1": 123456,
+ "key-2":
"2121331",
+ },
+ },
+ },
},
{
Name: "rule2",
@@ -381,14 +391,16 @@ func TestTranslateApisixRouteV2alpha1NotStrictly(t
*testing.T) {
assert.NoError(t, err, "translateRoute not strictly should be no error")
assert.Equal(t, 2, len(tx.Routes), "There should be 2 routes")
assert.Equal(t, 2, len(tx.Upstreams), "There should be 2 upstreams")
+ assert.Equal(t, 1, len(tx.PluginConfigs), "There should be 1
pluginConfigs")
assert.Equal(t, "test_ar_rule1", tx.Routes[0].Name, "route1 name error")
assert.Equal(t, "test_ar_rule2", tx.Routes[1].Name, "route2 name error")
assert.Equal(t, "test_svc1_81", tx.Upstreams[0].Name, "upstream1 name
error")
assert.Equal(t, "test_svc2_82", tx.Upstreams[1].Name, "upstream2 name
error")
+ assert.Equal(t, "test_svc1", tx.PluginConfigs[0].Name, "pluginConfig1
name error")
assert.Equal(t, id.GenID("test_ar_rule1"), tx.Routes[0].ID, "route1 id
error")
assert.Equal(t, id.GenID("test_ar_rule2"), tx.Routes[1].ID, "route2 id
error")
assert.Equal(t, id.GenID("test_svc1_81"), tx.Upstreams[0].ID,
"upstream1 id error")
assert.Equal(t, id.GenID("test_svc2_82"), tx.Upstreams[1].ID,
"upstream2 id error")
-
+ assert.Equal(t, id.GenID("test_svc1"), tx.PluginConfigs[0].ID,
"pluginConfig1 id error")
}
diff --git a/pkg/kube/translation/context.go b/pkg/kube/translation/context.go
index 3f21830..dc67ef7 100644
--- a/pkg/kube/translation/context.go
+++ b/pkg/kube/translation/context.go
@@ -18,11 +18,18 @@ import apisix
"github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
// TranslateContext contains APISIX resources generated by the translator.
type TranslateContext struct {
- Routes []*apisix.Route
- StreamRoutes []*apisix.StreamRoute
- Upstreams []*apisix.Upstream
- upstreamMap map[string]struct{}
- SSL []*apisix.Ssl
+ Routes []*apisix.Route
+ StreamRoutes []*apisix.StreamRoute
+ Upstreams []*apisix.Upstream
+ upstreamMap map[string]struct{}
+ SSL []*apisix.Ssl
+ PluginConfigs []*apisix.PluginConfig
+}
+
+func defaultEmptyTranslateContext() *TranslateContext {
+ return &TranslateContext{
+ upstreamMap: make(map[string]struct{}),
+ }
}
func (tc *TranslateContext) addRoute(r *apisix.Route) {
@@ -49,3 +56,7 @@ func (tc *TranslateContext) checkUpstreamExist(name string)
(ok bool) {
_, ok = tc.upstreamMap[name]
return
}
+
+func (tc *TranslateContext) addPluginConfig(pc *apisix.PluginConfig) {
+ tc.PluginConfigs = append(tc.PluginConfigs, pc)
+}
diff --git a/pkg/kube/translation/context_test.go
b/pkg/kube/translation/context_test.go
index 20f55a8..78cabc5 100644
--- a/pkg/kube/translation/context_test.go
+++ b/pkg/kube/translation/context_test.go
@@ -23,9 +23,8 @@ import (
)
func TestTranslateContext(t *testing.T) {
- ctx := &TranslateContext{
- upstreamMap: make(map[string]struct{}),
- }
+ ctx := defaultEmptyTranslateContext()
+
r1 := &apisix.Route{
Metadata: apisix.Metadata{
ID: "1",
@@ -54,22 +53,39 @@ func TestTranslateContext(t *testing.T) {
Name: "aaa",
},
}
+ pc1 := &apisix.PluginConfig{
+ Metadata: apisix.Metadata{
+ ID: "1",
+ Name: "aaa",
+ },
+ }
+ pc2 := &apisix.PluginConfig{
+ Metadata: apisix.Metadata{
+ ID: "2",
+ Name: "aaa",
+ },
+ }
ctx.addRoute(r1)
ctx.addRoute(r2)
ctx.addStreamRoute(sr1)
ctx.addStreamRoute(sr2)
ctx.addUpstream(u1)
ctx.addUpstream(u2)
+ ctx.addPluginConfig(pc1)
+ ctx.addPluginConfig(pc2)
assert.Len(t, ctx.Routes, 2)
assert.Len(t, ctx.StreamRoutes, 2)
assert.Len(t, ctx.Upstreams, 1)
+ assert.Len(t, ctx.PluginConfigs, 2)
assert.Equal(t, r1, ctx.Routes[0])
assert.Equal(t, r2, ctx.Routes[1])
assert.Equal(t, sr1, ctx.StreamRoutes[0])
assert.Equal(t, sr2, ctx.StreamRoutes[1])
assert.Equal(t, u1, ctx.Upstreams[0])
+ assert.Equal(t, pc1, ctx.PluginConfigs[0])
+ assert.Equal(t, pc2, ctx.PluginConfigs[1])
assert.Equal(t, true, ctx.checkUpstreamExist("aaa"))
assert.Equal(t, false, ctx.checkUpstreamExist("bbb"))
diff --git a/pkg/kube/translation/ingress.go b/pkg/kube/translation/ingress.go
index d7c5779..48fe18d 100644
--- a/pkg/kube/translation/ingress.go
+++ b/pkg/kube/translation/ingress.go
@@ -39,9 +39,7 @@ const (
)
func (t *translator) translateIngressV1(ing *networkingv1.Ingress)
(*TranslateContext, error) {
- ctx := &TranslateContext{
- upstreamMap: make(map[string]struct{}),
- }
+ ctx := defaultEmptyTranslateContext()
plugins := t.translateAnnotations(ing.Annotations)
annoExtractor := annotations.NewExtractor(ing.Annotations)
useRegex :=
annoExtractor.GetBoolAnnotation(annotations.AnnotationsPrefix + "use-regex")
@@ -78,8 +76,9 @@ func (t *translator) translateIngressV1(ing
*networkingv1.Ingress) (*TranslateCo
for _, rule := range ing.Spec.Rules {
for _, pathRule := range rule.HTTP.Paths {
var (
- ups *apisixv1.Upstream
- err error
+ ups *apisixv1.Upstream
+ pluginConfig *apisixv1.PluginConfig
+ err error
)
if pathRule.Backend.Service != nil {
ups, err =
t.translateUpstreamFromIngressV1(ing.Namespace, pathRule.Backend.Service)
@@ -138,6 +137,14 @@ func (t *translator) translateIngressV1(ing
*networkingv1.Ingress) (*TranslateCo
}
if len(plugins) > 0 {
route.Plugins = *(plugins.DeepCopy())
+
+ pluginConfig = apisixv1.NewDefaultPluginConfig()
+ pluginConfig.Name =
composeIngressPluginName(ing.Namespace, pathRule.Backend.Service.Name)
+ pluginConfig.ID = id.GenID(route.Name)
+ pluginConfig.Plugins = *(plugins.DeepCopy())
+ ctx.addPluginConfig(pluginConfig)
+
+ route.PluginConfigId = pluginConfig.ID
}
if ups != nil {
route.UpstreamId = ups.ID
@@ -149,9 +156,7 @@ func (t *translator) translateIngressV1(ing
*networkingv1.Ingress) (*TranslateCo
}
func (t *translator) translateIngressV1beta1(ing *networkingv1beta1.Ingress)
(*TranslateContext, error) {
- ctx := &TranslateContext{
- upstreamMap: make(map[string]struct{}),
- }
+ ctx := defaultEmptyTranslateContext()
plugins := t.translateAnnotations(ing.Annotations)
annoExtractor := annotations.NewExtractor(ing.Annotations)
useRegex :=
annoExtractor.GetBoolAnnotation(annotations.AnnotationsPrefix + "use-regex")
@@ -188,8 +193,9 @@ func (t *translator) translateIngressV1beta1(ing
*networkingv1beta1.Ingress) (*T
for _, rule := range ing.Spec.Rules {
for _, pathRule := range rule.HTTP.Paths {
var (
- ups *apisixv1.Upstream
- err error
+ ups *apisixv1.Upstream
+ pluginConfig *apisixv1.PluginConfig
+ err error
)
if pathRule.Backend.ServiceName != "" {
ups, err =
t.translateUpstreamFromIngressV1beta1(ing.Namespace,
pathRule.Backend.ServiceName, pathRule.Backend.ServicePort)
@@ -248,6 +254,14 @@ func (t *translator) translateIngressV1beta1(ing
*networkingv1beta1.Ingress) (*T
}
if len(plugins) > 0 {
route.Plugins = *(plugins.DeepCopy())
+
+ pluginConfig = apisixv1.NewDefaultPluginConfig()
+ pluginConfig.Name =
composeIngressPluginName(ing.Namespace, pathRule.Backend.ServiceName)
+ pluginConfig.ID = id.GenID(route.Name)
+ pluginConfig.Plugins = *(plugins.DeepCopy())
+ ctx.addPluginConfig(pluginConfig)
+
+ route.PluginConfigId = pluginConfig.ID
}
if ups != nil {
route.UpstreamId = ups.ID
@@ -290,9 +304,7 @@ func (t *translator)
translateUpstreamFromIngressV1(namespace string, backend *n
}
func (t *translator) translateIngressExtensionsV1beta1(ing
*extensionsv1beta1.Ingress) (*TranslateContext, error) {
- ctx := &TranslateContext{
- upstreamMap: make(map[string]struct{}),
- }
+ ctx := defaultEmptyTranslateContext()
plugins := t.translateAnnotations(ing.Annotations)
annoExtractor := annotations.NewExtractor(ing.Annotations)
useRegex :=
annoExtractor.GetBoolAnnotation(annotations.AnnotationsPrefix + "use-regex")
@@ -300,8 +312,9 @@ func (t *translator) translateIngressExtensionsV1beta1(ing
*extensionsv1beta1.In
for _, rule := range ing.Spec.Rules {
for _, pathRule := range rule.HTTP.Paths {
var (
- ups *apisixv1.Upstream
- err error
+ ups *apisixv1.Upstream
+ pluginConfig *apisixv1.PluginConfig
+ err error
)
if pathRule.Backend.ServiceName != "" {
// Structure here is same to
ingress.extensions/v1beta1, so just use this method.
@@ -361,6 +374,14 @@ func (t *translator) translateIngressExtensionsV1beta1(ing
*extensionsv1beta1.In
}
if len(plugins) > 0 {
route.Plugins = *(plugins.DeepCopy())
+
+ pluginConfig = apisixv1.NewDefaultPluginConfig()
+ pluginConfig.Name =
composeIngressPluginName(ing.Namespace, pathRule.Backend.ServiceName)
+ pluginConfig.ID = id.GenID(route.Name)
+ pluginConfig.Plugins = *(plugins.DeepCopy())
+ ctx.addPluginConfig(pluginConfig)
+
+ route.PluginConfigId = pluginConfig.ID
}
if ups != nil {
route.UpstreamId = ups.ID
@@ -415,3 +436,17 @@ func composeIngressRouteName(host, path string) string {
return buf.String()
}
+
+func composeIngressPluginName(svc, name string) string {
+ p := make([]byte, 0, len(svc)+len(name)+len("ingress")+2)
+ buf := bytes.NewBuffer(p)
+
+ buf.WriteString("ingress")
+ buf.WriteByte('_')
+ buf.WriteString(svc)
+ buf.WriteByte('_')
+ buf.WriteString(name)
+
+ return buf.String()
+
+}
diff --git a/pkg/kube/translation/ingress_test.go
b/pkg/kube/translation/ingress_test.go
index 98b953e..f72ea25 100644
--- a/pkg/kube/translation/ingress_test.go
+++ b/pkg/kube/translation/ingress_test.go
@@ -16,6 +16,7 @@ package translation
import (
"context"
+ "path"
"testing"
"github.com/stretchr/testify/assert"
@@ -34,6 +35,7 @@ import (
fakeapisix
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/client/clientset/versioned/fake"
apisixinformers
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/client/informers/externalversions"
apisixconst
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/const"
+
"github.com/apache/apisix-ingress-controller/pkg/kube/translation/annotations"
v1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
)
@@ -123,7 +125,9 @@ func TestTranslateIngressV1NoBackend(t *testing.T) {
assert.Nil(t, err)
assert.Len(t, ctx.Routes, 1)
assert.Len(t, ctx.Upstreams, 0)
+ assert.Len(t, ctx.PluginConfigs, 0)
assert.Equal(t, "", ctx.Routes[0].UpstreamId)
+ assert.Equal(t, "", ctx.Routes[0].PluginConfigId)
assert.Equal(t, []string{"/foo", "/foo/*"}, ctx.Routes[0].Uris)
}
@@ -282,6 +286,8 @@ func TestTranslateIngressV1WithRegex(t *testing.T) {
assert.Nil(t, err)
assert.Len(t, ctx.Routes, 1)
assert.Len(t, ctx.Upstreams, 1)
+ // the number of the PluginConfigs should be zero, cause there no
available Annotations matched te rule
+ assert.Len(t, ctx.PluginConfigs, 0)
routeVars, err :=
tr.translateRouteMatchExprs([]configv2beta3.ApisixRouteHTTPMatchExpr{{
Subject: configv2beta3.ApisixRouteHTTPMatchExprSubject{
Scope: apisixconst.ScopePath,
@@ -304,6 +310,11 @@ func TestTranslateIngressV1(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "default",
+ Annotations: map[string]string{
+ "k8s.apisix.apache.org/use-regex":
"true",
+ path.Join(annotations.AnnotationsPrefix,
"enable-cors"): "true",
+ path.Join(annotations.AnnotationsPrefix,
"allowlist-source-range"): "127.0.0.1",
+ },
},
Spec: networkingv1.IngressSpec{
Rules: []networkingv1.IngressRule{
@@ -386,12 +397,15 @@ func TestTranslateIngressV1(t *testing.T) {
assert.Nil(t, err)
assert.Len(t, ctx.Routes, 2)
assert.Len(t, ctx.Upstreams, 2)
+ assert.Len(t, ctx.PluginConfigs, 2)
assert.Equal(t, []string{"/foo", "/foo/*"}, ctx.Routes[0].Uris)
assert.Equal(t, ctx.Upstreams[0].ID, ctx.Routes[0].UpstreamId)
+ assert.Equal(t, ctx.PluginConfigs[0].ID, ctx.Routes[0].PluginConfigId)
assert.Equal(t, "apisix.apache.org", ctx.Routes[0].Host)
assert.Equal(t, []string{"/bar"}, ctx.Routes[1].Uris)
assert.Equal(t, ctx.Upstreams[1].ID, ctx.Routes[1].UpstreamId)
+ assert.Equal(t, ctx.PluginConfigs[1].ID, ctx.Routes[1].PluginConfigId)
assert.Equal(t, "apisix.apache.org", ctx.Routes[1].Host)
assert.Equal(t, "roundrobin", ctx.Upstreams[0].Type)
@@ -409,6 +423,9 @@ func TestTranslateIngressV1(t *testing.T) {
assert.Equal(t, "192.168.1.1", ctx.Upstreams[1].Nodes[0].Host)
assert.Equal(t, 9443, ctx.Upstreams[1].Nodes[1].Port)
assert.Equal(t, "192.168.1.2", ctx.Upstreams[1].Nodes[1].Host)
+
+ assert.Len(t, ctx.PluginConfigs[0].Plugins, 2)
+ assert.Len(t, ctx.PluginConfigs[1].Plugins, 2)
}
func TestTranslateIngressV1beta1NoBackend(t *testing.T) {
@@ -442,7 +459,9 @@ func TestTranslateIngressV1beta1NoBackend(t *testing.T) {
assert.Nil(t, err)
assert.Len(t, ctx.Routes, 1)
assert.Len(t, ctx.Upstreams, 0)
+ assert.Len(t, ctx.PluginConfigs, 0)
assert.Equal(t, "", ctx.Routes[0].UpstreamId)
+ assert.Equal(t, "", ctx.Routes[0].PluginConfigId)
assert.Equal(t, []string{"/foo", "/foo/*"}, ctx.Routes[0].Uris)
}
@@ -600,6 +619,8 @@ func TestTranslateIngressV1beta1WithRegex(t *testing.T) {
assert.Nil(t, err)
assert.Len(t, ctx.Routes, 1)
assert.Len(t, ctx.Upstreams, 1)
+ // the number of the PluginConfigs should be zero, cause there no
available Annotations matched te rule
+ assert.Len(t, ctx.PluginConfigs, 0)
routeVars, err :=
tr.translateRouteMatchExprs([]configv2beta3.ApisixRouteHTTPMatchExpr{{
Subject: configv2beta3.ApisixRouteHTTPMatchExprSubject{
@@ -621,6 +642,12 @@ func TestTranslateIngressV1beta1(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "default",
+ Annotations: map[string]string{
+ "k8s.apisix.apache.org/use-regex":
"true",
+ path.Join(annotations.AnnotationsPrefix,
"enable-cors"): "true",
+ path.Join(annotations.AnnotationsPrefix,
"allowlist-source-range"): "127.0.0.1",
+ path.Join(annotations.AnnotationsPrefix,
"enable-cors222"): "true",
+ },
},
Spec: networkingv1beta1.IngressSpec{
Rules: []networkingv1beta1.IngressRule{
@@ -701,6 +728,7 @@ func TestTranslateIngressV1beta1(t *testing.T) {
assert.Nil(t, err)
assert.Len(t, ctx.Routes, 2)
assert.Len(t, ctx.Upstreams, 2)
+ assert.Len(t, ctx.PluginConfigs, 2)
assert.Equal(t, []string{"/foo", "/foo/*"}, ctx.Routes[0].Uris)
assert.Equal(t, ctx.Upstreams[0].ID, ctx.Routes[0].UpstreamId)
@@ -724,6 +752,9 @@ func TestTranslateIngressV1beta1(t *testing.T) {
assert.Equal(t, "192.168.1.1", ctx.Upstreams[1].Nodes[0].Host)
assert.Equal(t, 9443, ctx.Upstreams[1].Nodes[1].Port)
assert.Equal(t, "192.168.1.2", ctx.Upstreams[1].Nodes[1].Host)
+
+ assert.Len(t, ctx.PluginConfigs[0].Plugins, 2)
+ assert.Len(t, ctx.PluginConfigs[1].Plugins, 2)
}
func TestTranslateIngressExtensionsV1beta1(t *testing.T) {
@@ -733,6 +764,12 @@ func TestTranslateIngressExtensionsV1beta1(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "default",
+ Annotations: map[string]string{
+ "k8s.apisix.apache.org/use-regex":
"true",
+ path.Join(annotations.AnnotationsPrefix,
"enable-cors"): "true",
+ path.Join(annotations.AnnotationsPrefix,
"allowlist-source-range"): "127.0.0.1",
+ path.Join(annotations.AnnotationsPrefix,
"enable-cors222"): "true",
+ },
},
Spec: extensionsv1beta1.IngressSpec{
Rules: []extensionsv1beta1.IngressRule{
@@ -813,6 +850,7 @@ func TestTranslateIngressExtensionsV1beta1(t *testing.T) {
assert.Nil(t, err)
assert.Len(t, ctx.Routes, 2)
assert.Len(t, ctx.Upstreams, 2)
+ assert.Len(t, ctx.PluginConfigs, 2)
assert.Equal(t, []string{"/foo", "/foo/*"}, ctx.Routes[0].Uris)
assert.Equal(t, ctx.Upstreams[0].ID, ctx.Routes[0].UpstreamId)
@@ -836,6 +874,9 @@ func TestTranslateIngressExtensionsV1beta1(t *testing.T) {
assert.Equal(t, "192.168.1.1", ctx.Upstreams[1].Nodes[0].Host)
assert.Equal(t, 9443, ctx.Upstreams[1].Nodes[1].Port)
assert.Equal(t, "192.168.1.2", ctx.Upstreams[1].Nodes[1].Host)
+
+ assert.Len(t, ctx.PluginConfigs[0].Plugins, 2)
+ assert.Len(t, ctx.PluginConfigs[1].Plugins, 2)
}
func TestTranslateIngressExtensionsV1beta1BackendWithInvalidService(t
*testing.T) {
@@ -991,6 +1032,8 @@ func TestTranslateIngressExtensionsV1beta1WithRegex(t
*testing.T) {
assert.Nil(t, err)
assert.Len(t, ctx.Routes, 1)
assert.Len(t, ctx.Upstreams, 1)
+ // the number of the PluginConfigs should be zero, cause there no
available Annotations matched te rule
+ assert.Len(t, ctx.PluginConfigs, 0)
routeVars, err :=
tr.translateRouteMatchExprs([]configv2beta3.ApisixRouteHTTPMatchExpr{{
Subject: configv2beta3.ApisixRouteHTTPMatchExprSubject{
Scope: apisixconst.ScopePath,
@@ -1004,5 +1047,4 @@ func TestTranslateIngressExtensionsV1beta1WithRegex(t
*testing.T) {
assert.Equal(t, []string{"/*"}, ctx.Routes[0].Uris)
assert.Equal(t, expectedVars, ctx.Routes[0].Vars)
-
}
diff --git a/pkg/kube/translation/translator.go
b/pkg/kube/translation/translator.go
index a4d2ad5..538e7a6 100644
--- a/pkg/kube/translation/translator.go
+++ b/pkg/kube/translation/translator.go
@@ -68,20 +68,20 @@ type Translator interface {
// TranslateRouteV2beta1 translates the configv2beta1.ApisixRoute
object into several Route
// and Upstream resources.
TranslateRouteV2beta1(*configv2beta1.ApisixRoute) (*TranslateContext,
error)
- // TranslateRouteV2beta1NotStrictly translates the
configv2beta1.ApisixRoute object into several Route
+ // TranslateRouteV2beta1NotStrictly translates the
configv2beta1.ApisixRoute object into several Route,
// and Upstream resources not strictly, only used for delete event.
TranslateRouteV2beta1NotStrictly(*configv2beta1.ApisixRoute)
(*TranslateContext, error)
- // TranslateRouteV2beta2 translates the configv2beta2.ApisixRoute
object into several Route
+ // TranslateRouteV2beta2 translates the configv2beta2.ApisixRoute
object into several Route,
// and Upstream resources.
TranslateRouteV2beta2(*configv2beta2.ApisixRoute) (*TranslateContext,
error)
- // TranslateRouteV2beta2NotStrictly translates the
configv2beta2.ApisixRoute object into several Route
- // and Upstream resources not strictly, only used for delete event.
+ // TranslateRouteV2beta2NotStrictly translates the
configv2beta2.ApisixRoute object into several Route,
+ // and Upstream resources not strictly, only used for delete event.
TranslateRouteV2beta2NotStrictly(*configv2beta2.ApisixRoute)
(*TranslateContext, error)
- // TranslateRouteV2beta3 translates the configv2beta3.ApisixRoute
object into several Route
- // and Upstream resources.
+ // TranslateRouteV2beta3 translates the configv2beta3.ApisixRoute
object into several Route,
+ // Upstream and PluginConfig resources.
TranslateRouteV2beta3(*configv2beta3.ApisixRoute) (*TranslateContext,
error)
- // TranslateRouteV2beta3NotStrictly translates the
configv2beta3.ApisixRoute object into several Route
- // and Upstream resources not strictly, only used for delete event.
+ // TranslateRouteV2beta3NotStrictly translates the
configv2beta3.ApisixRoute object into several Route,
+ // Upstream and PluginConfig resources not strictly, only used for
delete event.
TranslateRouteV2beta3NotStrictly(*configv2beta3.ApisixRoute)
(*TranslateContext, error)
// TranslateSSL translates the configv2beta3.ApisixTls object into the
APISIX SSL resource.
TranslateSSL(*configv2beta3.ApisixTls) (*apisixv1.Ssl, error)
@@ -91,6 +91,12 @@ type Translator interface {
// TranslateApisixConsumer translates the configv2beta3.APisixConsumer
object into the APISIX Consumer
// resource.
TranslateApisixConsumer(*configv2beta3.ApisixConsumer)
(*apisixv1.Consumer, error)
+ // TranslatePluginConfigV2beta3 translates the
configv2beta3.ApisixPluginConfig object into several PluginConfig
+ // resources.
+ TranslatePluginConfigV2beta3(*configv2beta3.ApisixPluginConfig)
(*TranslateContext, error)
+ // TranslatePluginConfigV2beta3NotStrictly translates the
configv2beta3.ApisixPluginConfig object into several PluginConfig
+ // resources not strictly, only used for delete event.
+
TranslatePluginConfigV2beta3NotStrictly(*configv2beta3.ApisixPluginConfig)
(*TranslateContext, error)
// ExtractKeyPair extracts certificate and private key pair from secret
// Supports APISIX style ("cert" and "key") and Kube style ("tls.crt"
and "tls.key)
ExtractKeyPair(s *corev1.Secret, hasPrivateKey bool) ([]byte, []byte,
error)
diff --git a/pkg/types/apisix/v1/types.go b/pkg/types/apisix/v1/types.go
index 99c99a9..0080584 100644
--- a/pkg/types/apisix/v1/types.go
+++ b/pkg/types/apisix/v1/types.go
@@ -100,6 +100,7 @@ type Route struct {
RemoteAddrs []string `json:"remote_addrs,omitempty"
yaml:"remote_addrs,omitempty"`
UpstreamId string `json:"upstream_id,omitempty"
yaml:"upstream_id,omitempty"`
Plugins Plugins `json:"plugins,omitempty"
yaml:"plugins,omitempty"`
+ PluginConfigId string `json:"plugin_config_id,omitempty"
yaml:"plugin_config_id,omitempty"`
}
// Vars represents the route match expressions of APISIX.
@@ -357,7 +358,7 @@ type Consumer struct {
// +k8s:deepcopy-gen=true
type PluginConfig struct {
Metadata `json:",inline" yaml:",inline"`
- Plugins Plugins `json:"plugins,omitempty" yaml:"plugins,omitempty"`
+ Plugins Plugins `json:"plugins" yaml:"plugins"`
}
// NewDefaultUpstream returns an empty Upstream with default values.
@@ -408,6 +409,19 @@ func NewDefaultConsumer() *Consumer {
}
}
+// NewDefaultPluginConfig returns an empty PluginConfig with default values.
+func NewDefaultPluginConfig() *PluginConfig {
+ return &PluginConfig{
+ Metadata: Metadata{
+ Desc: "Created by apisix-ingress-controller, DO NOT
modify it manually",
+ Labels: map[string]string{
+ "managed-by": "apisix-ingress-controller",
+ },
+ },
+ Plugins: make(Plugins),
+ }
+}
+
// ComposeUpstreamName uses namespace, name, subset (optional) and port info
to compose
// the upstream name.
func ComposeUpstreamName(namespace, name, subset string, port int32) string {
@@ -484,6 +498,21 @@ func ComposeConsumerName(namespace, name string) string {
return buf.String()
}
+// ComposePluginConfigName uses namespace, name to compose
+// the route name.
+func ComposePluginConfigName(namespace, name string) string {
+ // FIXME Use sync.Pool to reuse this buffer if the upstream
+ // name composing code path is hot.
+ p := make([]byte, 0, len(namespace)+len(name)+1)
+ buf := bytes.NewBuffer(p)
+
+ buf.WriteString(namespace)
+ buf.WriteByte('_')
+ buf.WriteString(name)
+
+ return buf.String()
+}
+
// Schema represents the schema of APISIX objects.
type Schema struct {
Name string `json:"name,omitempty" yaml:"name,omitempty"`
diff --git a/samples/deploy/crd/v1/ApisixPluginConfig.yaml
b/samples/deploy/crd/v1/ApisixPluginConfig.yaml
index a3f6a20..e329c56 100644
--- a/samples/deploy/crd/v1/ApisixPluginConfig.yaml
+++ b/samples/deploy/crd/v1/ApisixPluginConfig.yaml
@@ -60,6 +60,7 @@ spec:
type: boolean
config:
type: object
+ x-kubernetes-preserve-unknown-fields: true # we have
to enable it since plugin config
required:
- name
- enable
diff --git a/samples/deploy/crd/v1/ApisixRoute.yaml
b/samples/deploy/crd/v1/ApisixRoute.yaml
index bf01d35..0622a33 100644
--- a/samples/deploy/crd/v1/ApisixRoute.yaml
+++ b/samples/deploy/crd/v1/ApisixRoute.yaml
@@ -707,6 +707,9 @@ spec:
- required: [ "subject", "op", "set" ]
websocket:
type: boolean
+ plugin_config_name:
+ type: string
+ minLength: 1
backends:
type: array
minItems: 1
diff --git a/samples/deploy/rbac/apisix_view_clusterrole.yaml
b/samples/deploy/rbac/apisix_view_clusterrole.yaml
index 6394903..db2878d 100644
--- a/samples/deploy/rbac/apisix_view_clusterrole.yaml
+++ b/samples/deploy/rbac/apisix_view_clusterrole.yaml
@@ -149,8 +149,8 @@ rules:
- apisixclusterconfigs/status
- apisixconsumers
- apisixconsumers/status
- - apisixpluginconfig
- - apisixpluginconfig/status
+ - apisixpluginconfigs
+ - apisixpluginconfigs/status
verbs:
- '*'
- apiGroups:
diff --git a/test/e2e/ingress/resourcepushing.go
b/test/e2e/ingress/resourcepushing.go
index 8d1abc1..66f8069 100644
--- a/test/e2e/ingress/resourcepushing.go
+++ b/test/e2e/ingress/resourcepushing.go
@@ -96,6 +96,8 @@ spec:
assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")
err = s.EnsureNumApisixUpstreamsCreated(1)
assert.Nil(ginkgo.GinkgoT(), err, "Checking number of
upstreams")
+ err = s.EnsureNumApisixPluginConfigCreated(0)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of
pluginConfigs")
s.NewAPISIXClient().GET("/ip").WithHeader("Host",
"httpbin.com").Expect().Status(http.StatusOK)
@@ -133,6 +135,8 @@ spec:
assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")
err = s.EnsureNumApisixUpstreamsCreated(1)
assert.Nil(ginkgo.GinkgoT(), err, "Checking number of
upstreams")
+ err = s.EnsureNumApisixPluginConfigCreated(0)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of
pluginConfigs")
s.NewAPISIXClient().GET("/ip").WithHeader("Host",
"httpbin.com").Expect().Status(http.StatusNotFound)
s.NewAPISIXClient().GET("/ip").WithHeader("Host",
"httpbin.com").WithHeader("X-Foo", "barbaz").Expect().Status(http.StatusOK)
@@ -145,6 +149,9 @@ spec:
ups, err := s.ListApisixUpstreams()
assert.Nil(ginkgo.GinkgoT(), err, "list upstreams error")
assert.Len(ginkgo.GinkgoT(), ups, 0, "upstreams nodes not
expect")
+ pluginConfigs, err := s.ListApisixPluginConfig()
+ assert.Nil(ginkgo.GinkgoT(), err, "list pluginConfigs error")
+ assert.Len(ginkgo.GinkgoT(), pluginConfigs, 0, "pluginConfigs
nodes not expect")
body := s.NewAPISIXClient().GET("/ip").WithHeader("Host",
"httpbin.com").Expect().Status(http.StatusNotFound).Body().Raw()
assert.Contains(ginkgo.GinkgoT(), body, "404 Route Not Found")
@@ -175,6 +182,8 @@ spec:
assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")
err = s.EnsureNumApisixUpstreamsCreated(1)
assert.Nil(ginkgo.GinkgoT(), err, "Checking number of
upstreams")
+ err = s.EnsureNumApisixPluginConfigCreated(0)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of
pluginConfigs")
s.NewAPISIXClient().GET("/ip").WithHeader("Host",
"httpbin.com").Expect().Status(http.StatusOK)
@@ -212,6 +221,8 @@ spec:
assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")
err = s.EnsureNumApisixUpstreamsCreated(1)
assert.Nil(ginkgo.GinkgoT(), err, "Checking number of
upstreams")
+ err = s.EnsureNumApisixPluginConfigCreated(0)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of
pluginConfigs")
s.NewAPISIXClient().GET("/ip").WithHeader("Host",
"httpbin.com").Expect().Status(http.StatusNotFound)
s.NewAPISIXClient().GET("/ip").WithHeader("Host",
"httpbin.com").WithHeader("X-Foo", "barbaz").Expect().Status(http.StatusOK)
@@ -225,6 +236,9 @@ spec:
ups, err := s.ListApisixUpstreams()
assert.Nil(ginkgo.GinkgoT(), err, "list upstreams error")
assert.Len(ginkgo.GinkgoT(), ups, 0, "upstreams nodes not
expect")
+ pluginConfigs, err := s.ListApisixPluginConfig()
+ assert.Nil(ginkgo.GinkgoT(), err, "list pluginConfigs error")
+ assert.Len(ginkgo.GinkgoT(), pluginConfigs, 0, "pluginConfigs
nodes not expect")
body := s.NewAPISIXClient().GET("/ip").WithHeader("Host",
"httpbin.com").Expect().Status(http.StatusNotFound).Body().Raw()
assert.Contains(ginkgo.GinkgoT(), body, "404 Route Not Found")
@@ -253,6 +267,7 @@ spec:
assert.Nil(ginkgo.GinkgoT(),
s.CreateResourceFromString(apisixRoute), "creating ApisixRoute")
assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixRoutesCreated(1))
assert.Nil(ginkgo.GinkgoT(),
s.EnsureNumApisixUpstreamsCreated(1))
+ assert.Nil(ginkgo.GinkgoT(),
s.EnsureNumApisixPluginConfigCreated(0))
routes, err := s.ListApisixRoutes()
assert.Nil(ginkgo.GinkgoT(), err, "listing routes in APISIX")
@@ -262,6 +277,10 @@ spec:
assert.Nil(ginkgo.GinkgoT(), err, "listing upstreams in APISIX")
assert.Len(ginkgo.GinkgoT(), upstreams, 1)
+ pluginConfigs, err := s.ListApisixPluginConfig()
+ assert.Nil(ginkgo.GinkgoT(), err, "listing pluginConfigs in
APISIX")
+ assert.Len(ginkgo.GinkgoT(), pluginConfigs, 0)
+
s.NewAPISIXClient().GET("/ip").WithHeader("Host",
"httpbin.com").Expect().Status(http.StatusOK)
apisixRoute = fmt.Sprintf(`
@@ -285,6 +304,7 @@ spec:
assert.Nil(ginkgo.GinkgoT(),
s.CreateResourceFromString(apisixRoute), "creating ApisixRoute")
assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixRoutesCreated(1))
assert.Nil(ginkgo.GinkgoT(),
s.EnsureNumApisixUpstreamsCreated(1))
+ assert.Nil(ginkgo.GinkgoT(),
s.EnsureNumApisixPluginConfigCreated(0))
newRoutes, err := s.ListApisixRoutes()
assert.Nil(ginkgo.GinkgoT(), err, "listing routes in APISIX")
@@ -292,6 +312,9 @@ spec:
newUpstreams, err := s.ListApisixUpstreams()
assert.Nil(ginkgo.GinkgoT(), err, "listing upstreams in APISIX")
assert.Len(ginkgo.GinkgoT(), newUpstreams, 1)
+ newPluginConfigs, err := s.ListApisixPluginConfig()
+ assert.Nil(ginkgo.GinkgoT(), err, "listing pluginConfigs in
APISIX")
+ assert.Len(ginkgo.GinkgoT(), newPluginConfigs, 0)
// Upstream doesn't change.
assert.Equal(ginkgo.GinkgoT(), newUpstreams[0].ID,
upstreams[0].ID)
@@ -344,6 +367,7 @@ spec:
assert.Nil(ginkgo.GinkgoT(),
s.CreateResourceFromString(apisixRoute), "creating ApisixRoute")
assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixRoutesCreated(2))
assert.Nil(ginkgo.GinkgoT(),
s.EnsureNumApisixUpstreamsCreated(1))
+ assert.Nil(ginkgo.GinkgoT(),
s.EnsureNumApisixPluginConfigCreated(0))
s.NewAPISIXClient().GET("/ip").WithHeader("Host",
"httpbin.com").Expect().
Status(http.StatusOK).
@@ -399,6 +423,7 @@ spec:
assert.Nil(ginkgo.GinkgoT(),
s.CreateResourceFromString(apisixRoute), "creating ApisixRoute")
assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixRoutesCreated(2))
assert.Nil(ginkgo.GinkgoT(),
s.EnsureNumApisixUpstreamsCreated(1))
+ assert.Nil(ginkgo.GinkgoT(),
s.EnsureNumApisixPluginConfigCreated(1))
// Hit rule1
resp := s.NewAPISIXClient().GET("/ip").WithHeader("Host",
"httpbin.com").Expect()
@@ -413,7 +438,7 @@ spec:
resp.Header("X-Request-Id").NotEmpty()
})
- ginkgo.It("verify route/upstream items", func() {
+ ginkgo.It("verify route/upstream/pluginConfig items", func() {
backendSvc, backendSvcPort := s.DefaultHTTPBackend()
apisixRoute := fmt.Sprintf(`
apiVersion: apisix.apache.org/v2beta3
@@ -437,6 +462,7 @@ spec:
assert.Nil(ginkgo.GinkgoT(),
s.CreateResourceFromString(apisixRoute), "creating ApisixRoute")
assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixRoutesCreated(1))
assert.Nil(ginkgo.GinkgoT(),
s.EnsureNumApisixUpstreamsCreated(1))
+ assert.Nil(ginkgo.GinkgoT(),
s.EnsureNumApisixPluginConfigCreated(0))
routes, err := s.ListApisixRoutes()
assert.Nil(ginkgo.GinkgoT(), err, "listing routes")
@@ -460,6 +486,10 @@ spec:
"managed-by": "apisix-ingress-controller",
})
+ pluginConfigs, err := s.ListApisixPluginConfig()
+ assert.Nil(ginkgo.GinkgoT(), err, "listing pluginConfigs")
+ assert.Len(ginkgo.GinkgoT(), pluginConfigs, 0)
+
resp := s.NewAPISIXClient().GET("/ip").WithHeader("Host",
"httpbin.com").Expect()
resp.Status(http.StatusOK)
resp.Body().Contains("origin")
@@ -525,6 +555,10 @@ spec:
assert.Equal(ginkgo.GinkgoT(), ups[0].ID, routes[0].UpstreamId)
assert.Equal(ginkgo.GinkgoT(), ups[0].ID, routes[1].UpstreamId)
+ pluginConfigs, err := s.ListApisixPluginConfig()
+ assert.Nil(ginkgo.GinkgoT(), err, "listing pluginConfigs")
+ assert.Len(ginkgo.GinkgoT(), pluginConfigs, 0)
+
resp := s.NewAPISIXClient().GET("/ip").WithHeader("Host",
"httpbin.com").Expect()
resp.Status(http.StatusOK)
resp.Body().Contains("origin")
@@ -549,6 +583,11 @@ spec:
assert.Len(ginkgo.GinkgoT(), ups, 1)
assert.Equal(ginkgo.GinkgoT(), ups[0].ID, routes[0].UpstreamId)
+ // As httpbin service is referenced by ar2, the corresponding
PluginConfig still doesn't exist.
+ pluginConfigs, err = s.ListApisixPluginConfig()
+ assert.Nil(ginkgo.GinkgoT(), err, "listing pluginConfigs")
+ assert.Len(ginkgo.GinkgoT(), pluginConfigs, 0)
+
resp = s.NewAPISIXClient().GET("/ip").WithHeader("Host",
"httpbin.com").Expect()
resp.Status(http.StatusNotFound)
resp =
s.NewAPISIXClient().GET("/status/200").WithHeader("Host",
"httpbin.com").Expect()
@@ -567,6 +606,10 @@ spec:
assert.Nil(ginkgo.GinkgoT(), err, "listing upstreams")
assert.Len(ginkgo.GinkgoT(), ups, 0)
+ pluginConfigs, err = s.ListApisixPluginConfig()
+ assert.Nil(ginkgo.GinkgoT(), err, "listing pluginConfigs")
+ assert.Len(ginkgo.GinkgoT(), pluginConfigs, 0)
+
resp =
s.NewAPISIXClient().GET("/status/200").WithHeader("Host",
"httpbin.com").Expect()
resp.Status(http.StatusNotFound)
})
diff --git a/test/e2e/plugins/plugin_config.go
b/test/e2e/plugins/plugin_config.go
new file mode 100644
index 0000000..9b4ae47
--- /dev/null
+++ b/test/e2e/plugins/plugin_config.go
@@ -0,0 +1,546 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package plugins
+
+import (
+ "fmt"
+ "net/http"
+ "time"
+
+ "github.com/onsi/ginkgo"
+ "github.com/stretchr/testify/assert"
+
+ "github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
+)
+
+var _ = ginkgo.Describe("ApisixPluginConfig", func() {
+ opts := &scaffold.Options{
+ Name: "default",
+ Kubeconfig: scaffold.GetKubeconfig(),
+ APISIXConfigPath: "testdata/apisix-gw-config.yaml",
+ IngressAPISIXReplicas: 1,
+ HTTPBinServicePort: 80,
+ APISIXRouteVersion: "apisix.apache.org/v2beta3",
+ }
+ s := scaffold.NewScaffold(opts)
+ ginkgo.It("add crd from definition", func() {
+ backendSvc, backendPorts := s.DefaultHTTPBackend()
+ apc := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixPluginConfig
+metadata:
+ name: echo-and-cors-apc
+spec:
+ plugins:
+ - name: echo
+ enable: true
+ config:
+ before_body: "This is the preface"
+ after_body: "This is the epilogue"
+ headers:
+ X-Foo: v1
+ X-Foo2: v2
+ - name: cors
+ enable: true
+`)
+ assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(apc))
+
+ err := s.EnsureNumApisixPluginConfigCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of
pluginConfigs")
+
+ time.Sleep(time.Second * 3)
+
+ ar := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixRoute
+metadata:
+ name: httpbin-route
+spec:
+ http:
+ - name: rule1
+ match:
+ hosts:
+ - httpbin.org
+ paths:
+ - /ip
+ backends:
+ - serviceName: %s
+ servicePort: %d
+ weight: 10
+ plugin_config_name: echo-and-cors-apc
+`, backendSvc, backendPorts[0])
+ assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ar))
+
+ err = s.EnsureNumApisixRoutesCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")
+
+ time.Sleep(3 * time.Second)
+ pcs, err := s.ListApisixPluginConfig()
+ assert.Nil(ginkgo.GinkgoT(), err, nil, "listing pluginConfigs")
+ assert.Len(ginkgo.GinkgoT(), pcs, 1)
+ assert.Len(ginkgo.GinkgoT(), pcs[0].Plugins, 2)
+
+ resp := s.NewAPISIXClient().GET("/ip").WithHeader("Host",
"httpbin.org").Expect()
+ resp.Status(http.StatusOK)
+ resp.Header("X-Foo").Equal("v1")
+ resp.Header("X-Foo2").Equal("v2")
+ resp.Header("Access-Control-Allow-Origin").Equal("*")
+ resp.Header("Access-Control-Allow-Methods").Equal("*")
+ resp.Header("Access-Control-Allow-Headers").Equal("*")
+ resp.Header("Access-Control-Expose-Headers").Equal("*")
+ resp.Header("Access-Control-Max-Age").Equal("5")
+ resp.Body().Contains("This is the preface")
+ resp.Body().Contains("origin")
+ resp.Body().Contains("This is the epilogue")
+ })
+
+ ginkgo.It("ApisixPluginConfig replace body", func() {
+ backendSvc, backendPorts := s.DefaultHTTPBackend()
+ apc := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixPluginConfig
+metadata:
+ name: test-apc-1
+spec:
+ plugins:
+ - name: echo
+ enable: true
+ config:
+ body: "my custom body"
+`)
+ assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(apc))
+
+ ar := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixRoute
+metadata:
+ name: httpbin-route
+spec:
+ http:
+ - name: rule1
+ match:
+ hosts:
+ - httpbin.org
+ paths:
+ - /ip
+ backends:
+ - serviceName: %s
+ servicePort: %d
+ weight: 10
+ plugin_config_name: test-apc-1
+`, backendSvc, backendPorts[0])
+
+ assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ar))
+
+ err := s.EnsureNumApisixUpstreamsCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of
upstreams")
+ err = s.EnsureNumApisixPluginConfigCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of
pluginConfigs")
+ err = s.EnsureNumApisixRoutesCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")
+
+ resp := s.NewAPISIXClient().GET("/ip").WithHeader("Host",
"httpbin.org").Expect()
+ resp.Status(http.StatusOK)
+ resp.Body().Equal("my custom body")
+ })
+
+ ginkgo.It("disable plugin", func() {
+ backendSvc, backendPorts := s.DefaultHTTPBackend()
+ apc := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixPluginConfig
+metadata:
+ name: test-apc-1
+spec:
+ plugins:
+ - name: echo
+ enable: false
+ config:
+ body: "my custom body"
+`)
+ assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(apc))
+
+ ar := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixRoute
+metadata:
+ name: httpbin-route
+spec:
+ http:
+ - name: rule1
+ match:
+ hosts:
+ - httpbin.org
+ paths:
+ - /ip
+ backends:
+ - serviceName: %s
+ servicePort: %d
+ weight: 10
+ plugin_config_name: test-apc-1
+`, backendSvc, backendPorts[0])
+
+ assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ar))
+
+ time.Sleep(6 * time.Second)
+ err := s.EnsureNumApisixUpstreamsCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of
upstreams")
+ err = s.EnsureNumApisixPluginConfigCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of
pluginConfigs")
+ err = s.EnsureNumApisixRoutesCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")
+
+ resp := s.NewAPISIXClient().GET("/ip").WithHeader("Host",
"httpbin.org").Expect()
+ resp.Status(http.StatusOK)
+ resp.Body().Contains("origin")
+ resp.Body().NotContains("my custom body")
+ })
+
+ ginkgo.It("enable plugin and then delete it", func() {
+ backendSvc, backendPorts := s.DefaultHTTPBackend()
+ apc := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixPluginConfig
+metadata:
+ name: test-apc-1
+spec:
+ plugins:
+ - name: echo
+ enable: true
+ config:
+ body: "my custom body"
+`)
+ assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(apc))
+
+ ar := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixRoute
+metadata:
+ name: httpbin-route
+spec:
+ http:
+ - name: rule1
+ match:
+ hosts:
+ - httpbin.org
+ paths:
+ - /ip
+ backends:
+ - serviceName: %s
+ servicePort: %d
+ weight: 10
+ plugin_config_name: test-apc-1
+`, backendSvc, backendPorts[0])
+
+ assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ar))
+
+ err := s.EnsureNumApisixUpstreamsCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of
upstreams")
+ err = s.EnsureNumApisixPluginConfigCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of
pluginConfigs")
+ err = s.EnsureNumApisixRoutesCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")
+
+ resp := s.NewAPISIXClient().GET("/ip").WithHeader("Host",
"httpbin.org").Expect()
+ resp.Status(http.StatusOK)
+ resp.Body().Equal("my custom body")
+
+ apc = fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixPluginConfig
+metadata:
+ name: test-apc-1
+spec:
+ plugins:
+ - name: echo
+ enable: false
+ config:
+ body: "my custom body"
+`)
+ assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(apc))
+
+ err = s.EnsureNumApisixUpstreamsCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of
upstreams")
+ err = s.EnsureNumApisixPluginConfigCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of
pluginConfigs")
+ err = s.EnsureNumApisixRoutesCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")
+
+ resp = s.NewAPISIXClient().GET("/ip").WithHeader("Host",
"httpbin.org").Expect()
+ resp.Status(http.StatusOK)
+ resp.Body().NotContains("my custom body")
+ resp.Body().Contains("origin")
+ })
+
+ ginkgo.It("empty config", func() {
+ backendSvc, backendPorts := s.DefaultHTTPBackend()
+ apc := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixPluginConfig
+metadata:
+ name: test-apc-1
+spec:
+ plugins:
+ - name: cors
+ enable: true
+`)
+ assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(apc))
+ ar := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixRoute
+metadata:
+ name: httpbin-route
+spec:
+ http:
+ - name: rule1
+ match:
+ hosts:
+ - httpbin.org
+ paths:
+ - /ip
+ backends:
+ - serviceName: %s
+ servicePort: %d
+ weight: 10
+ plugin_config_name: test-apc-1
+`, backendSvc, backendPorts[0])
+
+ assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ar))
+ err := s.EnsureNumApisixUpstreamsCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of
upstreams")
+ err = s.EnsureNumApisixPluginConfigCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of
pluginConfigs")
+ err = s.EnsureNumApisixRoutesCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")
+
+ resp := s.NewAPISIXClient().GET("/ip").WithHeader("Host",
"httpbin.org").Expect()
+ resp.Status(http.StatusOK)
+ resp.Header("Access-Control-Allow-Origin").Equal("*")
+ resp.Header("Access-Control-Allow-Methods").Equal("*")
+ resp.Header("Access-Control-Allow-Headers").Equal("*")
+ resp.Header("Access-Control-Expose-Headers").Equal("*")
+ resp.Header("Access-Control-Max-Age").Equal("5")
+ resp.Body().Contains("origin")
+ })
+
+ ginkgo.It("finer granularity config", func() {
+ backendSvc, backendPorts := s.DefaultHTTPBackend()
+ apc := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixPluginConfig
+metadata:
+ name: test-apc-1
+spec:
+ plugins:
+ - name: cors
+ enable: true
+ config:
+ allow_origins: http://foo.bar.org
+ allow_methods: "GET,POST"
+ max_age: 3600
+ expose_headers: x-foo,x-baz
+ allow_headers: x-from-ingress
+ allow_credential: true
+`)
+ assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(apc))
+ ar := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixRoute
+metadata:
+ name: httpbin-route
+spec:
+ http:
+ - name: rule1
+ match:
+ hosts:
+ - httpbin.org
+ paths:
+ - /ip
+ backends:
+ - serviceName: %s
+ servicePort: %d
+ weight: 10
+ plugin_config_name: test-apc-1
+`, backendSvc, backendPorts[0])
+
+ assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ar))
+ err := s.EnsureNumApisixUpstreamsCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of
upstreams")
+ err = s.EnsureNumApisixPluginConfigCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of
pluginConfigs")
+ err = s.EnsureNumApisixRoutesCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")
+
+ resp := s.NewAPISIXClient().GET("/ip").
+ WithHeader("Host", "httpbin.org").
+ WithHeader("Origin", "http://foo.bar.org").
+ Expect()
+ resp.Status(http.StatusOK)
+
resp.Header("Access-Control-Allow-Origin").Equal("http://foo.bar.org")
+ resp.Header("Access-Control-Allow-Methods").Equal("GET,POST")
+
resp.Header("Access-Control-Allow-Headers").Equal("x-from-ingress")
+
resp.Header("Access-Control-Expose-Headers").Equal("x-foo,x-baz")
+ resp.Header("Access-Control-Max-Age").Equal("3600")
+ resp.Header("Access-Control-Allow-Credentials").Equal("true")
+ resp.Body().Contains("origin")
+
+ resp = s.NewAPISIXClient().GET("/ip").
+ WithHeader("Host", "httpbin.org").
+ WithHeader("Origin", "http://foo.bar2.org").
+ Expect()
+ resp.Header("Access-Control-Allow-Methods").Empty()
+ resp.Header("Access-Control-Allow-Headers").Empty()
+ resp.Header("Access-Control-Expose-Headers").Empty()
+ resp.Header("Access-Control-Max-Age").Empty()
+ // httpbin set it by itself.
+ //resp.Header("Access-Control-Allow-Credentials").Empty()
+ resp.Body().Contains("origin")
+ })
+
+ ginkgo.It("disable plugin", func() {
+ backendSvc, backendPorts := s.DefaultHTTPBackend()
+ apc := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixPluginConfig
+metadata:
+ name: test-apc-1
+spec:
+ plugins:
+ - name: cors
+ enable: false
+`)
+ assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(apc))
+ ar := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixRoute
+metadata:
+ name: httpbin-route
+spec:
+ http:
+ - name: rule1
+ match:
+ hosts:
+ - httpbin.org
+ paths:
+ - /ip
+ backends:
+ - serviceName: %s
+ servicePort: %d
+ weight: 10
+ plugin_config_name: test-apc-1
+`, backendSvc, backendPorts[0])
+
+ assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ar))
+ err := s.EnsureNumApisixUpstreamsCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of
upstreams")
+ err = s.EnsureNumApisixPluginConfigCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of
pluginConfigs")
+ err = s.EnsureNumApisixRoutesCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")
+
+ resp := s.NewAPISIXClient().GET("/ip").WithHeader("Host",
"httpbin.org").Expect()
+ resp.Status(http.StatusOK)
+ // httpbin sets this header by itself.
+ //resp.Header("Access-Control-Allow-Origin").Empty()
+ resp.Header("Access-Control-Allow-Methods").Empty()
+ resp.Header("Access-Control-Allow-Headers").Empty()
+ resp.Header("Access-Control-Expose-Headers").Empty()
+ resp.Header("Access-Control-Max-Age").Empty()
+ resp.Body().Contains("origin")
+ })
+
+ ginkgo.It("enable plugin and then delete it", func() {
+ backendSvc, backendPorts := s.DefaultHTTPBackend()
+ apc := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixPluginConfig
+metadata:
+ name: test-apc-1
+spec:
+ plugins:
+ - name: cors
+ enable: true
+`)
+ assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(apc))
+ ar := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixRoute
+metadata:
+ name: httpbin-route
+spec:
+ http:
+ - name: rule1
+ match:
+ hosts:
+ - httpbin.org
+ paths:
+ - /ip
+ backends:
+ - serviceName: %s
+ servicePort: %d
+ weight: 10
+ plugin_config_name: test-apc-1
+`, backendSvc, backendPorts[0])
+
+ assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ar))
+ err := s.EnsureNumApisixUpstreamsCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of
upstreams")
+ err = s.EnsureNumApisixPluginConfigCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of
pluginConfigs")
+ err = s.EnsureNumApisixRoutesCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")
+
+ resp := s.NewAPISIXClient().GET("/ip").WithHeader("Host",
"httpbin.org").Expect()
+ resp.Status(http.StatusOK)
+
+ resp.Header("Access-Control-Allow-Origin").Equal("*")
+ resp.Header("Access-Control-Allow-Methods").Equal("*")
+ resp.Header("Access-Control-Allow-Headers").Equal("*")
+ resp.Header("Access-Control-Expose-Headers").Equal("*")
+ resp.Header("Access-Control-Max-Age").Equal("5")
+ resp.Body().Contains("origin")
+
+ apc = fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixPluginConfig
+metadata:
+ name: test-apc-1
+spec:
+ plugins:
+ - name: cors
+ enable: false
+`)
+ assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(apc))
+
+ assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ar))
+ err = s.EnsureNumApisixUpstreamsCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of
upstreams")
+ err = s.EnsureNumApisixPluginConfigCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of
pluginConfigs")
+ err = s.EnsureNumApisixRoutesCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")
+
+ resp = s.NewAPISIXClient().GET("/ip").WithHeader("Host",
"httpbin.org").Expect()
+ resp.Status(http.StatusOK)
+
+ // httpbin sets this header by itself.
+ //resp.Header("Access-Control-Allow-Origin").Empty()
+ resp.Header("Access-Control-Allow-Methods").Empty()
+ resp.Header("Access-Control-Allow-Headers").Empty()
+ resp.Header("Access-Control-Expose-Headers").Empty()
+ resp.Header("Access-Control-Max-Age").Empty()
+ resp.Body().Contains("origin")
+ })
+})
diff --git a/test/e2e/scaffold/ingress.go b/test/e2e/scaffold/ingress.go
index 99b9ec3..6c5181c 100644
--- a/test/e2e/scaffold/ingress.go
+++ b/test/e2e/scaffold/ingress.go
@@ -168,8 +168,8 @@ rules:
- apisixclusterconfigs/status
- apisixconsumers
- apisixconsumers/status
- - apisixpluginconfig
- - apisixpluginconfig/status
+ - apisixpluginconfigs
+ - apisixpluginconfigs/status
verbs:
- '*'
- apiGroups:
diff --git a/test/e2e/scaffold/k8s.go b/test/e2e/scaffold/k8s.go
index 8362144..d6fde3d 100644
--- a/test/e2e/scaffold/k8s.go
+++ b/test/e2e/scaffold/k8s.go
@@ -240,6 +240,17 @@ func (s *Scaffold) EnsureNumApisixUpstreamsCreated(desired
int) error {
return s.ensureNumApisixCRDsCreated(u.String(), desired)
}
+// EnsureNumApisixPluginConfigCreated waits until desired number of
PluginConfig are created in
+// APISIX cluster.
+func (s *Scaffold) EnsureNumApisixPluginConfigCreated(desired int) error {
+ u := url.URL{
+ Scheme: "http",
+ Host: s.apisixAdminTunnel.Endpoint(),
+ Path: "/apisix/admin/plugin_configs",
+ }
+ return s.ensureNumApisixCRDsCreated(u.String(), desired)
+}
+
// GetServerInfo collect server info from "/v1/server_info" (Control API)
exposed by server-info plugin
func (s *Scaffold) GetServerInfo() (map[string]interface{}, error) {
u := url.URL{
@@ -397,6 +408,28 @@ func (s *Scaffold) ListApisixSsl() ([]*v1.Ssl, error) {
return cli.Cluster("").SSL().List(context.TODO())
}
+// ListApisixRoutes list all pluginConfigs from APISIX.
+func (s *Scaffold) ListApisixPluginConfig() ([]*v1.PluginConfig, error) {
+ u := url.URL{
+ Scheme: "http",
+ Host: s.apisixAdminTunnel.Endpoint(),
+ Path: "/apisix/admin",
+ }
+ cli, err := apisix.NewClient()
+ if err != nil {
+ return nil, err
+ }
+ err = cli.AddCluster(context.Background(), &apisix.ClusterOptions{
+ BaseURL: u.String(),
+ AdminKey: s.opts.APISIXAdminAPIKey,
+ MetricsCollector: metrics.NewPrometheusCollector(),
+ })
+ if err != nil {
+ return nil, err
+ }
+ return cli.Cluster("").PluginConfig().List(context.TODO())
+}
+
func (s *Scaffold) newAPISIXTunnels() error {
var (
adminNodePort int