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

tokers 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 0460a09  feat: add ApisixClusterConfig CRD and global_rule type, 
client (#411)
0460a09 is described below

commit 0460a092004d5029c3936be91fbfe667291f73ed
Author: Alex Zhang <zchao1...@gmail.com>
AuthorDate: Thu May 6 10:29:04 2021 +0800

    feat: add ApisixClusterConfig CRD and global_rule type, client (#411)
---
 pkg/apisix/apisix.go                               |  10 +
 pkg/apisix/cache/cache.go                          |   8 +
 pkg/apisix/cache/memdb.go                          |  28 +++
 pkg/apisix/cache/memdb_test.go                     |  43 ++++
 pkg/apisix/cache/schema.go                         |  10 +
 pkg/apisix/cluster.go                              |  32 +++
 pkg/apisix/global_rule.go                          | 223 +++++++++++++++++++++
 pkg/apisix/global_rule_test.go                     | 199 ++++++++++++++++++
 pkg/apisix/nonexistentclient.go                    |  28 +++
 pkg/apisix/resource.go                             |  10 +
 pkg/kube/apisix/apis/config/v2alpha1/types.go      |  72 +++++++
 .../apis/config/v2alpha1/zz_generated.deepcopy.go  | 152 ++++++++++++++
 .../typed/config/v2alpha1/apisixclusterconfig.go   | 168 ++++++++++++++++
 .../typed/config/v2alpha1/config_client.go         |   5 +
 .../v2alpha1/fake/fake_apisixclusterconfig.go      | 122 +++++++++++
 .../config/v2alpha1/fake/fake_config_client.go     |   4 +
 .../typed/config/v2alpha1/generated_expansion.go   |   2 +
 .../config/v2alpha1/apisixclusterconfig.go         |  89 ++++++++
 .../externalversions/config/v2alpha1/interface.go  |   7 +
 .../client/informers/externalversions/generic.go   |   2 +
 .../listers/config/v2alpha1/apisixclusterconfig.go |  68 +++++++
 .../listers/config/v2alpha1/expansion_generated.go |   4 +
 pkg/types/apisix/v1/types.go                       |   9 +-
 pkg/types/apisix/v1/zz_generated.deepcopy.go       |  17 ++
 .../deploy/crd/v1beta1/ApisixClusterConfig.yaml    |  71 +++++++
 samples/deploy/crd/v1beta1/kustomization.yaml      |   1 +
 26 files changed, 1383 insertions(+), 1 deletion(-)

diff --git a/pkg/apisix/apisix.go b/pkg/apisix/apisix.go
index 0e88f43..e407b7e 100644
--- a/pkg/apisix/apisix.go
+++ b/pkg/apisix/apisix.go
@@ -87,6 +87,16 @@ type StreamRoute interface {
        Update(context.Context, *v1.StreamRoute) (*v1.StreamRoute, error)
 }
 
+// GlobalRule is the specific client interface to take over the create, update,
+// list and delete for APISIX's Global Rule resource.
+type GlobalRule interface {
+       Get(context.Context, string) (*v1.GlobalRule, error)
+       List(context.Context) ([]*v1.GlobalRule, error)
+       Create(context.Context, *v1.GlobalRule) (*v1.GlobalRule, error)
+       Delete(context.Context, *v1.GlobalRule) error
+       Update(context.Context, *v1.GlobalRule) (*v1.GlobalRule, error)
+}
+
 type apisix struct {
        nonExistentCluster Cluster
        clusters           map[string]Cluster
diff --git a/pkg/apisix/cache/cache.go b/pkg/apisix/cache/cache.go
index c527157..c213f3e 100644
--- a/pkg/apisix/cache/cache.go
+++ b/pkg/apisix/cache/cache.go
@@ -31,6 +31,8 @@ type Cache interface {
        InsertUpstream(*v1.Upstream) error
        // InsertStreamRoute adds or updates stream_route to cache.
        InsertStreamRoute(*v1.StreamRoute) error
+       // InsertGlobalRule adds or updates global_rule to cache.
+       InsertGlobalRule(*v1.GlobalRule) error
 
        // GetRoute finds the route from cache according to the primary index 
(id).
        GetRoute(string) (*v1.Route, error)
@@ -40,6 +42,8 @@ type Cache interface {
        GetUpstream(string) (*v1.Upstream, error)
        // GetStreamRoute finds the stream_route from cache according to the 
primary index (id).
        GetStreamRoute(string) (*v1.StreamRoute, error)
+       // GetGlobalRule finds the global_rule from cache according to the 
primary index (id).
+       GetGlobalRule(string) (*v1.GlobalRule, error)
 
        // ListRoutes lists all routes in cache.
        ListRoutes() ([]*v1.Route, error)
@@ -49,6 +53,8 @@ type Cache interface {
        ListUpstreams() ([]*v1.Upstream, error)
        // ListStreamRoutes lists all stream_route in cache.
        ListStreamRoutes() ([]*v1.StreamRoute, error)
+       // ListGlobalRules lists all global_rule objects in cache.
+       ListGlobalRules() ([]*v1.GlobalRule, error)
 
        // DeleteRoute deletes the specified route in cache.
        DeleteRoute(*v1.Route) error
@@ -58,4 +64,6 @@ type Cache interface {
        DeleteUpstream(*v1.Upstream) error
        // DeleteStreamRoute deletes the specified stream_route in cache.
        DeleteStreamRoute(*v1.StreamRoute) error
+       // DeleteGlobalRule deletes the specified stream_route in cache.
+       DeleteGlobalRule(*v1.GlobalRule) error
 }
diff --git a/pkg/apisix/cache/memdb.go b/pkg/apisix/cache/memdb.go
index 481cfe7..18de938 100644
--- a/pkg/apisix/cache/memdb.go
+++ b/pkg/apisix/cache/memdb.go
@@ -62,6 +62,10 @@ func (c *dbCache) InsertStreamRoute(sr *v1.StreamRoute) 
error {
        return c.insert("stream_route", sr.DeepCopy())
 }
 
+func (c *dbCache) InsertGlobalRule(gr *v1.GlobalRule) error {
+       return c.insert("global_rule", gr.DeepCopy())
+}
+
 func (c *dbCache) insert(table string, obj interface{}) error {
        txn := c.db.Txn(true)
        defer txn.Abort()
@@ -104,6 +108,14 @@ func (c *dbCache) GetStreamRoute(id string) 
(*v1.StreamRoute, error) {
        return obj.(*v1.StreamRoute).DeepCopy(), nil
 }
 
+func (c *dbCache) GetGlobalRule(id string) (*v1.GlobalRule, error) {
+       obj, err := c.get("global_rule", id)
+       if err != nil {
+               return nil, err
+       }
+       return obj.(*v1.GlobalRule).DeepCopy(), nil
+}
+
 func (c *dbCache) get(table, id string) (interface{}, error) {
        txn := c.db.Txn(false)
        defer txn.Abort()
@@ -168,6 +180,18 @@ func (c *dbCache) ListStreamRoutes() ([]*v1.StreamRoute, 
error) {
        return streamRoutes, nil
 }
 
+func (c *dbCache) ListGlobalRules() ([]*v1.GlobalRule, error) {
+       raws, err := c.list("global_rule")
+       if err != nil {
+               return nil, err
+       }
+       globalRules := make([]*v1.GlobalRule, 0, len(raws))
+       for _, raw := range raws {
+               globalRules = append(globalRules, 
raw.(*v1.GlobalRule).DeepCopy())
+       }
+       return globalRules, nil
+}
+
 func (c *dbCache) list(table string) ([]interface{}, error) {
        txn := c.db.Txn(false)
        defer txn.Abort()
@@ -201,6 +225,10 @@ func (c *dbCache) DeleteStreamRoute(sr *v1.StreamRoute) 
error {
        return c.delete("stream_route", sr)
 }
 
+func (c *dbCache) DeleteGlobalRule(gr *v1.GlobalRule) error {
+       return c.delete("global_rule", gr)
+}
+
 func (c *dbCache) delete(table string, obj interface{}) error {
        txn := c.db.Txn(true)
        defer txn.Abort()
diff --git a/pkg/apisix/cache/memdb_test.go b/pkg/apisix/cache/memdb_test.go
index 8e449ea..91858d7 100644
--- a/pkg/apisix/cache/memdb_test.go
+++ b/pkg/apisix/cache/memdb_test.go
@@ -259,3 +259,46 @@ func TestMemDBCacheStreamRoute(t *testing.T) {
        }
        assert.Error(t, ErrNotFound, c.DeleteStreamRoute(r4))
 }
+
+func TestMemDBCacheGlobalRule(t *testing.T) {
+       c, err := NewMemDBCache()
+       assert.Nil(t, err, "NewMemDBCache")
+
+       gr1 := &v1.GlobalRule{
+               ID: "1",
+       }
+       assert.Nil(t, c.InsertGlobalRule(gr1), "inserting global rule 1")
+
+       gr, err := c.GetGlobalRule("1")
+       assert.Nil(t, err)
+       assert.Equal(t, gr1, gr)
+
+       gr2 := &v1.GlobalRule{
+               ID: "2",
+       }
+       gr3 := &v1.GlobalRule{
+               ID: "3",
+       }
+       assert.Nil(t, c.InsertGlobalRule(gr2), "inserting global_rule r2")
+       assert.Nil(t, c.InsertGlobalRule(gr3), "inserting global_rule r3")
+
+       gr, err = c.GetGlobalRule("3")
+       assert.Nil(t, err)
+       assert.Equal(t, gr, gr)
+
+       assert.Nil(t, c.DeleteGlobalRule(gr), "delete global_rule r3")
+
+       grs, err := c.ListGlobalRules()
+       assert.Nil(t, err, "listing global rules")
+
+       if grs[0].ID > grs[1].ID {
+               grs[0], grs[1] = grs[1], grs[0]
+       }
+       assert.Equal(t, grs[0], gr1)
+       assert.Equal(t, grs[1], gr2)
+
+       gr4 := &v1.GlobalRule{
+               ID: "4",
+       }
+       assert.Error(t, ErrNotFound, c.DeleteGlobalRule(gr4))
+}
diff --git a/pkg/apisix/cache/schema.go b/pkg/apisix/cache/schema.go
index 9db4ae7..c3e8877 100644
--- a/pkg/apisix/cache/schema.go
+++ b/pkg/apisix/cache/schema.go
@@ -86,6 +86,16 @@ var (
                                        },
                                },
                        },
+                       "global_rule": {
+                               Name: "global_rule",
+                               Indexes: map[string]*memdb.IndexSchema{
+                                       "id": {
+                                               Name:    "id",
+                                               Unique:  true,
+                                               Indexer: 
&memdb.StringFieldIndex{Field: "ID"},
+                                       },
+                               },
+                       },
                },
        }
 )
diff --git a/pkg/apisix/cluster.go b/pkg/apisix/cluster.go
index 362d5d5..bc31ee8 100644
--- a/pkg/apisix/cluster.go
+++ b/pkg/apisix/cluster.go
@@ -73,6 +73,7 @@ type cluster struct {
        upstream     Upstream
        ssl          SSL
        streamRoute  StreamRoute
+       globalRules  GlobalRule
 }
 
 func newCluster(o *ClusterOptions) (Cluster, error) {
@@ -103,6 +104,7 @@ func newCluster(o *ClusterOptions) (Cluster, error) {
        c.upstream = newUpstreamClient(c)
        c.ssl = newSSLClient(c)
        c.streamRoute = newStreamRouteClient(c)
+       c.globalRules = newGlobalRuleClient(c)
 
        go c.syncCache()
 
@@ -167,6 +169,16 @@ func (c *cluster) syncCacheOnce() (bool, error) {
                log.Errorf("failed to list ssl in APISIX: %s", err)
                return false, err
        }
+       streamRoutes, err := c.streamRoute.List(context.TODO())
+       if err != nil {
+               log.Errorf("failed to list stream_routes in APISIX: %s", err)
+               return false, err
+       }
+       globalRules, err := c.globalRules.List(context.TODO())
+       if err != nil {
+               log.Errorf("failed to list global_rules in APISIX: %s", err)
+               return false, err
+       }
 
        for _, r := range routes {
                if err := c.cache.InsertRoute(r); err != nil {
@@ -198,6 +210,26 @@ func (c *cluster) syncCacheOnce() (bool, error) {
                        return false, err
                }
        }
+       for _, sr := range streamRoutes {
+               if err := c.cache.InsertStreamRoute(sr); err != nil {
+                       log.Errorw("failed to insert stream_route to cache",
+                               zap.Any("stream_route", sr),
+                               zap.String("cluster", c.name),
+                               zap.String("error", err.Error()),
+                       )
+                       return false, err
+               }
+       }
+       for _, gr := range globalRules {
+               if err := c.cache.InsertGlobalRule(gr); err != nil {
+                       log.Errorw("failed to insert global_rule to cache",
+                               zap.Any("global_rule", gr),
+                               zap.String("cluster", c.name),
+                               zap.String("error", err.Error()),
+                       )
+                       return false, err
+               }
+       }
        return true, nil
 }
 
diff --git a/pkg/apisix/global_rule.go b/pkg/apisix/global_rule.go
new file mode 100644
index 0000000..7060354
--- /dev/null
+++ b/pkg/apisix/global_rule.go
@@ -0,0 +1,223 @@
+// 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 apisix
+
+import (
+       "bytes"
+       "context"
+       "encoding/json"
+
+       "github.com/apache/apisix-ingress-controller/pkg/apisix/cache"
+       "github.com/apache/apisix-ingress-controller/pkg/id"
+       "github.com/apache/apisix-ingress-controller/pkg/log"
+       v1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
+       "go.uber.org/zap"
+)
+
+type globalRuleClient struct {
+       url     string
+       cluster *cluster
+}
+
+func newGlobalRuleClient(c *cluster) GlobalRule {
+       return &globalRuleClient{
+               url:     c.baseURL + "/global_rules",
+               cluster: c,
+       }
+}
+
+// Get returns the GlobalRule.
+// FIXME, currently if caller pass a non-existent resource, the Get always 
passes
+// through cache.
+func (r *globalRuleClient) Get(ctx context.Context, name string) 
(*v1.GlobalRule, error) {
+       log.Debugw("try to look up global_rule",
+               zap.String("name", name),
+               zap.String("url", r.url),
+               zap.String("cluster", "default"),
+       )
+       rid := id.GenID(name)
+       globalRule, err := r.cluster.cache.GetGlobalRule(rid)
+       if err == nil {
+               return globalRule, nil
+       }
+       if err != cache.ErrNotFound {
+               log.Errorw("failed to find global_rule in cache, will try to 
lookup from APISIX",
+                       zap.String("name", name),
+                       zap.Error(err),
+               )
+       } else {
+               log.Debugw("failed to find global_rule in cache, will try to 
lookup from APISIX",
+                       zap.String("name", name),
+                       zap.Error(err),
+               )
+       }
+
+       // TODO Add mutex here to avoid dog-pile effect.
+       url := r.url + "/" + rid
+       resp, err := r.cluster.getResource(ctx, url)
+       if err != nil {
+               if err == cache.ErrNotFound {
+                       log.Warnw("global_rule not found",
+                               zap.String("name", name),
+                               zap.String("url", url),
+                               zap.String("cluster", "default"),
+                       )
+               } else {
+                       log.Errorw("failed to get global_rule from APISIX",
+                               zap.String("name", name),
+                               zap.String("url", url),
+                               zap.String("cluster", "default"),
+                               zap.Error(err),
+                       )
+               }
+               return nil, err
+       }
+
+       globalRule, err = resp.Item.globalRule()
+       if err != nil {
+               log.Errorw("failed to convert global_rule item",
+                       zap.String("url", r.url),
+                       zap.String("global_rule_key", resp.Item.Key),
+                       zap.String("global_rule_value", 
string(resp.Item.Value)),
+                       zap.Error(err),
+               )
+               return nil, err
+       }
+
+       if err := r.cluster.cache.InsertGlobalRule(globalRule); err != nil {
+               log.Errorf("failed to reflect global_rule create to cache: %s", 
err)
+               return nil, err
+       }
+       return globalRule, nil
+}
+
+// List is only used in cache warming up. So here just pass through
+// to APISIX.
+func (r *globalRuleClient) List(ctx context.Context) ([]*v1.GlobalRule, error) 
{
+       log.Debugw("try to list global_rules in APISIX",
+               zap.String("cluster", "default"),
+               zap.String("url", r.url),
+       )
+       globalRuleItems, err := r.cluster.listResource(ctx, r.url)
+       if err != nil {
+               log.Errorf("failed to list global_rules: %s", err)
+               return nil, err
+       }
+
+       var items []*v1.GlobalRule
+       for i, item := range globalRuleItems.Node.Items {
+               globalRule, err := item.globalRule()
+               if err != nil {
+                       log.Errorw("failed to convert global_rule item",
+                               zap.String("url", r.url),
+                               zap.String("global_rule_key", item.Key),
+                               zap.String("global_rule_value", 
string(item.Value)),
+                               zap.Error(err),
+                       )
+                       return nil, err
+               }
+
+               items = append(items, globalRule)
+               log.Debugf("list global_rule #%d, body: %s", i, 
string(item.Value))
+       }
+
+       return items, nil
+}
+
+func (r *globalRuleClient) Create(ctx context.Context, obj *v1.GlobalRule) 
(*v1.GlobalRule, error) {
+       log.Debugw("try to create global_rule",
+               zap.String("id", obj.ID),
+               zap.Any("plugins", obj.Plugins),
+               zap.String("cluster", "default"),
+               zap.String("url", r.url),
+       )
+
+       if err := r.cluster.HasSynced(ctx); err != nil {
+               return nil, err
+       }
+       data, err := json.Marshal(obj)
+       if err != nil {
+               return nil, err
+       }
+
+       url := r.url + "/" + obj.ID
+       log.Debugw("creating global_rule", zap.ByteString("body", data), 
zap.String("url", url))
+       resp, err := r.cluster.createResource(ctx, url, bytes.NewReader(data))
+       if err != nil {
+               log.Errorf("failed to create global_rule: %s", err)
+               return nil, err
+       }
+
+       globalRules, err := resp.Item.globalRule()
+       if err != nil {
+               return nil, err
+       }
+       if err := r.cluster.cache.InsertGlobalRule(globalRules); err != nil {
+               log.Errorf("failed to reflect global_rules create to cache: 
%s", err)
+               return nil, err
+       }
+       return globalRules, nil
+}
+
+func (r *globalRuleClient) Delete(ctx context.Context, obj *v1.GlobalRule) 
error {
+       log.Debugw("try to delete global_rule",
+               zap.String("id", obj.ID),
+               zap.String("cluster", "default"),
+               zap.String("url", r.url),
+       )
+       if err := r.cluster.HasSynced(ctx); err != nil {
+               return err
+       }
+       url := r.url + "/" + obj.ID
+       if err := r.cluster.deleteResource(ctx, url); err != nil {
+               return err
+       }
+       if err := r.cluster.cache.DeleteGlobalRule(obj); err != nil {
+               log.Errorf("failed to reflect global_rule delete to cache: %s", 
err)
+               return err
+       }
+       return nil
+}
+
+func (r *globalRuleClient) Update(ctx context.Context, obj *v1.GlobalRule) 
(*v1.GlobalRule, error) {
+       log.Debugw("try to update global_rule",
+               zap.String("id", obj.ID),
+               zap.Any("plugins", obj.Plugins),
+               zap.String("cluster", "default"),
+               zap.String("url", r.url),
+       )
+       if err := r.cluster.HasSynced(ctx); err != nil {
+               return nil, err
+       }
+       body, err := json.Marshal(obj)
+       if err != nil {
+               return nil, err
+       }
+       url := r.url + "/" + obj.ID
+       log.Debugw("updating global_rule", zap.ByteString("body", body), 
zap.String("url", url))
+       resp, err := r.cluster.updateResource(ctx, url, bytes.NewReader(body))
+       if err != nil {
+               return nil, err
+       }
+       globalRule, err := resp.Item.globalRule()
+       if err != nil {
+               return nil, err
+       }
+       if err := r.cluster.cache.InsertGlobalRule(globalRule); err != nil {
+               log.Errorf("failed to reflect global_rule update to cache: %s", 
err)
+               return nil, err
+       }
+       return globalRule, nil
+}
diff --git a/pkg/apisix/global_rule_test.go b/pkg/apisix/global_rule_test.go
new file mode 100644
index 0000000..01a4d38
--- /dev/null
+++ b/pkg/apisix/global_rule_test.go
@@ -0,0 +1,199 @@
+// 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 apisix
+
+import (
+       "context"
+       "encoding/json"
+       "fmt"
+       "io/ioutil"
+       "net/http"
+       "net/url"
+       "sort"
+       "strconv"
+       "strings"
+       "testing"
+
+       "github.com/stretchr/testify/assert"
+       "golang.org/x/net/nettest"
+
+       v1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
+)
+
+type fakeAPISIXGlobalRuleSrv struct {
+       globalRule map[string]json.RawMessage
+}
+
+func (srv *fakeAPISIXGlobalRuleSrv) ServeHTTP(w http.ResponseWriter, r 
*http.Request) {
+       defer r.Body.Close()
+
+       if !strings.HasPrefix(r.URL.Path, "/apisix/admin/global_rules") {
+               w.WriteHeader(http.StatusNotFound)
+               return
+       }
+
+       if r.Method == http.MethodGet {
+               resp := fakeListResp{
+                       Count: strconv.Itoa(len(srv.globalRule)),
+                       Node: fakeNode{
+                               Key: "/apisix/global_rules",
+                       },
+               }
+               var keys []string
+               for key := range srv.globalRule {
+                       keys = append(keys, key)
+               }
+               sort.Strings(keys)
+               for _, key := range keys {
+                       resp.Node.Items = append(resp.Node.Items, fakeItem{
+                               Key:   key,
+                               Value: srv.globalRule[key],
+                       })
+               }
+               w.WriteHeader(http.StatusOK)
+               data, _ := json.Marshal(resp)
+               _, _ = w.Write(data)
+               return
+       }
+
+       if r.Method == http.MethodDelete {
+               id := strings.TrimPrefix(r.URL.Path, 
"/apisix/admin/global_rules/")
+               id = "/apisix/admin/global_rules/" + id
+               code := http.StatusNotFound
+               if _, ok := srv.globalRule[id]; ok {
+                       delete(srv.globalRule, id)
+                       code = http.StatusOK
+               }
+               w.WriteHeader(code)
+       }
+
+       if r.Method == http.MethodPut {
+               paths := strings.Split(r.URL.Path, "/")
+               key := fmt.Sprintf("/apisix/admin/global_rules/%s", 
paths[len(paths)-1])
+               data, _ := ioutil.ReadAll(r.Body)
+               srv.globalRule[key] = data
+               w.WriteHeader(http.StatusCreated)
+               resp := fakeCreateResp{
+                       Action: "create",
+                       Node: fakeItem{
+                               Key:   key,
+                               Value: json.RawMessage(data),
+                       },
+               }
+               data, _ = json.Marshal(resp)
+               _, _ = w.Write(data)
+               return
+       }
+
+       if r.Method == http.MethodPatch {
+               id := strings.TrimPrefix(r.URL.Path, 
"/apisix/admin/global_rules/")
+               id = "/apisix/global_rules/" + id
+               if _, ok := srv.globalRule[id]; !ok {
+                       w.WriteHeader(http.StatusNotFound)
+                       return
+               }
+
+               data, _ := ioutil.ReadAll(r.Body)
+               srv.globalRule[id] = data
+
+               w.WriteHeader(http.StatusOK)
+               output := fmt.Sprintf(`{"action": "compareAndSwap", "node": 
{"key": "%s", "value": %s}}`, id, string(data))
+               _, _ = w.Write([]byte(output))
+               return
+       }
+}
+
+func runFakeGlobalRuleSrv(t *testing.T) *http.Server {
+       srv := &fakeAPISIXGlobalRuleSrv{
+               globalRule: make(map[string]json.RawMessage),
+       }
+
+       ln, _ := nettest.NewLocalListener("tcp")
+
+       httpSrv := &http.Server{
+               Addr:    ln.Addr().String(),
+               Handler: srv,
+       }
+
+       go func() {
+               if err := httpSrv.Serve(ln); err != nil && err != 
http.ErrServerClosed {
+                       t.Errorf("failed to run http server: %s", err)
+               }
+       }()
+
+       return httpSrv
+}
+
+func TestGlobalRuleClient(t *testing.T) {
+       srv := runFakeGlobalRuleSrv(t)
+       defer func() {
+               assert.Nil(t, srv.Shutdown(context.Background()))
+       }()
+
+       u := url.URL{
+               Scheme: "http",
+               Host:   srv.Addr,
+               Path:   "/apisix/admin",
+       }
+
+       closedCh := make(chan struct{})
+       close(closedCh)
+       cli := newGlobalRuleClient(&cluster{
+               baseURL:     u.String(),
+               cli:         http.DefaultClient,
+               cache:       &dummyCache{},
+               cacheSynced: closedCh,
+       })
+
+       // Create
+       obj, err := cli.Create(context.Background(), &v1.GlobalRule{
+               ID: "1",
+       })
+       assert.Nil(t, err)
+       assert.Equal(t, obj.ID, "1")
+
+       obj, err = cli.Create(context.Background(), &v1.GlobalRule{
+               ID: "2",
+       })
+       assert.Nil(t, err)
+       assert.Equal(t, obj.ID, "2")
+
+       // List
+       objs, err := cli.List(context.Background())
+       assert.Nil(t, err)
+       assert.Len(t, objs, 2)
+       assert.Equal(t, objs[0].ID, "1")
+       assert.Equal(t, objs[1].ID, "2")
+
+       // Delete then List
+       assert.Nil(t, cli.Delete(context.Background(), objs[0]))
+       objs, err = cli.List(context.Background())
+       assert.Nil(t, err)
+       assert.Len(t, objs, 1)
+       assert.Equal(t, "2", objs[0].ID)
+
+       // Patch then List
+       _, err = cli.Update(context.Background(), &v1.GlobalRule{
+               ID: "2",
+               Plugins: map[string]interface{}{
+                       "prometheus": struct{}{},
+               },
+       })
+       assert.Nil(t, err)
+       objs, err = cli.List(context.Background())
+       assert.Nil(t, err)
+       assert.Len(t, objs, 1)
+       assert.Equal(t, "2", objs[0].ID)
+}
diff --git a/pkg/apisix/nonexistentclient.go b/pkg/apisix/nonexistentclient.go
index 3113bae..d965cda 100644
--- a/pkg/apisix/nonexistentclient.go
+++ b/pkg/apisix/nonexistentclient.go
@@ -33,6 +33,7 @@ func newNonExistentCluster() *nonExistentCluster {
                        ssl:         &dummySSL{},
                        upstream:    &dummyUpstream{},
                        streamRoute: &dummyStreamRoute{},
+                       globalRule:  &dummyGlobalRule{},
                },
        }
 }
@@ -42,6 +43,7 @@ type embedDummyResourceImplementer struct {
        ssl         SSL
        upstream    Upstream
        streamRoute StreamRoute
+       globalRule  GlobalRule
 }
 
 type dummyRoute struct{}
@@ -132,6 +134,28 @@ func (f *dummyStreamRoute) Update(_ context.Context, _ 
*v1.StreamRoute) (*v1.Str
        return nil, ErrClusterNotExist
 }
 
+type dummyGlobalRule struct{}
+
+func (f *dummyGlobalRule) Get(_ context.Context, _ string) (*v1.GlobalRule, 
error) {
+       return nil, ErrClusterNotExist
+}
+
+func (f *dummyGlobalRule) List(_ context.Context) ([]*v1.GlobalRule, error) {
+       return nil, ErrClusterNotExist
+}
+
+func (f *dummyGlobalRule) Create(_ context.Context, _ *v1.GlobalRule) 
(*v1.GlobalRule, error) {
+       return nil, ErrClusterNotExist
+}
+
+func (f *dummyGlobalRule) Delete(_ context.Context, _ *v1.GlobalRule) error {
+       return ErrClusterNotExist
+}
+
+func (f *dummyGlobalRule) Update(_ context.Context, _ *v1.GlobalRule) 
(*v1.GlobalRule, error) {
+       return nil, ErrClusterNotExist
+}
+
 func (nc *nonExistentCluster) Route() Route {
        return nc.route
 }
@@ -164,15 +188,19 @@ func (c *dummyCache) InsertRoute(_ *v1.Route) error       
             { return
 func (c *dummyCache) InsertSSL(_ *v1.Ssl) error                        { 
return nil }
 func (c *dummyCache) InsertUpstream(_ *v1.Upstream) error              { 
return nil }
 func (c *dummyCache) InsertStreamRoute(_ *v1.StreamRoute) error        { 
return nil }
+func (c *dummyCache) InsertGlobalRule(_ *v1.GlobalRule) error          { 
return nil }
 func (c *dummyCache) GetRoute(_ string) (*v1.Route, error)             { 
return nil, cache.ErrNotFound }
 func (c *dummyCache) GetSSL(_ string) (*v1.Ssl, error)                 { 
return nil, cache.ErrNotFound }
 func (c *dummyCache) GetUpstream(_ string) (*v1.Upstream, error)       { 
return nil, cache.ErrNotFound }
 func (c *dummyCache) GetStreamRoute(_ string) (*v1.StreamRoute, error) { 
return nil, cache.ErrNotFound }
+func (c *dummyCache) GetGlobalRule(_ string) (*v1.GlobalRule, error)   { 
return nil, cache.ErrNotFound }
 func (c *dummyCache) ListRoutes() ([]*v1.Route, error)                 { 
return nil, nil }
 func (c *dummyCache) ListSSL() ([]*v1.Ssl, error)                      { 
return nil, nil }
 func (c *dummyCache) ListUpstreams() ([]*v1.Upstream, error)           { 
return nil, nil }
 func (c *dummyCache) ListStreamRoutes() ([]*v1.StreamRoute, error)     { 
return nil, nil }
+func (c *dummyCache) ListGlobalRules() ([]*v1.GlobalRule, error)       { 
return nil, nil }
 func (c *dummyCache) DeleteRoute(_ *v1.Route) error                    { 
return nil }
 func (c *dummyCache) DeleteSSL(_ *v1.Ssl) error                        { 
return nil }
 func (c *dummyCache) DeleteUpstream(_ *v1.Upstream) error              { 
return nil }
 func (c *dummyCache) DeleteStreamRoute(_ *v1.StreamRoute) error        { 
return nil }
+func (c *dummyCache) DeleteGlobalRule(_ *v1.GlobalRule) error          { 
return nil }
diff --git a/pkg/apisix/resource.go b/pkg/apisix/resource.go
index 5526a66..d24791f 100644
--- a/pkg/apisix/resource.go
+++ b/pkg/apisix/resource.go
@@ -137,3 +137,13 @@ func (i *item) ssl() (*v1.Ssl, error) {
        }
        return &ssl, nil
 }
+
+// globalRule decodes item.Value and converts it to v1.GlobalRule.
+func (i *item) globalRule() (*v1.GlobalRule, error) {
+       log.Debugf("got global_rule: %s", string(i.Value))
+       var globalRule v1.GlobalRule
+       if err := json.Unmarshal(i.Value, &globalRule); err != nil {
+               return nil, err
+       }
+       return &globalRule, nil
+}
diff --git a/pkg/kube/apisix/apis/config/v2alpha1/types.go 
b/pkg/kube/apisix/apis/config/v2alpha1/types.go
index 926addb..2c01b6f 100644
--- a/pkg/kube/apisix/apis/config/v2alpha1/types.go
+++ b/pkg/kube/apisix/apis/config/v2alpha1/types.go
@@ -232,8 +232,80 @@ type ApisixRouteTCPBackend struct {
 }
 
 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+
+// ApisixRouteList contains a list of ApisixRoute.
 type ApisixRouteList struct {
        metav1.TypeMeta `json:",inline" yaml:",inline"`
        metav1.ListMeta `json:"metadata" yaml:"metadata"`
        Items           []ApisixRoute `json:"items,omitempty" 
yaml:"items,omitempty"`
 }
+
+// +genclient
+// +genclient:nonNamespaced
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+// +kubebuilder:subresource:status
+
+// ApisixClusterConfig is the Schema for the ApisixClusterConfig resource.
+// An ApisixClusterConfig is used to identify an APISIX cluster, it's a
+// ClusterScoped resource so the name is unique.
+// It also contains some cluster-level configurations like monitoring.
+type ApisixClusterConfig struct {
+       metav1.TypeMeta   `json:",inline" yaml:",inline"`
+       metav1.ObjectMeta `json:"metadata" yaml:"metadata"`
+
+       // Spec defines the desired state of ApisixClusterConfigSpec.
+       Spec ApisixClusterConfigSpec `json:"spec" yaml:"spec"`
+}
+
+// ApisixClusterConfigSpec defines the desired state of 
ApisixClusterConfigSpec.
+type ApisixClusterConfigSpec struct {
+       // Monitoring categories all monitoring related features.
+       // +optional
+       Monitoring *ApisixClusterMonitoringConfig `json:"monitoring" 
yaml:"monitoring"`
+       // Admin contains the Admin API information about APISIX cluster.
+       // +optional
+       Admin *ApisixClusterAdminConfig `json:"admin" yaml:"admin"`
+}
+
+// ApisixClusterMonitoringConfig categories all monitoring related features.
+type ApisixClusterMonitoringConfig struct {
+       // Prometheus is the config for using Prometheus in APISIX Cluster.
+       // +optional
+       Prometheus ApisixClusterPrometheusConfig
+       // Skywalking is the config for using Skywalking in APISIX Cluster.
+       // +optional
+       Skywalking ApisixClusterSkywalkingConfig
+}
+
+// ApisixClusterPrometheusConfig is the config for using Prometheus in APISIX 
Cluster.
+type ApisixClusterPrometheusConfig struct {
+       // Enable means whether enable Prometheus or not.
+       Enable bool `json:"enable" yaml:"enable"`
+}
+
+// ApisixClusterSkywalkingConfig is the config for using Skywalking in APISIX 
Cluster.
+type ApisixClusterSkywalkingConfig struct {
+       // Enable means whether enable Skywalking or not.
+       Enable bool `json:"enable" yaml:"enable"`
+       // SampleRatio means the ratio to collect
+       SampleRatio float64 `json:"sampleRatio" yaml:"sampleRatio"`
+}
+
+// ApisixClusterAdminConfig is the admin config for the corresponding APISIX 
Cluster.
+type ApisixClusterAdminConfig struct {
+       // BaseURL is the base URL for the APISIX Admin API.
+       // It looks like 
"http://apisix-admin.default.svc.cluster.local:9080/apisix/admin";
+       BaseURL string
+       // AdminKey is used to verify the admin API user.
+       AdminKey string
+}
+
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+
+// ApisixClusterConfigList contains a list of ApisixClusterConfig.
+type ApisixClusterConfigList struct {
+       metav1.TypeMeta `json:",inline" yaml:",inline"`
+       metav1.ListMeta `json:"metadata" yaml:"metadata"`
+
+       Items []ApisixClusterConfig `json:"items" yaml:"items"`
+}
diff --git a/pkg/kube/apisix/apis/config/v2alpha1/zz_generated.deepcopy.go 
b/pkg/kube/apisix/apis/config/v2alpha1/zz_generated.deepcopy.go
index 16cedce..c11e22c 100644
--- a/pkg/kube/apisix/apis/config/v2alpha1/zz_generated.deepcopy.go
+++ b/pkg/kube/apisix/apis/config/v2alpha1/zz_generated.deepcopy.go
@@ -26,6 +26,158 @@ import (
 )
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, 
writing into out. in must be non-nil.
+func (in *ApisixClusterAdminConfig) DeepCopyInto(out 
*ApisixClusterAdminConfig) {
+       *out = *in
+       return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, 
creating a new ApisixClusterAdminConfig.
+func (in *ApisixClusterAdminConfig) DeepCopy() *ApisixClusterAdminConfig {
+       if in == nil {
+               return nil
+       }
+       out := new(ApisixClusterAdminConfig)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, 
writing into out. in must be non-nil.
+func (in *ApisixClusterConfig) DeepCopyInto(out *ApisixClusterConfig) {
+       *out = *in
+       out.TypeMeta = in.TypeMeta
+       in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+       in.Spec.DeepCopyInto(&out.Spec)
+       return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, 
creating a new ApisixClusterConfig.
+func (in *ApisixClusterConfig) DeepCopy() *ApisixClusterConfig {
+       if in == nil {
+               return nil
+       }
+       out := new(ApisixClusterConfig)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, 
creating a new runtime.Object.
+func (in *ApisixClusterConfig) DeepCopyObject() runtime.Object {
+       if c := in.DeepCopy(); c != nil {
+               return c
+       }
+       return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, 
writing into out. in must be non-nil.
+func (in *ApisixClusterConfigList) DeepCopyInto(out *ApisixClusterConfigList) {
+       *out = *in
+       out.TypeMeta = in.TypeMeta
+       in.ListMeta.DeepCopyInto(&out.ListMeta)
+       if in.Items != nil {
+               in, out := &in.Items, &out.Items
+               *out = make([]ApisixClusterConfig, len(*in))
+               for i := range *in {
+                       (*in)[i].DeepCopyInto(&(*out)[i])
+               }
+       }
+       return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, 
creating a new ApisixClusterConfigList.
+func (in *ApisixClusterConfigList) DeepCopy() *ApisixClusterConfigList {
+       if in == nil {
+               return nil
+       }
+       out := new(ApisixClusterConfigList)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, 
creating a new runtime.Object.
+func (in *ApisixClusterConfigList) DeepCopyObject() runtime.Object {
+       if c := in.DeepCopy(); c != nil {
+               return c
+       }
+       return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, 
writing into out. in must be non-nil.
+func (in *ApisixClusterConfigSpec) DeepCopyInto(out *ApisixClusterConfigSpec) {
+       *out = *in
+       if in.Monitoring != nil {
+               in, out := &in.Monitoring, &out.Monitoring
+               *out = new(ApisixClusterMonitoringConfig)
+               **out = **in
+       }
+       if in.Admin != nil {
+               in, out := &in.Admin, &out.Admin
+               *out = new(ApisixClusterAdminConfig)
+               **out = **in
+       }
+       return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, 
creating a new ApisixClusterConfigSpec.
+func (in *ApisixClusterConfigSpec) DeepCopy() *ApisixClusterConfigSpec {
+       if in == nil {
+               return nil
+       }
+       out := new(ApisixClusterConfigSpec)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, 
writing into out. in must be non-nil.
+func (in *ApisixClusterMonitoringConfig) DeepCopyInto(out 
*ApisixClusterMonitoringConfig) {
+       *out = *in
+       out.Prometheus = in.Prometheus
+       out.Skywalking = in.Skywalking
+       return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, 
creating a new ApisixClusterMonitoringConfig.
+func (in *ApisixClusterMonitoringConfig) DeepCopy() 
*ApisixClusterMonitoringConfig {
+       if in == nil {
+               return nil
+       }
+       out := new(ApisixClusterMonitoringConfig)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, 
writing into out. in must be non-nil.
+func (in *ApisixClusterPrometheusConfig) DeepCopyInto(out 
*ApisixClusterPrometheusConfig) {
+       *out = *in
+       return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, 
creating a new ApisixClusterPrometheusConfig.
+func (in *ApisixClusterPrometheusConfig) DeepCopy() 
*ApisixClusterPrometheusConfig {
+       if in == nil {
+               return nil
+       }
+       out := new(ApisixClusterPrometheusConfig)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, 
writing into out. in must be non-nil.
+func (in *ApisixClusterSkywalkingConfig) DeepCopyInto(out 
*ApisixClusterSkywalkingConfig) {
+       *out = *in
+       return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, 
creating a new ApisixClusterSkywalkingConfig.
+func (in *ApisixClusterSkywalkingConfig) DeepCopy() 
*ApisixClusterSkywalkingConfig {
+       if in == nil {
+               return nil
+       }
+       out := new(ApisixClusterSkywalkingConfig)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, 
writing into out. in must be non-nil.
 func (in *ApisixRoute) DeepCopyInto(out *ApisixRoute) {
        *out = *in
        out.TypeMeta = in.TypeMeta
diff --git 
a/pkg/kube/apisix/client/clientset/versioned/typed/config/v2alpha1/apisixclusterconfig.go
 
b/pkg/kube/apisix/client/clientset/versioned/typed/config/v2alpha1/apisixclusterconfig.go
new file mode 100644
index 0000000..bcd8c56
--- /dev/null
+++ 
b/pkg/kube/apisix/client/clientset/versioned/typed/config/v2alpha1/apisixclusterconfig.go
@@ -0,0 +1,168 @@
+/*
+Copyright The Kubernetes Authors.
+
+Licensed 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.
+*/
+
+// Code generated by client-gen. DO NOT EDIT.
+
+package v2alpha1
+
+import (
+       "context"
+       "time"
+
+       v2alpha1 
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2alpha1"
+       scheme 
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/client/clientset/versioned/scheme"
+       v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       types "k8s.io/apimachinery/pkg/types"
+       watch "k8s.io/apimachinery/pkg/watch"
+       rest "k8s.io/client-go/rest"
+)
+
+// ApisixClusterConfigsGetter has a method to return a 
ApisixClusterConfigInterface.
+// A group's client should implement this interface.
+type ApisixClusterConfigsGetter interface {
+       ApisixClusterConfigs() ApisixClusterConfigInterface
+}
+
+// ApisixClusterConfigInterface has methods to work with ApisixClusterConfig 
resources.
+type ApisixClusterConfigInterface interface {
+       Create(ctx context.Context, apisixClusterConfig 
*v2alpha1.ApisixClusterConfig, opts v1.CreateOptions) 
(*v2alpha1.ApisixClusterConfig, error)
+       Update(ctx context.Context, apisixClusterConfig 
*v2alpha1.ApisixClusterConfig, opts v1.UpdateOptions) 
(*v2alpha1.ApisixClusterConfig, error)
+       Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
+       DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts 
v1.ListOptions) error
+       Get(ctx context.Context, name string, opts v1.GetOptions) 
(*v2alpha1.ApisixClusterConfig, error)
+       List(ctx context.Context, opts v1.ListOptions) 
(*v2alpha1.ApisixClusterConfigList, error)
+       Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
+       Patch(ctx context.Context, name string, pt types.PatchType, data 
[]byte, opts v1.PatchOptions, subresources ...string) (result 
*v2alpha1.ApisixClusterConfig, err error)
+       ApisixClusterConfigExpansion
+}
+
+// apisixClusterConfigs implements ApisixClusterConfigInterface
+type apisixClusterConfigs struct {
+       client rest.Interface
+}
+
+// newApisixClusterConfigs returns a ApisixClusterConfigs
+func newApisixClusterConfigs(c *ApisixV2alpha1Client) *apisixClusterConfigs {
+       return &apisixClusterConfigs{
+               client: c.RESTClient(),
+       }
+}
+
+// Get takes name of the apisixClusterConfig, and returns the corresponding 
apisixClusterConfig object, and an error if there is any.
+func (c *apisixClusterConfigs) Get(ctx context.Context, name string, options 
v1.GetOptions) (result *v2alpha1.ApisixClusterConfig, err error) {
+       result = &v2alpha1.ApisixClusterConfig{}
+       err = c.client.Get().
+               Resource("apisixclusterconfigs").
+               Name(name).
+               VersionedParams(&options, scheme.ParameterCodec).
+               Do(ctx).
+               Into(result)
+       return
+}
+
+// List takes label and field selectors, and returns the list of 
ApisixClusterConfigs that match those selectors.
+func (c *apisixClusterConfigs) List(ctx context.Context, opts v1.ListOptions) 
(result *v2alpha1.ApisixClusterConfigList, err error) {
+       var timeout time.Duration
+       if opts.TimeoutSeconds != nil {
+               timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
+       }
+       result = &v2alpha1.ApisixClusterConfigList{}
+       err = c.client.Get().
+               Resource("apisixclusterconfigs").
+               VersionedParams(&opts, scheme.ParameterCodec).
+               Timeout(timeout).
+               Do(ctx).
+               Into(result)
+       return
+}
+
+// Watch returns a watch.Interface that watches the requested 
apisixClusterConfigs.
+func (c *apisixClusterConfigs) Watch(ctx context.Context, opts v1.ListOptions) 
(watch.Interface, error) {
+       var timeout time.Duration
+       if opts.TimeoutSeconds != nil {
+               timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
+       }
+       opts.Watch = true
+       return c.client.Get().
+               Resource("apisixclusterconfigs").
+               VersionedParams(&opts, scheme.ParameterCodec).
+               Timeout(timeout).
+               Watch(ctx)
+}
+
+// Create takes the representation of a apisixClusterConfig and creates it.  
Returns the server's representation of the apisixClusterConfig, and an error, 
if there is any.
+func (c *apisixClusterConfigs) Create(ctx context.Context, apisixClusterConfig 
*v2alpha1.ApisixClusterConfig, opts v1.CreateOptions) (result 
*v2alpha1.ApisixClusterConfig, err error) {
+       result = &v2alpha1.ApisixClusterConfig{}
+       err = c.client.Post().
+               Resource("apisixclusterconfigs").
+               VersionedParams(&opts, scheme.ParameterCodec).
+               Body(apisixClusterConfig).
+               Do(ctx).
+               Into(result)
+       return
+}
+
+// Update takes the representation of a apisixClusterConfig and updates it. 
Returns the server's representation of the apisixClusterConfig, and an error, 
if there is any.
+func (c *apisixClusterConfigs) Update(ctx context.Context, apisixClusterConfig 
*v2alpha1.ApisixClusterConfig, opts v1.UpdateOptions) (result 
*v2alpha1.ApisixClusterConfig, err error) {
+       result = &v2alpha1.ApisixClusterConfig{}
+       err = c.client.Put().
+               Resource("apisixclusterconfigs").
+               Name(apisixClusterConfig.Name).
+               VersionedParams(&opts, scheme.ParameterCodec).
+               Body(apisixClusterConfig).
+               Do(ctx).
+               Into(result)
+       return
+}
+
+// Delete takes name of the apisixClusterConfig and deletes it. Returns an 
error if one occurs.
+func (c *apisixClusterConfigs) Delete(ctx context.Context, name string, opts 
v1.DeleteOptions) error {
+       return c.client.Delete().
+               Resource("apisixclusterconfigs").
+               Name(name).
+               Body(&opts).
+               Do(ctx).
+               Error()
+}
+
+// DeleteCollection deletes a collection of objects.
+func (c *apisixClusterConfigs) DeleteCollection(ctx context.Context, opts 
v1.DeleteOptions, listOpts v1.ListOptions) error {
+       var timeout time.Duration
+       if listOpts.TimeoutSeconds != nil {
+               timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second
+       }
+       return c.client.Delete().
+               Resource("apisixclusterconfigs").
+               VersionedParams(&listOpts, scheme.ParameterCodec).
+               Timeout(timeout).
+               Body(&opts).
+               Do(ctx).
+               Error()
+}
+
+// Patch applies the patch and returns the patched apisixClusterConfig.
+func (c *apisixClusterConfigs) Patch(ctx context.Context, name string, pt 
types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) 
(result *v2alpha1.ApisixClusterConfig, err error) {
+       result = &v2alpha1.ApisixClusterConfig{}
+       err = c.client.Patch(pt).
+               Resource("apisixclusterconfigs").
+               Name(name).
+               SubResource(subresources...).
+               VersionedParams(&opts, scheme.ParameterCodec).
+               Body(data).
+               Do(ctx).
+               Into(result)
+       return
+}
diff --git 
a/pkg/kube/apisix/client/clientset/versioned/typed/config/v2alpha1/config_client.go
 
b/pkg/kube/apisix/client/clientset/versioned/typed/config/v2alpha1/config_client.go
index ade8d12..1197eb0 100644
--- 
a/pkg/kube/apisix/client/clientset/versioned/typed/config/v2alpha1/config_client.go
+++ 
b/pkg/kube/apisix/client/clientset/versioned/typed/config/v2alpha1/config_client.go
@@ -26,6 +26,7 @@ import (
 
 type ApisixV2alpha1Interface interface {
        RESTClient() rest.Interface
+       ApisixClusterConfigsGetter
        ApisixRoutesGetter
 }
 
@@ -34,6 +35,10 @@ type ApisixV2alpha1Client struct {
        restClient rest.Interface
 }
 
+func (c *ApisixV2alpha1Client) ApisixClusterConfigs() 
ApisixClusterConfigInterface {
+       return newApisixClusterConfigs(c)
+}
+
 func (c *ApisixV2alpha1Client) ApisixRoutes(namespace string) 
ApisixRouteInterface {
        return newApisixRoutes(c, namespace)
 }
diff --git 
a/pkg/kube/apisix/client/clientset/versioned/typed/config/v2alpha1/fake/fake_apisixclusterconfig.go
 
b/pkg/kube/apisix/client/clientset/versioned/typed/config/v2alpha1/fake/fake_apisixclusterconfig.go
new file mode 100644
index 0000000..cc22ad7
--- /dev/null
+++ 
b/pkg/kube/apisix/client/clientset/versioned/typed/config/v2alpha1/fake/fake_apisixclusterconfig.go
@@ -0,0 +1,122 @@
+/*
+Copyright The Kubernetes Authors.
+
+Licensed 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.
+*/
+
+// Code generated by client-gen. DO NOT EDIT.
+
+package fake
+
+import (
+       "context"
+
+       v2alpha1 
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2alpha1"
+       v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       labels "k8s.io/apimachinery/pkg/labels"
+       schema "k8s.io/apimachinery/pkg/runtime/schema"
+       types "k8s.io/apimachinery/pkg/types"
+       watch "k8s.io/apimachinery/pkg/watch"
+       testing "k8s.io/client-go/testing"
+)
+
+// FakeApisixClusterConfigs implements ApisixClusterConfigInterface
+type FakeApisixClusterConfigs struct {
+       Fake *FakeApisixV2alpha1
+}
+
+var apisixclusterconfigsResource = schema.GroupVersionResource{Group: 
"apisix.apache.org", Version: "v2alpha1", Resource: "apisixclusterconfigs"}
+
+var apisixclusterconfigsKind = schema.GroupVersionKind{Group: 
"apisix.apache.org", Version: "v2alpha1", Kind: "ApisixClusterConfig"}
+
+// Get takes name of the apisixClusterConfig, and returns the corresponding 
apisixClusterConfig object, and an error if there is any.
+func (c *FakeApisixClusterConfigs) Get(ctx context.Context, name string, 
options v1.GetOptions) (result *v2alpha1.ApisixClusterConfig, err error) {
+       obj, err := c.Fake.
+               Invokes(testing.NewRootGetAction(apisixclusterconfigsResource, 
name), &v2alpha1.ApisixClusterConfig{})
+       if obj == nil {
+               return nil, err
+       }
+       return obj.(*v2alpha1.ApisixClusterConfig), err
+}
+
+// List takes label and field selectors, and returns the list of 
ApisixClusterConfigs that match those selectors.
+func (c *FakeApisixClusterConfigs) List(ctx context.Context, opts 
v1.ListOptions) (result *v2alpha1.ApisixClusterConfigList, err error) {
+       obj, err := c.Fake.
+               Invokes(testing.NewRootListAction(apisixclusterconfigsResource, 
apisixclusterconfigsKind, opts), &v2alpha1.ApisixClusterConfigList{})
+       if obj == nil {
+               return nil, err
+       }
+
+       label, _, _ := testing.ExtractFromListOptions(opts)
+       if label == nil {
+               label = labels.Everything()
+       }
+       list := &v2alpha1.ApisixClusterConfigList{ListMeta: 
obj.(*v2alpha1.ApisixClusterConfigList).ListMeta}
+       for _, item := range obj.(*v2alpha1.ApisixClusterConfigList).Items {
+               if label.Matches(labels.Set(item.Labels)) {
+                       list.Items = append(list.Items, item)
+               }
+       }
+       return list, err
+}
+
+// Watch returns a watch.Interface that watches the requested 
apisixClusterConfigs.
+func (c *FakeApisixClusterConfigs) Watch(ctx context.Context, opts 
v1.ListOptions) (watch.Interface, error) {
+       return c.Fake.
+               
InvokesWatch(testing.NewRootWatchAction(apisixclusterconfigsResource, opts))
+}
+
+// Create takes the representation of a apisixClusterConfig and creates it.  
Returns the server's representation of the apisixClusterConfig, and an error, 
if there is any.
+func (c *FakeApisixClusterConfigs) Create(ctx context.Context, 
apisixClusterConfig *v2alpha1.ApisixClusterConfig, opts v1.CreateOptions) 
(result *v2alpha1.ApisixClusterConfig, err error) {
+       obj, err := c.Fake.
+               
Invokes(testing.NewRootCreateAction(apisixclusterconfigsResource, 
apisixClusterConfig), &v2alpha1.ApisixClusterConfig{})
+       if obj == nil {
+               return nil, err
+       }
+       return obj.(*v2alpha1.ApisixClusterConfig), err
+}
+
+// Update takes the representation of a apisixClusterConfig and updates it. 
Returns the server's representation of the apisixClusterConfig, and an error, 
if there is any.
+func (c *FakeApisixClusterConfigs) Update(ctx context.Context, 
apisixClusterConfig *v2alpha1.ApisixClusterConfig, opts v1.UpdateOptions) 
(result *v2alpha1.ApisixClusterConfig, err error) {
+       obj, err := c.Fake.
+               
Invokes(testing.NewRootUpdateAction(apisixclusterconfigsResource, 
apisixClusterConfig), &v2alpha1.ApisixClusterConfig{})
+       if obj == nil {
+               return nil, err
+       }
+       return obj.(*v2alpha1.ApisixClusterConfig), err
+}
+
+// Delete takes name of the apisixClusterConfig and deletes it. Returns an 
error if one occurs.
+func (c *FakeApisixClusterConfigs) Delete(ctx context.Context, name string, 
opts v1.DeleteOptions) error {
+       _, err := c.Fake.
+               
Invokes(testing.NewRootDeleteAction(apisixclusterconfigsResource, name), 
&v2alpha1.ApisixClusterConfig{})
+       return err
+}
+
+// DeleteCollection deletes a collection of objects.
+func (c *FakeApisixClusterConfigs) DeleteCollection(ctx context.Context, opts 
v1.DeleteOptions, listOpts v1.ListOptions) error {
+       action := 
testing.NewRootDeleteCollectionAction(apisixclusterconfigsResource, listOpts)
+
+       _, err := c.Fake.Invokes(action, &v2alpha1.ApisixClusterConfigList{})
+       return err
+}
+
+// Patch applies the patch and returns the patched apisixClusterConfig.
+func (c *FakeApisixClusterConfigs) Patch(ctx context.Context, name string, pt 
types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) 
(result *v2alpha1.ApisixClusterConfig, err error) {
+       obj, err := c.Fake.
+               
Invokes(testing.NewRootPatchSubresourceAction(apisixclusterconfigsResource, 
name, pt, data, subresources...), &v2alpha1.ApisixClusterConfig{})
+       if obj == nil {
+               return nil, err
+       }
+       return obj.(*v2alpha1.ApisixClusterConfig), err
+}
diff --git 
a/pkg/kube/apisix/client/clientset/versioned/typed/config/v2alpha1/fake/fake_config_client.go
 
b/pkg/kube/apisix/client/clientset/versioned/typed/config/v2alpha1/fake/fake_config_client.go
index 448bafe..16cabaf 100644
--- 
a/pkg/kube/apisix/client/clientset/versioned/typed/config/v2alpha1/fake/fake_config_client.go
+++ 
b/pkg/kube/apisix/client/clientset/versioned/typed/config/v2alpha1/fake/fake_config_client.go
@@ -28,6 +28,10 @@ type FakeApisixV2alpha1 struct {
        *testing.Fake
 }
 
+func (c *FakeApisixV2alpha1) ApisixClusterConfigs() 
v2alpha1.ApisixClusterConfigInterface {
+       return &FakeApisixClusterConfigs{c}
+}
+
 func (c *FakeApisixV2alpha1) ApisixRoutes(namespace string) 
v2alpha1.ApisixRouteInterface {
        return &FakeApisixRoutes{c, namespace}
 }
diff --git 
a/pkg/kube/apisix/client/clientset/versioned/typed/config/v2alpha1/generated_expansion.go
 
b/pkg/kube/apisix/client/clientset/versioned/typed/config/v2alpha1/generated_expansion.go
index 980dad5..b1775c9 100644
--- 
a/pkg/kube/apisix/client/clientset/versioned/typed/config/v2alpha1/generated_expansion.go
+++ 
b/pkg/kube/apisix/client/clientset/versioned/typed/config/v2alpha1/generated_expansion.go
@@ -18,4 +18,6 @@ limitations under the License.
 
 package v2alpha1
 
+type ApisixClusterConfigExpansion interface{}
+
 type ApisixRouteExpansion interface{}
diff --git 
a/pkg/kube/apisix/client/informers/externalversions/config/v2alpha1/apisixclusterconfig.go
 
b/pkg/kube/apisix/client/informers/externalversions/config/v2alpha1/apisixclusterconfig.go
new file mode 100644
index 0000000..4744d8a
--- /dev/null
+++ 
b/pkg/kube/apisix/client/informers/externalversions/config/v2alpha1/apisixclusterconfig.go
@@ -0,0 +1,89 @@
+/*
+Copyright The Kubernetes Authors.
+
+Licensed 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.
+*/
+
+// Code generated by informer-gen. DO NOT EDIT.
+
+package v2alpha1
+
+import (
+       "context"
+       time "time"
+
+       configv2alpha1 
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2alpha1"
+       versioned 
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/client/clientset/versioned"
+       internalinterfaces 
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/client/informers/externalversions/internalinterfaces"
+       v2alpha1 
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/client/listers/config/v2alpha1"
+       v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       runtime "k8s.io/apimachinery/pkg/runtime"
+       watch "k8s.io/apimachinery/pkg/watch"
+       cache "k8s.io/client-go/tools/cache"
+)
+
+// ApisixClusterConfigInformer provides access to a shared informer and lister 
for
+// ApisixClusterConfigs.
+type ApisixClusterConfigInformer interface {
+       Informer() cache.SharedIndexInformer
+       Lister() v2alpha1.ApisixClusterConfigLister
+}
+
+type apisixClusterConfigInformer struct {
+       factory          internalinterfaces.SharedInformerFactory
+       tweakListOptions internalinterfaces.TweakListOptionsFunc
+}
+
+// NewApisixClusterConfigInformer constructs a new informer for 
ApisixClusterConfig type.
+// Always prefer using an informer factory to get a shared informer instead of 
getting an independent
+// one. This reduces memory footprint and number of connections to the server.
+func NewApisixClusterConfigInformer(client versioned.Interface, resyncPeriod 
time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
+       return NewFilteredApisixClusterConfigInformer(client, resyncPeriod, 
indexers, nil)
+}
+
+// NewFilteredApisixClusterConfigInformer constructs a new informer for 
ApisixClusterConfig type.
+// Always prefer using an informer factory to get a shared informer instead of 
getting an independent
+// one. This reduces memory footprint and number of connections to the server.
+func NewFilteredApisixClusterConfigInformer(client versioned.Interface, 
resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions 
internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
+       return cache.NewSharedIndexInformer(
+               &cache.ListWatch{
+                       ListFunc: func(options v1.ListOptions) (runtime.Object, 
error) {
+                               if tweakListOptions != nil {
+                                       tweakListOptions(&options)
+                               }
+                               return 
client.ApisixV2alpha1().ApisixClusterConfigs().List(context.TODO(), options)
+                       },
+                       WatchFunc: func(options v1.ListOptions) 
(watch.Interface, error) {
+                               if tweakListOptions != nil {
+                                       tweakListOptions(&options)
+                               }
+                               return 
client.ApisixV2alpha1().ApisixClusterConfigs().Watch(context.TODO(), options)
+                       },
+               },
+               &configv2alpha1.ApisixClusterConfig{},
+               resyncPeriod,
+               indexers,
+       )
+}
+
+func (f *apisixClusterConfigInformer) defaultInformer(client 
versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
+       return NewFilteredApisixClusterConfigInformer(client, resyncPeriod, 
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, 
f.tweakListOptions)
+}
+
+func (f *apisixClusterConfigInformer) Informer() cache.SharedIndexInformer {
+       return f.factory.InformerFor(&configv2alpha1.ApisixClusterConfig{}, 
f.defaultInformer)
+}
+
+func (f *apisixClusterConfigInformer) Lister() 
v2alpha1.ApisixClusterConfigLister {
+       return v2alpha1.NewApisixClusterConfigLister(f.Informer().GetIndexer())
+}
diff --git 
a/pkg/kube/apisix/client/informers/externalversions/config/v2alpha1/interface.go
 
b/pkg/kube/apisix/client/informers/externalversions/config/v2alpha1/interface.go
index 620edb7..a787523 100644
--- 
a/pkg/kube/apisix/client/informers/externalversions/config/v2alpha1/interface.go
+++ 
b/pkg/kube/apisix/client/informers/externalversions/config/v2alpha1/interface.go
@@ -24,6 +24,8 @@ import (
 
 // Interface provides access to all the informers in this group version.
 type Interface interface {
+       // ApisixClusterConfigs returns a ApisixClusterConfigInformer.
+       ApisixClusterConfigs() ApisixClusterConfigInformer
        // ApisixRoutes returns a ApisixRouteInformer.
        ApisixRoutes() ApisixRouteInformer
 }
@@ -39,6 +41,11 @@ func New(f internalinterfaces.SharedInformerFactory, 
namespace string, tweakList
        return &version{factory: f, namespace: namespace, tweakListOptions: 
tweakListOptions}
 }
 
+// ApisixClusterConfigs returns a ApisixClusterConfigInformer.
+func (v *version) ApisixClusterConfigs() ApisixClusterConfigInformer {
+       return &apisixClusterConfigInformer{factory: v.factory, 
tweakListOptions: v.tweakListOptions}
+}
+
 // ApisixRoutes returns a ApisixRouteInformer.
 func (v *version) ApisixRoutes() ApisixRouteInformer {
        return &apisixRouteInformer{factory: v.factory, namespace: v.namespace, 
tweakListOptions: v.tweakListOptions}
diff --git a/pkg/kube/apisix/client/informers/externalversions/generic.go 
b/pkg/kube/apisix/client/informers/externalversions/generic.go
index 8c2e73e..e24f2f8 100644
--- a/pkg/kube/apisix/client/informers/externalversions/generic.go
+++ b/pkg/kube/apisix/client/informers/externalversions/generic.go
@@ -62,6 +62,8 @@ func (f *sharedInformerFactory) ForResource(resource 
schema.GroupVersionResource
                return &genericInformer{resource: resource.GroupResource(), 
informer: f.Apisix().V1().ApisixUpstreams().Informer()}, nil
 
                // Group=apisix.apache.org, Version=v2alpha1
+       case v2alpha1.SchemeGroupVersion.WithResource("apisixclusterconfigs"):
+               return &genericInformer{resource: resource.GroupResource(), 
informer: f.Apisix().V2alpha1().ApisixClusterConfigs().Informer()}, nil
        case v2alpha1.SchemeGroupVersion.WithResource("apisixroutes"):
                return &genericInformer{resource: resource.GroupResource(), 
informer: f.Apisix().V2alpha1().ApisixRoutes().Informer()}, nil
 
diff --git 
a/pkg/kube/apisix/client/listers/config/v2alpha1/apisixclusterconfig.go 
b/pkg/kube/apisix/client/listers/config/v2alpha1/apisixclusterconfig.go
new file mode 100644
index 0000000..296e181
--- /dev/null
+++ b/pkg/kube/apisix/client/listers/config/v2alpha1/apisixclusterconfig.go
@@ -0,0 +1,68 @@
+/*
+Copyright The Kubernetes Authors.
+
+Licensed 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.
+*/
+
+// Code generated by lister-gen. DO NOT EDIT.
+
+package v2alpha1
+
+import (
+       v2alpha1 
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2alpha1"
+       "k8s.io/apimachinery/pkg/api/errors"
+       "k8s.io/apimachinery/pkg/labels"
+       "k8s.io/client-go/tools/cache"
+)
+
+// ApisixClusterConfigLister helps list ApisixClusterConfigs.
+// All objects returned here must be treated as read-only.
+type ApisixClusterConfigLister interface {
+       // List lists all ApisixClusterConfigs in the indexer.
+       // Objects returned here must be treated as read-only.
+       List(selector labels.Selector) (ret []*v2alpha1.ApisixClusterConfig, 
err error)
+       // Get retrieves the ApisixClusterConfig from the index for a given 
name.
+       // Objects returned here must be treated as read-only.
+       Get(name string) (*v2alpha1.ApisixClusterConfig, error)
+       ApisixClusterConfigListerExpansion
+}
+
+// apisixClusterConfigLister implements the ApisixClusterConfigLister 
interface.
+type apisixClusterConfigLister struct {
+       indexer cache.Indexer
+}
+
+// NewApisixClusterConfigLister returns a new ApisixClusterConfigLister.
+func NewApisixClusterConfigLister(indexer cache.Indexer) 
ApisixClusterConfigLister {
+       return &apisixClusterConfigLister{indexer: indexer}
+}
+
+// List lists all ApisixClusterConfigs in the indexer.
+func (s *apisixClusterConfigLister) List(selector labels.Selector) (ret 
[]*v2alpha1.ApisixClusterConfig, err error) {
+       err = cache.ListAll(s.indexer, selector, func(m interface{}) {
+               ret = append(ret, m.(*v2alpha1.ApisixClusterConfig))
+       })
+       return ret, err
+}
+
+// Get retrieves the ApisixClusterConfig from the index for a given name.
+func (s *apisixClusterConfigLister) Get(name string) 
(*v2alpha1.ApisixClusterConfig, error) {
+       obj, exists, err := s.indexer.GetByKey(name)
+       if err != nil {
+               return nil, err
+       }
+       if !exists {
+               return nil, 
errors.NewNotFound(v2alpha1.Resource("apisixclusterconfig"), name)
+       }
+       return obj.(*v2alpha1.ApisixClusterConfig), nil
+}
diff --git 
a/pkg/kube/apisix/client/listers/config/v2alpha1/expansion_generated.go 
b/pkg/kube/apisix/client/listers/config/v2alpha1/expansion_generated.go
index e26b85d..5d5ca57 100644
--- a/pkg/kube/apisix/client/listers/config/v2alpha1/expansion_generated.go
+++ b/pkg/kube/apisix/client/listers/config/v2alpha1/expansion_generated.go
@@ -18,6 +18,10 @@ limitations under the License.
 
 package v2alpha1
 
+// ApisixClusterConfigListerExpansion allows custom methods to be added to
+// ApisixClusterConfigLister.
+type ApisixClusterConfigListerExpansion interface{}
+
 // ApisixRouteListerExpansion allows custom methods to be added to
 // ApisixRouteLister.
 type ApisixRouteListerExpansion interface{}
diff --git a/pkg/types/apisix/v1/types.go b/pkg/types/apisix/v1/types.go
index 6ab1aa0..7acbf92 100644
--- a/pkg/types/apisix/v1/types.go
+++ b/pkg/types/apisix/v1/types.go
@@ -323,7 +323,7 @@ type TrafficSplitConfigRuleWeightedUpstream struct {
        Weight     int    `json:"weight"`
 }
 
-// StreamRoute represents the stream route object in APISIX.
+// StreamRoute represents the stream_route object in APISIX.
 // +k8s:deepcopy-gen=true
 type StreamRoute struct {
        // TODO metadata should use Metadata type
@@ -334,6 +334,13 @@ type StreamRoute struct {
        UpstreamId string            `json:"upstream_id,omitempty" 
yaml:"upstream_id,omitempty"`
 }
 
+// GlobalRule represents the global_rule object in APISIX.
+// +k8s:deepcopy-gen=true
+type GlobalRule struct {
+       ID      string  `json:"id,omitempty" yaml:"id,omitempty"`
+       Plugins Plugins `json:"plugins,omitempty" yaml:"plugins,omitempty"`
+}
+
 // NewDefaultUpstream returns an empty Upstream with default values.
 func NewDefaultUpstream() *Upstream {
        return &Upstream{
diff --git a/pkg/types/apisix/v1/zz_generated.deepcopy.go 
b/pkg/types/apisix/v1/zz_generated.deepcopy.go
index 8fa8c11..a264ac0 100644
--- a/pkg/types/apisix/v1/zz_generated.deepcopy.go
+++ b/pkg/types/apisix/v1/zz_generated.deepcopy.go
@@ -21,6 +21,23 @@ limitations under the License.
 package v1
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, 
writing into out. in must be non-nil.
+func (in *GlobalRule) DeepCopyInto(out *GlobalRule) {
+       *out = *in
+       in.Plugins.DeepCopyInto(&out.Plugins)
+       return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, 
creating a new GlobalRule.
+func (in *GlobalRule) DeepCopy() *GlobalRule {
+       if in == nil {
+               return nil
+       }
+       out := new(GlobalRule)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, 
writing into out. in must be non-nil.
 func (in *Metadata) DeepCopyInto(out *Metadata) {
        *out = *in
        if in.Labels != nil {
diff --git a/samples/deploy/crd/v1beta1/ApisixClusterConfig.yaml 
b/samples/deploy/crd/v1beta1/ApisixClusterConfig.yaml
new file mode 100644
index 0000000..b1d11d1
--- /dev/null
+++ b/samples/deploy/crd/v1beta1/ApisixClusterConfig.yaml
@@ -0,0 +1,71 @@
+#
+# 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.
+#
+
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  name: apisixclusterconfigs.apisix.apache.org
+spec:
+  group: apisix.apache.org
+  versions:
+    - name: v2alpha1
+      served: true
+      storage: true
+  scope: Cluster
+  names:
+    plural: apisixclusterconfigs
+    singular: apisixclusterconfig
+    kind: ApisixClusterConfig
+    shortNames:
+      - acc
+  preserveUnknownFields: false
+  subresources:
+    status: {}
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          properties:
+            admin:
+              type: object
+              required:
+                - baseURL
+              properties:
+                baseURL:
+                  type: string
+                  pattern: "https?://[^:]+:(\\d+)"
+                adminKey:
+                  type: string
+            monitoring:
+              type: object
+              properties:
+                prometheus:
+                  type: object
+                  properties:
+                    enable:
+                      type: boolean
+                skywalking:
+                  type: object
+                  properties:
+                    enable:
+                      type: boolean
+                    sampleRatio:
+                      type: number
+                      minimum: 0.00001
+                      maximum: 1
diff --git a/samples/deploy/crd/v1beta1/kustomization.yaml 
b/samples/deploy/crd/v1beta1/kustomization.yaml
index 27a979f..1a2b65c 100644
--- a/samples/deploy/crd/v1beta1/kustomization.yaml
+++ b/samples/deploy/crd/v1beta1/kustomization.yaml
@@ -19,3 +19,4 @@ resources:
   - ./ApisixRoute.yaml
   - ./ApisixUpstream.yaml
   - ./ApisixTls.yaml
+  - ./ApisixClusterConfig.yaml

Reply via email to